Package: release.debian.org Severity: normal X-Debbugs-Cc: mi...@packages.debian.org Control: affects -1 + src:mina2 User: release.debian....@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 <p...@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 <p...@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