Bug#1109358: unblock: mina2/2.2.1-4

Pierre Gruet pgt at debian.org
Tue Jul 15 23:02:07 BST 2025


Package: release.debian.org
Severity: normal
X-Debbugs-Cc: mina2 at packages.debian.org
Control: affects -1 + src:mina2
User: release.debian.org at packages.debian.org
Usertags: unblock

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Dear Release team,

This is a request for upload to unstable + unblock for the key package mina2,
which has NOT yet been uploaded to unstable.

[ Reason ]
mina2 is affected by grave bug #1091530 about CVE-2024-52046. I have prepared
an upload that fixes it by following the security tracker
    https://security-tracker.debian.org/tracker/CVE-2024-52046

As
    https://lists.apache.org/thread/4wxktgjpggdbto15d515wdctohb0qmv8
explains, the CVE is fixed by applying commit cdb59eb, visible at
    https://github.com/apache/mina/commit/cdb59eb6131696a440870ab89ad0e20804eb5ca7#diff-cb3019e35ae0f7cccf4b546a473fbb784e94624dc736a754e3ad01633ceaf32dR401-R402
and by reworking calls to ObjectSerializationDecoder in the rdeps of mina2. I
checked that no Debian package calls this class.

My only change to the package is applying the above-cited commit.

[ Impact ]
If the unblock is not granted, we will ship mina2 to trixie with the above CVE
characterized by a grave bug.

[ Tests ]
I built mina2 and all its rdeps with this change, everything is OK.

[ Risks ]
In my opinion, risks seem to be limited as mina2 is key only as a B-D of key
packages. I could check that the rdeps build with the proposed change. However,
mina2 includes no test nor autopkgtest, so no possible problem could be
detected in this way.

[ Checklist ]
  [X] all changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in testing

unblock mina2/2.2.1-4

Thanks for your attention in any case,

Cheers,

- -- 
Pierre Gruet

-----BEGIN PGP SIGNATURE-----

iQJDBAEBCgAtFiEEM8soQxPpC9J9y0UjYAMWptwndHYFAmh2z9gPHHBndEBkZWJp
YW4ub3JnAAoJEGADFqbcJ3R2EHIQAIT5ucxHl2KEO2KHORGKPAxZU7p2zRwgMBCD
qm31VaUfnVLagNZmDGXMh7H98SpjHq2ezy9paJp+xzy8GlhYWDxGo1ehjAeIDyvF
mMiQH5CKeH501CYQ3voNFsdDF3nluohB4lII/cLvXG/3WJtTmvm6+Gkoz0hMBWHl
/ZSr69islreOx9s+TDCNmkTeoZCpcqMvWUFpzikezaz3d5Wo7QXUEnsQYunQuiz2
v3gholyKeBiD914LofKz5fyVYvoaBqdcMSzWsMfHyu5vLyZUZMPwO4GZP0lwiuxu
0Uk7RKdBFo1fgLlQVtvQdI1YeZUevvdibqR3mocfefxcXX+kwGzvudgmt56GcuUr
xsrXpUkFgYVZNSR3es499fDNUjHV3i83GWHWU+LI57pJMdXYDENUC59eiBN1uZ9L
l7I+C8zKv4WaOJgb55MM5wDCczfJNUaP6wWsWDtLq3/EcFDrHXC7YhXLlGn9ETYh
TIg1cnzVcpXBXSbqmIMQ4TFPgQfQC4mNSyWG0N3gDPe3HhwsWDk5eM+5TEpTcI98
zZIHe0WdLTuEkZtu0dEFSFBTLnlqcul4C4J3UTzzvAI3hIAd6KGrbLUX1rfNeZCz
1aMV+5H9r86Y3W07VyCpKWw3KxF5HAxvi/H0DRuSOUdMU2jb/tGG8Nqse3MPVyk9
wRqP1d73
=+YC9
-----END PGP SIGNATURE-----
-------------- next part --------------
diff -Nru mina2-2.2.1/debian/changelog mina2-2.2.1/debian/changelog
--- mina2-2.2.1/debian/changelog	2023-02-13 14:48:31.000000000 +0100
+++ mina2-2.2.1/debian/changelog	2025-07-15 23:47:20.000000000 +0200
@@ -1,3 +1,16 @@
+mina2 (2.2.1-4) unstable; urgency=medium
+
+  * Team upload
+  * Fixing CVE-2024-52046: The ObjectSerializationDecoder in Apache MINA uses
+    Java?s native deserialization protocol to process incoming serialized
+    data but lacks the necessary security checks and defenses. This
+    vulnerability allows attackers to exploit the deserialization process by
+    sending specially crafted malicious serialized data, potentially leading to
+    remote code execution (RCE) attacks.
+    Closes: #1091530
+
+ -- Pierre Gruet <pgt at debian.org>  Tue, 15 Jul 2025 23:47:20 +0200
+
 mina2 (2.2.1-3) unstable; urgency=medium
 
   * No longer build mina-transport-apr and drop the libtomcat9-java dependency
diff -Nru mina2-2.2.1/debian/patches/cve-2024-52046.patch mina2-2.2.1/debian/patches/cve-2024-52046.patch
--- mina2-2.2.1/debian/patches/cve-2024-52046.patch	1970-01-01 01:00:00.000000000 +0100
+++ mina2-2.2.1/debian/patches/cve-2024-52046.patch	2025-07-15 23:46:37.000000000 +0200
@@ -0,0 +1,1823 @@
+Description: fixing CVE-2024-52046: Apache MINA: MINA applications using
+ unbounded deserialization may allow RCE
+Author: Pierre Gruet <pgt at debian.org>
+Origin: upstream, https://lists.apache.org/thread/4wxktgjpggdbto15d515wdctohb0qmv8
+Bug-Debian: https://bugs.debian.org/1091530
+Applied-Upstream: https://github.com/apache/mina/commit/cdb59eb6131696a440870ab89ad0e20804eb5ca7#diff-cb3019e35ae0f7cccf4b546a473fbb784e94624dc736a754e3ad01633ceaf32dR401-R402
+Last-Update: 2025-07-14
+
+--- a/src/mina-core/pom.xml
++++ b/src/mina-core/pom.xml
+@@ -52,6 +52,7 @@
+             <!--<Export-Package>
+               org.apache.mina.core,
+               org.apache.mina.core.buffer,
++              org.apache.mina.core.buffer.matcher,
+               org.apache.mina.core.file,
+               org.apache.mina.core.filterchain,
+               org.apache.mina.core.future,
+--- a/src/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java
+@@ -43,8 +43,16 @@
+ import java.nio.charset.CharsetEncoder;
+ import java.nio.charset.CoderResult;
+ import java.nio.charset.StandardCharsets;
++import java.util.ArrayList;
+ import java.util.EnumSet;
++import java.util.List;
+ import java.util.Set;
++import java.util.regex.Pattern;
++
++import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.FullClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+ 
+ /**
+  * A base implementation of {@link IoBuffer}. This implementation assumes that
+@@ -80,6 +88,8 @@
+     /** A mask for an int */
+     private static final long INT_MASK = 0xFFFFFFFFL;
+ 
++    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
++
+     /**
+      * We don't have any access to Buffer.markValue(), so we need to track it down,
+      * which will cause small extra overhead.
+@@ -2164,18 +2174,20 @@
+             @Override
+             protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
+                 int type = read();
++
+                 if (type < 0) {
+                     throw new EOFException();
+                 }
++
+                 switch (type) {
+-                case 0: // NON-Serializable class or Primitive types
+-                    return super.readClassDescriptor();
+-                case 1: // Serializable class
+-                    String className = readUTF();
+-                    Class<?> clazz = Class.forName(className, true, classLoader);
+-                    return ObjectStreamClass.lookup(clazz);
+-                default:
+-                    throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
++                    case 0: // NON-Serializable class or Primitive types
++                        return super.readClassDescriptor();
++                    case 1: // Serializable class
++                        String className = readUTF();
++                        Class<?> clazz = Class.forName(className, true, classLoader);
++                        return ObjectStreamClass.lookup(clazz);
++                    default:
++                        throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
+                 }
+             }
+ 
+@@ -2191,7 +2203,20 @@
+                         return super.resolveClass(desc);
+                     }
+                 } else {
+-                    return clazz;
++                    boolean found = false;
++                    String className = desc.getName();
++
++                    for (ClassNameMatcher matcher : acceptMatchers) {
++                        if (matcher.matches(className)) {
++                            found = true;
++                            break;
++                        }
++                    }
++                    if (found) {
++                        return clazz;
++                    }
++
++                    throw new ClassNotFoundException();
+                 }
+             }
+         }) {
+@@ -2747,4 +2772,58 @@
+             throw new IllegalArgumentException("fieldSize cannot be negative: " + fieldSize);
+         }
+     }
++
++    /**
++     * Accept the specified classes for deserialization, unless they
++     * are otherwise rejected.
++     *
++     * @param classes Classes to accept
++     * @return this object
++     */
++    public IoBuffer accept(Class<?>... classes) {
++        for (Class<?> clazz:classes) {
++            acceptMatchers.add(new FullClassNameMatcher(clazz.getName()));
++        }
++        return this;
++    }
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(ClassNameMatcher m) {
++        acceptMatchers.add(m);
++
++        return this;
++    }
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(Pattern pattern) {
++        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
++
++        return this;
++    }
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(String... patterns) {
++        for (String pattern:patterns) {
++            acceptMatchers.add(new WildcardClassNameMatcher(pattern));
++        }
++
++        return this;
++    }
++
++    /**
++     * {@inheritDoc}
++     */
++    public void setMatchers(List<ClassNameMatcher> matchers) {
++        acceptMatchers.clear();
++
++        for (ClassNameMatcher matcher:matchers) {
++            acceptMatchers.add(matcher);
++        }
++    }
+ }
+--- a/src/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java
+@@ -35,8 +35,11 @@
+ import java.nio.charset.CharsetDecoder;
+ import java.nio.charset.CharsetEncoder;
+ import java.util.EnumSet;
++import java.util.List;
+ import java.util.Set;
++import java.util.regex.Pattern;
+ 
++import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
+ import org.apache.mina.core.session.IoSession;
+ 
+ /**
+@@ -2100,6 +2103,39 @@
+     public abstract <E extends Enum<E>> IoBuffer putEnumSetLong(Set<E> set);
+ 
+     /**
++     * Accept class names where the supplied ClassNameMatcher matches for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param m the matcher to use
++     * @return this object
++     */
++    public abstract IoBuffer accept(ClassNameMatcher m);
++    /**
++     * Accept class names that match the supplied pattern for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param pattern standard Java regexp
++     * @return this object
++     */
++    public abstract IoBuffer accept(Pattern pattern);
++    /**
++     * Accept the wildcard specified classes for deserialization,
++     * unless they are otherwise rejected.
++     *
++     * @param patterns Wildcard file name patterns as defined by
++     *                  {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
++     * @return this object
++     */
++    public abstract IoBuffer accept(String... patterns);
++
++    /**
++     * Set the list of class matchers for in incoming buffer
++     *
++     * @param matchers The list of matchers
++     */
++    public abstract void setMatchers(List<ClassNameMatcher> matchers);
++
++    /**
+      * Writes the specified {@link Set} to the buffer as a long sized bit vector.
+      * 
+      * @param <E>   the enum type of the Set
+--- a/src/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java
+@@ -33,7 +33,13 @@
+ import java.nio.charset.CharacterCodingException;
+ import java.nio.charset.CharsetDecoder;
+ import java.nio.charset.CharsetEncoder;
++import java.util.List;
+ import java.util.Set;
++import java.util.regex.Pattern;
++
++import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+ 
+ /**
+  * A {@link IoBuffer} that wraps a buffer and proxies any operations to it.
+@@ -1535,4 +1541,33 @@
+         buf.putUnsigned(index, value);
+         return this;
+     }
++
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(ClassNameMatcher m) {
++        return buf.accept(m);
++    }
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(Pattern pattern) {
++        return buf.accept(pattern);
++    }
++    /**
++     * {@inheritDoc}
++     */
++    @Override
++    public IoBuffer accept(String... patterns) {
++        return buf.accept(patterns);
++    }
++
++    /**
++     * {@inheritDoc}
++     */
++    public void setMatchers(List<ClassNameMatcher> matchers) {
++        buf.setMatchers(matchers);
++    }
+ }
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java
+@@ -0,0 +1,31 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *   http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++/**
++ * An object that matches a Class name to a condition.
++ */
++public interface ClassNameMatcher {
++    /**
++     * Returns {@code true} if the supplied class name matches this object's condition.
++     *
++     * @param className fully qualified class name
++     * @return {@code true} if the class name matches this object's condition
++     */
++    boolean matches(String className);
++}
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java
+@@ -0,0 +1,490 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements.  See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License.  You may obtain a copy of the License at
++ *
++ *      http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++import java.util.Arrays;
++import java.util.Locale;
++import java.util.Objects;
++/**
++ * Abstracts an OS' file system details, currently supporting the single use case of converting a file name String to a
++ * legal file name with {@link #toLegalFileName(String, char)}.
++ * <p>
++ * The starting point of any operation is {@link #getCurrent()} which gets you the enum for the file system that matches
++ * the OS hosting the running JVM.
++ * </p>
++ *
++ * @since 2.7
++ */
++public enum FileSystem {
++    /**
++     * Generic file system.
++     */
++    GENERIC(4096, false, false, Integer.MAX_VALUE, Integer.MAX_VALUE, new int[] { 0 }, new String[] {}, false, false, '/'),
++    /**
++     * Linux file system.
++     */
++    LINUX(8192, true, true, 255, 4096, new int[] {
++            // KEEP THIS ARRAY SORTED!
++            // @formatter:off
++            // ASCII NUL
++            0,
++             '/'
++            // @formatter:on
++    }, new String[] {}, false, false, '/'),
++    /**
++     * MacOS file system.
++     */
++    MAC_OSX(4096, true, true, 255, 1024, new int[] {
++            // KEEP THIS ARRAY SORTED!
++            // @formatter:off
++            // ASCII NUL
++            0,
++            '/',
++             ':'
++            // @formatter:on
++    }, new String[] {}, false, false, '/'),
++    /**
++     * Windows file system.
++     * <p>
++     * The reserved characters are defined in the
++     * <a href="https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file">Naming Conventions
++     * (microsoft.com)</a>.
++     * </p>
++     *
++     * @see <a href="https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file">Naming Conventions
++     *      (microsoft.com)</a>
++     * @see <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles">
++     *      CreateFileA function - Consoles (microsoft.com)</a>
++     */
++    WINDOWS(4096, false, true,
++            255, 32000, // KEEP THIS ARRAY SORTED!
++            new int[] {
++                    // KEEP THIS ARRAY SORTED!
++                    // @formatter:off
++                    // ASCII NUL
++                    0,
++                    // 1-31 may be allowed in file streams
++                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
++                    29, 30, 31,
++                    '"', '*', '/', ':', '<', '>', '?', '\\', '|'
++                    // @formatter:on
++            }, new String[] { "AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "CONIN$", "CONOUT$",
++                            "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", "PRN" }, true, true, '\\');
++    /**
++     * <p>
++     * Is {@code true} if this is Linux.
++     * </p>
++     * <p>
++     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
++     * </p>
++     */
++    private static final boolean IS_OS_LINUX = getOsMatchesName("Linux");
++    /**
++     * <p>
++     * Is {@code true} if this is Mac.
++     * </p>
++     * <p>
++     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
++     * </p>
++     */
++    private static final boolean IS_OS_MAC = getOsMatchesName("Mac");
++    /**
++     * The prefix String for all Windows OS.
++     */
++    private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
++    /**
++     * <p>
++     * Is {@code true} if this is Windows.
++     * </p>
++     * <p>
++     * The field will return {@code false} if {@code OS_NAME} is {@code null}.
++     * </p>
++     */
++    private static final boolean IS_OS_WINDOWS = getOsMatchesName(OS_NAME_WINDOWS_PREFIX);
++    /**
++     * The current FileSystem.
++     */
++    private static final FileSystem CURRENT = current();
++    /**
++     * Gets the current file system.
++     *
++     * @return the current file system
++     */
++    private static FileSystem current() {
++        if (IS_OS_LINUX) {
++            return LINUX;
++        }
++        if (IS_OS_MAC) {
++            return MAC_OSX;
++        }
++        if (IS_OS_WINDOWS) {
++            return WINDOWS;
++        }
++        return GENERIC;
++    }
++    /**
++     * Gets the current file system.
++     *
++     * @return the current file system
++     */
++    public static FileSystem getCurrent() {
++        return CURRENT;
++    }
++    /**
++     * Decides if the operating system matches.
++     *
++     * @param osNamePrefix
++     *            the prefix for the os name
++     * @return true if matches, or false if not or can't determine
++     */
++    private static boolean getOsMatchesName(final String osNamePrefix) {
++        return isOsNameMatch(getSystemProperty("os.name"), osNamePrefix);
++    }
++    /**
++     * <p>
++     * Gets a System property, defaulting to {@code null} if the property cannot be read.
++     * </p>
++     * <p>
++     * If a {@link SecurityException} is caught, the return value is {@code null} and a message is written to
++     * {@code System.err}.
++     * </p>
++     *
++     * @param property
++     *            the system property name
++     * @return the system property value or {@code null} if a security problem occurs
++     */
++    private static String getSystemProperty(final String property) {
++        try {
++            return System.getProperty(property);
++        } catch (final SecurityException ex) {
++            // we are not allowed to look at this property
++            System.err.println("Caught a SecurityException reading the system property '" + property
++                    + "'; the SystemUtils property value will default to null.");
++            return null;
++        }
++    }
++    /**
++     * Copied from Apache Commons Lang CharSequenceUtils.
++     *
++     * Returns the index within {@code cs} of the first occurrence of the
++     * specified character, starting the search at the specified index.
++     * <p>
++     * If a character with value {@code searchChar} occurs in the
++     * character sequence represented by the {@code cs}
++     * object at an index no smaller than {@code start}, then
++     * the index of the first such occurrence is returned. For values
++     * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
++     * this is the smallest value <i>k</i> such that:
++     * </p>
++     * <blockquote><pre>
++     * (this.charAt(<i>k</i>) == searchChar) && (<i>k</i> >= start)
++     * </pre></blockquote>
++     * is true. For other values of {@code searchChar}, it is the
++     * smallest value <i>k</i> such that:
++     * <blockquote><pre>
++     * (this.codePointAt(<i>k</i>) == searchChar) && (<i>k</i> >= start)
++     * </pre></blockquote>
++     * <p>
++     * is true. In either case, if no such character occurs inm {@code cs}
++     * at or after position {@code start}, then
++     * {@code -1} is returned.
++     * </p>
++     * <p>
++     * There is no restriction on the value of {@code start}. If it
++     * is negative, it has the same effect as if it were zero: the entire
++     * {@link CharSequence} may be searched. If it is greater than
++     * the length of {@code cs}, it has the same effect as if it were
++     * equal to the length of {@code cs}: {@code -1} is returned.
++     * </p>
++     * <p>All indices are specified in {@code char} values
++     * (Unicode code units).
++     * </p>
++     *
++     * @param cs  the {@link CharSequence} to be processed, not null
++     * @param searchChar  the char to be searched for
++     * @param start  the start index, negative starts at the string start
++     * @return the index where the search char was found, -1 if not found
++     * @since 3.6 updated to behave more like {@link String}
++     */
++    private static int indexOf(final CharSequence cs, final int searchChar, int start) {
++        if (cs instanceof String) {
++            return ((String) cs).indexOf(searchChar, start);
++        }
++        final int sz = cs.length();
++        if (start < 0) {
++            start = 0;
++        }
++        if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
++            for (int i = start; i < sz; i++) {
++                if (cs.charAt(i) == searchChar) {
++                    return i;
++                }
++            }
++            return -1;
++        }
++        //supplementary characters (LANG1300)
++        if (searchChar <= Character.MAX_CODE_POINT) {
++            final char[] chars = Character.toChars(searchChar);
++            for (int i = start; i < sz - 1; i++) {
++                final char high = cs.charAt(i);
++                final char low = cs.charAt(i + 1);
++                if (high == chars[0] && low == chars[1]) {
++                    return i;
++                }
++            }
++        }
++        return -1;
++    }
++    /**
++     * Decides if the operating system matches.
++     * <p>
++     * This method is package private instead of private to support unit test invocation.
++     * </p>
++     *
++     * @param osName
++     *            the actual OS name
++     * @param osNamePrefix
++     *            the prefix for the expected OS name
++     * @return true if matches, or false if not or can't determine
++     */
++    private static boolean isOsNameMatch(final String osName, final String osNamePrefix) {
++        if (osName == null) {
++            return false;
++        }
++        return osName.toUpperCase(Locale.ROOT).startsWith(osNamePrefix.toUpperCase(Locale.ROOT));
++    }
++    /**
++     * Null-safe replace.
++     *
++     * @param path the path to be changed, null ignored.
++     * @param oldChar the old character.
++     * @param newChar the new character.
++     * @return the new path.
++     */
++    private static String replace(final String path, final char oldChar, final char newChar) {
++        return path == null ? null : path.replace(oldChar, newChar);
++    }
++    private final int blockSize;
++    private final boolean casePreserving;
++    private final boolean caseSensitive;
++    private final int[] illegalFileNameChars;
++    private final int maxFileNameLength;
++    private final int maxPathLength;
++    private final String[] reservedFileNames;
++    private final boolean reservedFileNamesExtensions;
++    private final boolean supportsDriveLetter;
++    private final char nameSeparator;
++    private final char nameSeparatorOther;
++    /**
++     * Constructs a new instance.
++     *
++     * @param blockSize file allocation block size in bytes.
++     * @param caseSensitive Whether this file system is case-sensitive.
++     * @param casePreserving Whether this file system is case-preserving.
++     * @param maxFileLength The maximum length for file names. The file name does not include folders.
++     * @param maxPathLength The maximum length of the path to a file. This can include folders.
++     * @param illegalFileNameChars Illegal characters for this file system.
++     * @param reservedFileNames The reserved file names.
++     * @param reservedFileNamesExtensions TODO
++     * @param supportsDriveLetter Whether this file system support driver letters.
++     * @param nameSeparator The name separator, '\\' on Windows, '/' on Linux.
++     */
++    FileSystem(final int blockSize, final boolean caseSensitive, final boolean casePreserving,
++        final int maxFileLength, final int maxPathLength, final int[] illegalFileNameChars,
++        final String[] reservedFileNames, final boolean reservedFileNamesExtensions, final boolean supportsDriveLetter, final char nameSeparator) {
++        this.blockSize = blockSize;
++        this.maxFileNameLength = maxFileLength;
++        this.maxPathLength = maxPathLength;
++        this.illegalFileNameChars = Objects.requireNonNull(illegalFileNameChars, "illegalFileNameChars");
++        this.reservedFileNames = Objects.requireNonNull(reservedFileNames, "reservedFileNames");
++        this.reservedFileNamesExtensions = reservedFileNamesExtensions;
++        this.caseSensitive = caseSensitive;
++        this.casePreserving = casePreserving;
++        this.supportsDriveLetter = supportsDriveLetter;
++        this.nameSeparator = nameSeparator;
++        this.nameSeparatorOther = FilenameUtils.flipSeparator(nameSeparator);
++    }
++    /**
++     * Gets the file allocation block size in bytes.
++     * @return the file allocation block size in bytes.
++     *
++     * @since 2.12.0
++     */
++    public int getBlockSize() {
++        return blockSize;
++    }
++    /**
++     * Gets a cloned copy of the illegal characters for this file system.
++     *
++     * @return the illegal characters for this file system.
++     */
++    public char[] getIllegalFileNameChars() {
++        final char[] chars = new char[illegalFileNameChars.length];
++        for (int i = 0; i < illegalFileNameChars.length; i++) {
++            chars[i] = (char) illegalFileNameChars[i];
++        }
++        return chars;
++    }
++    /**
++     * Gets a cloned copy of the illegal code points for this file system.
++     *
++     * @return the illegal code points for this file system.
++     * @since 2.12.0
++     */
++    public int[] getIllegalFileNameCodePoints() {
++        return this.illegalFileNameChars.clone();
++    }
++    /**
++     * Gets the maximum length for file names. The file name does not include folders.
++     *
++     * @return the maximum length for file names.
++     */
++    public int getMaxFileNameLength() {
++        return maxFileNameLength;
++    }
++    /**
++     * Gets the maximum length of the path to a file. This can include folders.
++     *
++     * @return the maximum length of the path to a file.
++     */
++    public int getMaxPathLength() {
++        return maxPathLength;
++    }
++    /**
++     * Gets the name separator, '\\' on Windows, '/' on Linux.
++     *
++     * @return '\\' on Windows, '/' on Linux.
++     *
++     * @since 2.12.0
++     */
++    public char getNameSeparator() {
++        return nameSeparator;
++    }
++    /**
++     * Gets a cloned copy of the reserved file names.
++     *
++     * @return the reserved file names.
++     */
++    public String[] getReservedFileNames() {
++        return reservedFileNames.clone();
++    }
++    /**
++     * Tests whether this file system preserves case.
++     *
++     * @return Whether this file system preserves case.
++     */
++    public boolean isCasePreserving() {
++        return casePreserving;
++    }
++    /**
++     * Tests whether this file system is case-sensitive.
++     *
++     * @return Whether this file system is case-sensitive.
++     */
++    public boolean isCaseSensitive() {
++        return caseSensitive;
++    }
++    /**
++     * Tests if the given character is illegal in a file name, {@code false} otherwise.
++     *
++     * @param c
++     *            the character to test
++     * @return {@code true} if the given character is illegal in a file name, {@code false} otherwise.
++     */
++    private boolean isIllegalFileNameChar(final int c) {
++        return Arrays.binarySearch(illegalFileNameChars, c) >= 0;
++    }
++    /**
++     * Tests if a candidate file name (without a path) such as {@code "filename.ext"} or {@code "filename"} is a
++     * potentially legal file name. If the file name length exceeds {@link #getMaxFileNameLength()}, or if it contains
++     * an illegal character then the check fails.
++     *
++     * @param candidate
++     *            a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}
++     * @return {@code true} if the candidate name is legal
++     */
++    public boolean isLegalFileName(final CharSequence candidate) {
++        if (candidate == null || candidate.length() == 0 || candidate.length() > maxFileNameLength) {
++            return false;
++        }
++        if (isReservedFileName(candidate)) {
++            return false;
++        }
++        return candidate.chars().noneMatch(this::isIllegalFileNameChar);
++    }
++    /**
++     * Tests whether the given string is a reserved file name.
++     *
++     * @param candidate
++     *            the string to test
++     * @return {@code true} if the given string is a reserved file name.
++     */
++    public boolean isReservedFileName(final CharSequence candidate) {
++        final CharSequence test = reservedFileNamesExtensions ? trimExtension(candidate) : candidate;
++        return Arrays.binarySearch(reservedFileNames, test) >= 0;
++    }
++    /**
++     * Converts all separators to the Windows separator of backslash.
++     *
++     * @param path the path to be changed, null ignored
++     * @return the updated path
++     * @since 2.12.0
++     */
++    public String normalizeSeparators(final String path) {
++        return replace(path, nameSeparatorOther, nameSeparator);
++    }
++    /**
++     * Tests whether this file system support driver letters.
++     * <p>
++     * Windows supports driver letters as do other operating systems. Whether these other OS's still support Java like
++     * OS/2, is a different matter.
++     * </p>
++     *
++     * @return whether this file system support driver letters.
++     * @since 2.9.0
++     * @see <a href="https://en.wikipedia.org/wiki/Drive_letter_assignment">Operating systems that use drive letter
++     *      assignment</a>
++     */
++    public boolean supportsDriveLetter() {
++        return supportsDriveLetter;
++    }
++    /**
++     * Converts a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} to a legal file
++     * name. Illegal characters in the candidate name are replaced by the {@code replacement} character. If the file
++     * name length exceeds {@link #getMaxFileNameLength()}, then the name is truncated to
++     * {@link #getMaxFileNameLength()}.
++     *
++     * @param candidate
++     *            a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"}
++     * @param replacement
++     *            Illegal characters in the candidate name are replaced by this character
++     * @return a String without illegal characters
++     */
++    public String toLegalFileName(final String candidate, final char replacement) {
++        if (isIllegalFileNameChar(replacement)) {
++            // %s does not work properly with NUL
++            throw new IllegalArgumentException(String.format("The replacement character '%s' cannot be one of the %s illegal characters: %s",
++                replacement == '\0' ? "\\0" : replacement, name(), Arrays.toString(illegalFileNameChars)));
++        }
++        final String truncated = candidate.length() > maxFileNameLength ? candidate.substring(0, maxFileNameLength) : candidate;
++        final int[] array = truncated.chars().map(i -> isIllegalFileNameChar(i) ? replacement : i).toArray();
++        return new String(array, 0, array.length);
++    }
++    CharSequence trimExtension(final CharSequence cs) {
++        final int index = indexOf(cs, '.', 0);
++        return index < 0 ? cs : cs.subSequence(0, index);
++    }
++}
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java
+@@ -0,0 +1,153 @@
++package org.apache.mina.core.buffer.matcher;
++import java.util.ArrayDeque;
++import java.util.ArrayList;
++import java.util.Deque;
++public class FilenameUtils
++{
++    private static final int NOT_FOUND = -1;
++    private static final String[] EMPTY_STRING_ARRAY = {};
++    /**
++     * The Unix separator character.
++     */
++    private static final char UNIX_NAME_SEPARATOR = '/';
++    /**
++     * The Windows separator character.
++     */
++    private static final char WINDOWS_NAME_SEPARATOR = '\\';
++    /**
++     * Checks a fileName to see if it matches the specified wildcard matcher
++     * allowing control over case-sensitivity.
++     * <p>
++     * The wildcard matcher uses the characters '?' and '*' to represent a
++     * single or multiple (zero or more) wildcard characters.
++     * N.B. the sequence "*?" does not work properly at present in match strings.
++     *
++     * @param fileName  the fileName to match on
++     * @param wildcardMatcher  the wildcard string to match against
++     * @param ioCase  what case sensitivity rule to use, null means case-sensitive
++     * @return true if the fileName matches the wildcard string
++     * @since 1.3
++     */
++    public static boolean wildcardMatch(final String fileName, final String wildcardMatcher, IOCase ioCase) {
++        if (fileName == null && wildcardMatcher == null) {
++            return true;
++        }
++        if (fileName == null || wildcardMatcher == null) {
++            return false;
++        }
++        ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
++        final String[] wcs = splitOnTokens(wildcardMatcher);
++        boolean anyChars = false;
++        int textIdx = 0;
++        int wcsIdx = 0;
++        final Deque<int[]> backtrack = new ArrayDeque<>(wcs.length);
++        // loop around a backtrack stack, to handle complex * matching
++        do {
++            if (!backtrack.isEmpty()) {
++                final int[] array = backtrack.pop();
++                wcsIdx = array[0];
++                textIdx = array[1];
++                anyChars = true;
++            }
++            // loop whilst tokens and text left to process
++            while (wcsIdx < wcs.length) {
++                if (wcs[wcsIdx].equals("?")) {
++                    // ? so move to next text char
++                    textIdx++;
++                    if (textIdx > fileName.length()) {
++                        break;
++                    }
++                    anyChars = false;
++                } else if (wcs[wcsIdx].equals("*")) {
++                    // set any chars status
++                    anyChars = true;
++                    if (wcsIdx == wcs.length - 1) {
++                        textIdx = fileName.length();
++                    }
++                } else {
++                    // matching text token
++                    if (anyChars) {
++                        // any chars then try to locate text token
++                        textIdx = ioCase.checkIndexOf(fileName, textIdx, wcs[wcsIdx]);
++                        if (textIdx == NOT_FOUND) {
++                            // token not found
++                            break;
++                        }
++                        final int repeat = ioCase.checkIndexOf(fileName, textIdx + 1, wcs[wcsIdx]);
++                        if (repeat >= 0) {
++                            backtrack.push(new int[] {wcsIdx, repeat});
++                        }
++                    } else if (!ioCase.checkRegionMatches(fileName, textIdx, wcs[wcsIdx])) {
++                        // matching from current position
++                        // couldn't match token
++                        break;
++                    }
++                    // matched text token, move text index to end of matched token
++                    textIdx += wcs[wcsIdx].length();
++                    anyChars = false;
++                }
++                wcsIdx++;
++            }
++            // full match
++            if (wcsIdx == wcs.length && textIdx == fileName.length()) {
++                return true;
++            }
++        } while (!backtrack.isEmpty());
++        return false;
++    }
++    /**
++     * Splits a string into a number of tokens.
++     * The text is split by '?' and '*'.
++     * Where multiple '*' occur consecutively they are collapsed into a single '*'.
++     *
++     * @param text  the text to split
++     * @return the array of tokens, never null
++     */
++    static String[] splitOnTokens(final String text) {
++        // used by wildcardMatch
++        // package level so a unit test may run on this
++        if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) {
++            return new String[] { text };
++        }
++        final char[] array = text.toCharArray();
++        final ArrayList<String> list = new ArrayList<>();
++        final StringBuilder buffer = new StringBuilder();
++        char prevChar = 0;
++        for (final char ch : array) {
++            if (ch == '?' || ch == '*') {
++                if (buffer.length() != 0) {
++                    list.add(buffer.toString());
++                    buffer.setLength(0);
++                }
++                if (ch == '?') {
++                    list.add("?");
++                } else if (prevChar != '*') {// ch == '*' here; check if previous char was '*'
++                    list.add("*");
++                }
++            } else {
++                buffer.append(ch);
++            }
++            prevChar = ch;
++        }
++        if (buffer.length() != 0) {
++            list.add(buffer.toString());
++        }
++        return list.toArray(EMPTY_STRING_ARRAY);
++    }
++    /**
++     * Flips the Windows name separator to Linux and vice-versa.
++     *
++     * @param ch The Windows or Linux name separator.
++     * @return The Windows or Linux name separator.
++     */
++    static char flipSeparator(final char ch) {
++        if (ch == UNIX_NAME_SEPARATOR) {
++            return WINDOWS_NAME_SEPARATOR;
++        }
++        if (ch == WINDOWS_NAME_SEPARATOR) {
++            return UNIX_NAME_SEPARATOR;
++        }
++        throw new IllegalArgumentException(String.valueOf(ch));
++    }
++}
++
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java
+@@ -0,0 +1,44 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *   http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++import java.util.Arrays;
++import java.util.Collections;
++import java.util.HashSet;
++import java.util.Set;
++/**
++ * A {@link ClassNameMatcher} that matches on full class names.
++ * <p>
++ * This object is immutable and thread-safe.
++ * </p>
++ */
++public final class FullClassNameMatcher implements ClassNameMatcher {
++    private final Set<String> classesSet;
++    /**
++     * Constructs an object based on the specified class names.
++     *
++     * @param classes a list of class names
++     */
++    public FullClassNameMatcher(String... classes) {
++        classesSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(classes)));
++    }
++    @Override
++    public boolean matches(String className) {
++        return classesSet.contains(className);
++    }
++}
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java
+@@ -0,0 +1,253 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements.  See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License.  You may obtain a copy of the License at
++ *
++ *      http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++import java.util.Objects;
++import java.util.stream.Stream;
++/**
++ * Enumeration of IO case sensitivity.
++ * <p>
++ * Different filing systems have different rules for case-sensitivity.
++ * Windows is case-insensitive, Unix is case-sensitive.
++ * </p>
++ * <p>
++ * This class captures that difference, providing an enumeration to
++ * control how file name comparisons should be performed. It also provides
++ * methods that use the enumeration to perform comparisons.
++ * </p>
++ * <p>
++ * Wherever possible, you should use the {@code check} methods in this
++ * class to compare file names.
++ * </p>
++ *
++ * @since 1.3
++ */
++public enum IOCase {
++    /**
++     * The constant for case-sensitive regardless of operating system.
++     */
++    SENSITIVE("Sensitive", true),
++    /**
++     * The constant for case-insensitive regardless of operating system.
++     */
++    INSENSITIVE("Insensitive", false),
++    /**
++     * The constant for case sensitivity determined by the current operating system.
++     * Windows is case-insensitive when comparing file names, Unix is case-sensitive.
++     * <p>
++     * <strong>Note:</strong> This only caters for Windows and Unix. Other operating
++     * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the
++     * Unix file separator and case-insensitive if they use the Windows file separator
++     * (see {@link java.io.File#separatorChar}).
++     * </p>
++     * <p>
++     * If you serialize this constant on Windows, and deserialize on Unix, or vice
++     * versa, then the value of the case-sensitivity flag will change.
++     * </p>
++     */
++    SYSTEM("System", FileSystem.getCurrent().isCaseSensitive());
++    /** Serialization version. */
++    private static final long serialVersionUID = -6343169151696340687L;
++    /**
++     * Factory method to create an IOCase from a name.
++     *
++     * @param name  the name to find
++     * @return the IOCase object
++     * @throws IllegalArgumentException if the name is invalid
++     */
++    public static IOCase forName(final String name) {
++        return Stream.of(IOCase.values()).filter(ioCase -> ioCase.getName().equals(name)).findFirst()
++                .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name));
++    }
++    /**
++     * Tests for cases sensitivity in a null-safe manner.
++     *
++     * @param ioCase an IOCase.
++     * @return true if the input is non-null and {@link #isCaseSensitive()}.
++     * @since 2.10.0
++     */
++    public static boolean isCaseSensitive(final IOCase ioCase) {
++        return ioCase != null && ioCase.isCaseSensitive();
++    }
++    /**
++     * Returns the given value if not-null, the defaultValue if null.
++     *
++     * @param value the value to test.
++     * @param defaultValue the default value.
++     * @return the given value if not-null, the defaultValue if null.
++     * @since 2.12.0
++     */
++    public static IOCase value(final IOCase value, final IOCase defaultValue) {
++        return value != null ? value : defaultValue;
++    }
++    /** The enumeration name. */
++    private final String name;
++    /** The sensitivity flag. */
++    private final transient boolean sensitive;
++    /**
++     * Constructs a new instance.
++     *
++     * @param name  the name
++     * @param sensitive  the sensitivity
++     */
++    IOCase(final String name, final boolean sensitive) {
++        this.name = name;
++        this.sensitive = sensitive;
++    }
++    /**
++     * Compares two strings using the case-sensitivity rule.
++     * <p>
++     * This method mimics {@link String#compareTo} but takes case-sensitivity
++     * into account.
++     * </p>
++     *
++     * @param str1  the first string to compare, not null
++     * @param str2  the second string to compare, not null
++     * @return true if equal using the case rules
++     * @throws NullPointerException if either string is null
++     */
++    public int checkCompareTo(final String str1, final String str2) {
++        Objects.requireNonNull(str1, "str1");
++        Objects.requireNonNull(str2, "str2");
++        return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
++    }
++    /**
++     * Checks if one string ends with another using the case-sensitivity rule.
++     * <p>
++     * This method mimics {@link String#endsWith} but takes case-sensitivity
++     * into account.
++     * </p>
++     *
++     * @param str  the string to check
++     * @param end  the end to compare against
++     * @return true if equal using the case rules, false if either input is null
++     */
++    public boolean checkEndsWith(final String str, final String end) {
++        if (str == null || end == null) {
++            return false;
++        }
++        final int endLen = end.length();
++        return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
++    }
++    /**
++     * Compares two strings using the case-sensitivity rule.
++     * <p>
++     * This method mimics {@link String#equals} but takes case-sensitivity
++     * into account.
++     * </p>
++     *
++     * @param str1  the first string to compare, not null
++     * @param str2  the second string to compare, not null
++     * @return true if equal using the case rules
++     * @throws NullPointerException if either string is null
++     */
++    public boolean checkEquals(final String str1, final String str2) {
++        Objects.requireNonNull(str1, "str1");
++        Objects.requireNonNull(str2, "str2");
++        return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
++    }
++    /**
++     * Checks if one string contains another starting at a specific index using the
++     * case-sensitivity rule.
++     * <p>
++     * This method mimics parts of {@link String#indexOf(String, int)}
++     * but takes case-sensitivity into account.
++     * </p>
++     *
++     * @param str  the string to check, not null
++     * @param strStartIndex  the index to start at in str
++     * @param search  the start to search for, not null
++     * @return the first index of the search String,
++     *  -1 if no match or {@code null} string input
++     * @throws NullPointerException if either string is null
++     * @since 2.0
++     */
++    public int checkIndexOf(final String str, final int strStartIndex, final String search) {
++        final int endIndex = str.length() - search.length();
++        if (endIndex >= strStartIndex) {
++            for (int i = strStartIndex; i <= endIndex; i++) {
++                if (checkRegionMatches(str, i, search)) {
++                    return i;
++                }
++            }
++        }
++        return -1;
++    }
++    /**
++     * Checks if one string contains another at a specific index using the case-sensitivity rule.
++     * <p>
++     * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)}
++     * but takes case-sensitivity into account.
++     * </p>
++     *
++     * @param str  the string to check, not null
++     * @param strStartIndex  the index to start at in str
++     * @param search  the start to search for, not null
++     * @return true if equal using the case rules
++     * @throws NullPointerException if either string is null
++     */
++    public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) {
++        return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
++    }
++    /**
++     * Checks if one string starts with another using the case-sensitivity rule.
++     * <p>
++     * This method mimics {@link String#startsWith(String)} but takes case-sensitivity
++     * into account.
++     * </p>
++     *
++     * @param str  the string to check
++     * @param start  the start to compare against
++     * @return true if equal using the case rules, false if either input is null
++     */
++    public boolean checkStartsWith(final String str, final String start) {
++        return str != null && start != null && str.regionMatches(!sensitive, 0, start, 0, start.length());
++    }
++    /**
++     * Gets the name of the constant.
++     *
++     * @return the name of the constant
++     */
++    public String getName() {
++        return name;
++    }
++    /**
++     * Does the object represent case-sensitive comparison.
++     *
++     * @return true if case-sensitive
++     */
++    public boolean isCaseSensitive() {
++        return sensitive;
++    }
++    /**
++     * Replaces the enumeration from the stream with a real one.
++     * This ensures that the correct flag is set for SYSTEM.
++     *
++     * @return the resolved object
++     */
++    private Object readResolve() {
++        return forName(name);
++    }
++    /**
++     * Gets a string describing the sensitivity.
++     *
++     * @return a string describing the sensitivity
++     */
++    @Override
++    public String toString() {
++        return name;
++    }
++}
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java
+@@ -0,0 +1,52 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *   http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++import java.util.Objects;
++import java.util.regex.Pattern;
++/**
++ * A {@link ClassNameMatcher} that uses regular expressions.
++ * <p>
++ * This object is immutable and thread-safe.
++ * </p>
++ */
++public final class RegexpClassNameMatcher implements ClassNameMatcher {
++    private final Pattern pattern; // Class is thread-safe
++    /**
++     * Constructs an object based on the specified pattern.
++     *
++     * @param pattern a pattern for evaluating acceptable class names
++     * @throws NullPointerException if {@code pattern} is null
++     */
++    public RegexpClassNameMatcher(Pattern pattern) {
++        this.pattern = Objects.requireNonNull(pattern, "pattern");
++    }
++    /**
++     * Constructs an object based on the specified regular expression.
++     *
++     * @param regex a regular expression for evaluating acceptable class names
++     */
++    public RegexpClassNameMatcher(String regex) {
++        this(Pattern.compile(regex));
++    }
++    @Override
++    public boolean matches(String className) {
++        return pattern.matcher(className).matches();
++    }
++}
++
+--- /dev/null
++++ b/src/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java
+@@ -0,0 +1,41 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *   http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.mina.core.buffer.matcher;
++/**
++ * A {@link ClassNameMatcher} that uses simplified regular expressions
++ *  provided by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
++ * <p>
++ * This object is immutable and thread-safe.
++ * </p>
++ */
++public final class WildcardClassNameMatcher implements ClassNameMatcher {
++    private final String pattern;
++    /**
++     * Constructs an object based on the specified simplified regular expression.
++     *
++     * @param pattern a {@link FilenameUtils#wildcardMatch} pattern.
++     */
++    public WildcardClassNameMatcher(String pattern) {
++        this.pattern = pattern;
++    }
++    @Override
++    public boolean matches(String className) {
++        return FilenameUtils.wildcardMatch(className, pattern, IOCase.SENSITIVE);
++    }
++}
+--- a/src/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java
++++ b/src/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java
+@@ -309,6 +309,7 @@
+     /**
+      * {@inheritDoc}
+      */
++    @Deprecated
+     public final CloseFuture close(boolean rightNow) {
+         if (rightNow) {
+             return closeNow();
+@@ -320,6 +321,7 @@
+     /**
+      * {@inheritDoc}
+      */
++    @Deprecated
+     public final CloseFuture close() {
+         return closeNow();
+     }
+--- a/src/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java
++++ b/src/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java
+@@ -65,6 +65,7 @@
+         /**
+          * {@inheritDoc}
+          */
++        @Deprecated
+         @Override
+         public void join() {
+             // Do nothing
+@@ -73,6 +74,7 @@
+         /**
+          * {@inheritDoc}
+          */
++        @Deprecated
+         @Override
+         public boolean join(long timeoutInMillis) {
+             return true;
+@@ -315,4 +317,4 @@
+     public boolean isEncoded() {
+         return false;
+     }
+-}
+\ No newline at end of file
++}
+--- a/src/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java
++++ b/src/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java
+@@ -19,7 +19,12 @@
+  */
+ package org.apache.mina.filter.codec.serialization;
+ 
++import java.util.regex.Pattern;
++
+ import org.apache.mina.core.buffer.BufferDataException;
++import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+ import org.apache.mina.core.session.IoSession;
+ import org.apache.mina.filter.codec.ProtocolCodecFactory;
+ import org.apache.mina.filter.codec.ProtocolDecoder;
+@@ -122,4 +127,34 @@
+     public void setDecoderMaxObjectSize(int maxObjectSize) {
+         decoder.setMaxObjectSize(maxObjectSize);
+     }
++    /**
++     * Accept class names where the supplied ClassNameMatcher matches for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param classNameMatcher the matcher to use
++     */
++    public void accept(ClassNameMatcher classNameMatcher) {
++        decoder.accept(classNameMatcher);
++    }
++    /**
++     * Accept class names that match the supplied pattern for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param pattern standard Java regexp
++     */
++    public void accept(Pattern pattern) {
++        decoder.accept(new RegexpClassNameMatcher(pattern));
++    }
++    /**
++     * Accept the wildcard specified classes for deserialization,
++     * unless they are otherwise rejected.
++     *
++     * @param patterns Wildcard file name patterns as defined by
++     *                  {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
++     */
++    public void accept(String... patterns) {
++        for (String pattern:patterns) {
++            decoder.accept(new WildcardClassNameMatcher(pattern));
++        }
++    }
+ }
+--- a/src/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java
++++ b/src/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java
+@@ -20,9 +20,15 @@
+ package org.apache.mina.filter.codec.serialization;
+ 
+ import java.io.Serializable;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.regex.Pattern;
+ 
+ import org.apache.mina.core.buffer.BufferDataException;
+ import org.apache.mina.core.buffer.IoBuffer;
++import org.apache.mina.core.buffer.matcher.ClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+ import org.apache.mina.core.session.IoSession;
+ import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
+ import org.apache.mina.filter.codec.ProtocolDecoder;
+@@ -39,6 +45,9 @@
+ 
+     private int maxObjectSize = 1048576; // 1MB
+ 
++    /** The classes we accept when deserializing a binary blob */
++    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
++
+     /**
+      * Creates a new instance with the {@link ClassLoader} of
+      * the current thread.
+@@ -94,7 +103,39 @@
+             return false;
+         }
+ 
++        in.setMatchers(acceptMatchers);
++
+         out.write(in.getObject(classLoader));
+         return true;
+     }
++    /**
++     * Accept class names where the supplied ClassNameMatcher matches for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param classNameMatcher the matcher to use
++     */
++    public void accept(ClassNameMatcher classNameMatcher) {
++        acceptMatchers.add(classNameMatcher);
++    }
++    /**
++     * Accept class names that match the supplied pattern for
++     * deserialization, unless they are otherwise rejected.
++     *
++     * @param pattern standard Java regexp
++     */
++    public void accept(Pattern pattern) {
++        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
++    }
++    /**
++     * Accept the wildcard specified classes for deserialization,
++     * unless they are otherwise rejected.
++     *
++     * @param patterns Wildcard file name patterns as defined by
++     *                  {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
++     */
++    public void accept(String... patterns) {
++        for (String pattern:patterns) {
++            acceptMatchers.add(new WildcardClassNameMatcher(pattern));
++        }
++    }
+ }
+--- a/src/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java
++++ b/src/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java
+@@ -40,6 +40,9 @@
+ import java.util.EnumSet;
+ import java.util.List;
+ 
++
++import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher;
++import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher;
+ import org.apache.mina.util.Bar;
+ import org.junit.Test;
+ 
+@@ -371,6 +374,7 @@
+         List<Object> o = new ArrayList<Object>();
+         o.add(new Date());
+         o.add(long.class);
++        buf.accept(ArrayList.class.getName(), Date.class.getName(), long.class.getName());
+ 
+         // Test writing an object.
+         buf.putObject(o);
+@@ -386,12 +390,46 @@
+ 
+     @Test
+     public void testNonserializableClass() throws Exception {
+-        Class<?> c = NonserializableClass.class;
++        Class<?> c = String.class;
++
++        IoBuffer buffer = IoBuffer.allocate(16);
++        buffer.setAutoExpand(true);
++        buffer.putObject(c);
++
++        // Accept the String class
++        buffer.accept(String.class.getName());
++        buffer.flip();
++        Object o = buffer.getObject();
++        assertEquals(c, o);
++        assertSame(c, o);
++    }
++    @Test
++    public void testNonserializableClassAcceptWildcard() throws Exception {
++        Class<?> c = String.class;
++        IoBuffer buffer = IoBuffer.allocate(16);
++        buffer.setAutoExpand(true);
++        buffer.putObject(c);
+ 
++        // Accept all classes which name starts with 'java.lan'
++        // That includes 'java.lang.String'
++        buffer.accept(new WildcardClassNameMatcher("java.lan*"));
++        buffer.flip();
++        Object o = buffer.getObject();
++        assertEquals(c, o);
++        assertSame(c, o);
++    }
++
++    @Test
++    public void testNonserializableClassAcceptRegexp() throws Exception {
++        Class<?> c = String.class;
+         IoBuffer buffer = IoBuffer.allocate(16);
+         buffer.setAutoExpand(true);
+         buffer.putObject(c);
+ 
++        // Accept all class which contains '.lang.' in their name
++        // That includes java.lang.String
++        buffer.accept(new RegexpClassNameMatcher(".*\\.lang\\..*"));
++
+         buffer.flip();
+         Object o = buffer.getObject();
+ 
+@@ -399,6 +437,19 @@
+         assertSame(c, o);
+     }
+ 
++    @Test(expected=ClassNotFoundException.class)
++    public void testNonserializableClassReject() throws Exception {
++        Class<?> c = String.class;
++        IoBuffer buffer = IoBuffer.allocate(16);
++        buffer.setAutoExpand(true);
++        buffer.putObject(c);
++        // Don't accept the java.lang.String class
++        buffer.flip();
++
++        // Should throw an exception
++        buffer.getObject();
++    }
++
+     @Test
+     public void testNonserializableInterface() throws Exception {
+         Class<?> c = NonserializableInterface.class;
+@@ -406,6 +457,7 @@
+         IoBuffer buffer = IoBuffer.allocate(16);
+         buffer.setAutoExpand(true);
+         buffer.putObject(c);
++        buffer.accept(NonserializableInterface.class.getName());
+ 
+         buffer.flip();
+         Object o = buffer.getObject();
+@@ -946,6 +998,7 @@
+ 
+         // Test writing an object.
+         buf.putObject(expected);
++        buf.accept(Bar.class.getName());
+ 
+         // Test reading an object.
+         buf.clear();
+--- a/src/mina-example/pom.xml
++++ b/src/mina-example/pom.xml
+@@ -80,5 +80,17 @@
+       <artifactId>jcl-over-slf4j</artifactId>
+     </dependency>
+ 
++    <dependency>
++      <groupId>org.apache.commons</groupId>
++      <artifactId>commons-collections4</artifactId>
++      <version>4.0</version>
++    </dependency>
++
++    <dependency>
++      <groupId>com.nqzero</groupId>
++      <artifactId>permit-reflect</artifactId>
++      <version>0.3</version>
++    </dependency>
++
+   </dependencies>
+ </project>
+--- a/src/mina-legal/pom.xml
++++ b/src/mina-legal/pom.xml
+@@ -76,8 +76,8 @@
+         </dependency>
+ 
+         <dependency>
+-            <groupId>pmd</groupId>
+-            <artifactId>pmd</artifactId>
++      <groupId>net.sourceforge.pmd</groupId>
++      <artifactId>pmd-core</artifactId>
+         </dependency>
+     </dependencies>
+ </project>
+--- a/src/pom.xml
++++ b/src/pom.xml
+@@ -146,7 +146,7 @@
+     <version.jzlib>1.1.3</version.jzlib>
+     <version.log4j>1.2.17</version.log4j>
+     <version.ognl>3.2.15</version.ognl>
+-    <version.pmd>4.3</version.pmd>
++    <version.pmd>7.7.0</version.pmd>
+     <version.rmock>2.0.2</version.rmock>
+     <version.slf4j.api>1.7.36</version.slf4j.api>
+     <version.slf4j.log4j12>1.7.36</version.slf4j.log4j12>
+@@ -301,8 +301,8 @@
+       </dependency>
+ 
+       <dependency>
+-        <groupId>pmd</groupId>
+-        <artifactId>pmd</artifactId>
++        <groupId>net.sourceforge.pmd</groupId>
++        <artifactId>pmd-core</artifactId>
+         <version>${version.pmd}</version>
+       </dependency>
+ 
+@@ -404,10 +404,7 @@
+               <goals>
+                 <goal>javadoc</goal>
+               </goals>
+-              <configuration>
+-                <aggregate>true</aggregate>
+-                <!-- additionalparam>-Xdoclint:none</additionalparam -->
+-              </configuration>
++              <configuration/>
+             </execution>
+           </executions>
+         </plugin>
+@@ -422,7 +419,7 @@
+   <profile>
+     <id>java-8-compilation</id>
+     <activation>
+-      <jdk>[9,)</jdk>
++      <jdk>[11,)</jdk>
+     </activation>
+     <properties>
+       <maven.compiler.release>8</maven.compiler.release>
+@@ -469,10 +466,10 @@
+           <groupId>org.apache.maven.plugins</groupId>
+           <artifactId>maven-compiler-plugin</artifactId>
+           <version>${version.compiler.plugin}</version>
+-          <configuration>
+-          <optimize>true</optimize>
++          <configuration>$a
++            <debug>true</debug>
+             <showDeprecation>true</showDeprecation>
+-            <encoding>ISO-8859-1</encoding>
++            <encoding>UTF-8</encoding>
+           </configuration>
+         </plugin>
+ 
+@@ -748,12 +745,19 @@
+           <artifactId>taglist-maven-plugin</artifactId>
+           <version>${version.taglist.plugin}</version>
+           <configuration>
+-            <tags>
+-              <tag>TODO</tag>
+-              <tag>@todo</tag>
+-              <tag>@deprecated</tag>
+-              <tag>FIXME</tag>
+-            </tags>
++            <tagListOptions>
++              <tagClasses>>
++                <tagClass>
++                  <displayName>Documentation Work</displayName>
++                  <tags>
++                    <tag>TODO</tag>
++                    <tag>@todo</tag>
++                    <tag>@deprecated</tag>
++                    <tag>FIXME</tag>
++                  </tags>
++                </tagClass>
++              </tagClasses>
++            </tagListOptions>
+           </configuration>
+         </plugin>
+ 
+@@ -763,29 +767,21 @@
+           <version>${version.versions.plugin}</version>
+         </plugin>
+ 
+-        <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+         <plugin>
+-          <groupId>org.eclipse.m2e</groupId>
+-          <artifactId>lifecycle-mapping</artifactId>
+-          <version>1.0.0</version>
++          <groupId>org.cyclonedx</groupId>
++          <artifactId>cyclonedx-maven-plugin</artifactId>
++          <version>${version.cyclonedx.plugin}</version>
++          <executions>
++            <execution>
++              <id>make-bom</id>
++              <phase>package</phase>
++              <goals>
++                <goal>makeAggregateBom</goal>
++              </goals>
++            </execution>
++          </executions>
+           <configuration>
+-            <lifecycleMappingMetadata>
+-              <pluginExecutions>
+-                <pluginExecution>
+-                  <pluginExecutionFilter>
+-                    <groupId>org.apache.xbean</groupId>
+-                      <artifactId>maven-xbean-plugin</artifactId>
+-                      <versionRange>[4.12,)</versionRange>
+-                      <goals>
+-                        <goal>mapping</goal>
+-                      </goals>
+-                    </pluginExecutionFilter>
+-                  <action>
+-                    <ignore />
+-                  </action>
+-                </pluginExecution>
+-              </pluginExecutions>
+-            </lifecycleMappingMetadata>
++            <outputName>${project.artifactId}-${project.version}-bom</outputName>
+           </configuration>
+         </plugin>
+       </plugins>
+@@ -818,7 +814,7 @@
+           <encoding>UTF-8</encoding>
+           <debug>true</debug>
+           <optimize>true</optimize>
+-          <showDeprecations>true</showDeprecations>
++          <showDeprecation>true</showDeprecation>
+         </configuration>
+       </plugin>
+ 
+@@ -892,7 +888,6 @@
+         <version>${version.javadoc.plugin}</version>
+         <inherited>false</inherited>
+         <configuration>
+-          <aggregate>true</aggregate>
+           <breakiterator>true</breakiterator>
+           <charset>UTF-8</charset>
+           <docencoding>UTF-8</docencoding>
+@@ -903,7 +898,6 @@
+           <links>
+             <link>http://java.sun.com/j2se/1.5.0/docs/api/</link>
+             <link>http://www.slf4j.org/api/</link>
+-            <link>http://static.springframework.org/spring/docs/2.0.x/api/</link>
+             <link>http://dcl.mathcs.emory.edu/util/backport-util-concurrent/doc/api/</link>
+           </links>
+           <locale>en_US</locale>
+@@ -916,28 +910,12 @@
+         <version>${version.jxr.plugin}</version>
+         <inherited>false</inherited>
+         <configuration>
+-          <aggregate>true</aggregate>
+           <inputEncoding>UTF-8</inputEncoding>
+           <outputEncoding>UTF-8</outputEncoding>
+           <windowTitle>Apache MINA ${project.version} Cross Reference</windowTitle>
+           <docTitle>Apache MINA ${project.version} Cross Reference</docTitle>
+         </configuration>
+       </plugin>
+-
+-      <plugin>
+-        <groupId>org.codehaus.mojo</groupId>
+-        <artifactId>rat-maven-plugin</artifactId>
+-        <version>${version.rat.maven.plugin}</version>
+-        <configuration>
+-          <excludes>
+-            <exclude>**/target/**/*</exclude>
+-            <exclude>**/.*</exclude>
+-            <exclude>**/NOTICE.txt</exclude>
+-            <exclude>**/LICENSE*.txt</exclude>
+-          </excludes>
+-          <excludeSubProjects>false</excludeSubProjects>
+-        </configuration>
+-      </plugin>
+     </plugins>
+   </reporting>
+ </project>
diff -Nru mina2-2.2.1/debian/patches/series mina2-2.2.1/debian/patches/series
--- mina2-2.2.1/debian/patches/series	2022-12-15 09:29:47.000000000 +0100
+++ mina2-2.2.1/debian/patches/series	2025-07-14 14:20:02.000000000 +0200
@@ -2,3 +2,4 @@
 03-easymock-compatibility.patch
 05-spring-dependency.patch
 maven-bundle-plugin.patch
+cve-2024-52046.patch


More information about the pkg-java-maintainers mailing list