[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1109358: marked as done (unblock: mina2/2.2.1-4)



Your message dated Wed, 23 Jul 2025 18:33:12 +0000
with message-id <E1ueeHA-00CdgG-10@respighi.debian.org>
and subject line unblock mina2
has caused the Debian Bug report #1109358,
regarding unblock: mina2/2.2.1-4
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1109358: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1109358
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: mina2@packages.debian.org
Control: affects -1 + src:mina2
User: release.debian.org@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-----
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@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@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) &amp;&amp; (<i>k</i> &gt;= 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) &amp;&amp; (<i>k</i> &gt;= 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

--- End Message ---
--- Begin Message ---
Unblocked mina2.

--- End Message ---

Reply to: