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