This is an automated email from the ASF dual-hosted git repository. elecharny pushed a commit to branch 2.0.X in repository https://gitbox.apache.org/repos/asf/mina.git
commit 834396355766e0c8f6bbf0493d4588b3fa9d347d Author: emmanuel lecharny <[email protected]> AuthorDate: Wed Nov 6 22:44:00 2024 +0100 Addded some controls on classes that can be deserialized; Bumped up some plugin dependencies, and some dependencies; Fixed some javadoc issues; Fixed some Maven issues --- mina-core/pom.xml | 1 + .../apache/mina/core/buffer/AbstractIoBuffer.java | 127 ++++- .../java/org/apache/mina/core/buffer/IoBuffer.java | 39 ++ .../apache/mina/core/buffer/IoBufferWrapper.java | 37 ++ .../mina/core/buffer/matcher/ClassNameMatcher.java | 32 ++ .../mina/core/buffer/matcher/FileSystem.java | 526 +++++++++++++++++++++ .../mina/core/buffer/matcher/FilenameUtils.java | 174 +++++++ .../core/buffer/matcher/FullClassNameMatcher.java | 48 ++ .../apache/mina/core/buffer/matcher/IOCase.java | 275 +++++++++++ .../buffer/matcher/RegexpClassNameMatcher.java | 56 +++ .../buffer/matcher/WildcardClassNameMatcher.java | 45 ++ .../apache/mina/core/file/FilenameFileRegion.java | 1 - .../core/polling/AbstractPollingIoProcessor.java | 6 - .../mina/core/session/AbstractIoSession.java | 2 + .../mina/core/write/DefaultWriteRequest.java | 4 +- .../ObjectSerializationCodecFactory.java | 38 ++ .../serialization/ObjectSerializationDecoder.java | 44 ++ .../proxy/handlers/http/ntlm/NTLMResponses.java | 2 - .../org/apache/mina/core/buffer/IoBufferTest.java | 63 ++- mina-example/pom.xml | 11 + mina-legal/pom.xml | 4 +- pom.xml | 113 ++--- 22 files changed, 1548 insertions(+), 100 deletions(-) diff --git a/mina-core/pom.xml b/mina-core/pom.xml index 2c22aa08e..a425f9d68 100644 --- a/mina-core/pom.xml +++ b/mina-core/pom.xml @@ -52,6 +52,7 @@ <Export-Package> org.apache.mina.core;version=${project.version};-noimport:=true, org.apache.mina.core.buffer;version=${project.version};-noimport:=true, + org.apache.mina.core.buffer.matcher;version=${project.version};-noimport:=true, org.apache.mina.core.file;version=${project.version};-noimport:=true, org.apache.mina.core.filterchain;version=${project.version};-noimport:=true, org.apache.mina.core.future;version=${project.version};-noimport:=true, diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java index 4f3660934..1a2463a6a 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java @@ -43,8 +43,16 @@ import java.nio.charset.CharsetDecoder; 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 @@ -80,6 +88,8 @@ public abstract class AbstractIoBuffer extends IoBuffer { /** 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. @@ -2158,40 +2168,60 @@ public abstract class AbstractIoBuffer extends IoBuffer { limit(position() + length); try (ObjectInputStream in = new ObjectInputStream(asInputStream()) { - @Override - protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { - int type = read(); - if (type < 0) { - throw new EOFException(); - } - switch (type) { + @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); - } } + } - @Override - protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { - Class<?> clazz = desc.forClass(); + @Override + protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + Class<?> clazz = desc.forClass(); - if (clazz == null) { - String name = desc.getName(); - try { - return Class.forName(name, false, classLoader); - } catch (ClassNotFoundException ex) { - return super.resolveClass(desc); + if (clazz == null) { + String name = desc.getName(); + + try { + return Class.forName(name, false, classLoader); + } catch (ClassNotFoundException ex) { + return super.resolveClass(desc); + } + } else { + boolean found = false; + String className = desc.getName(); + + for (ClassNameMatcher matcher : acceptMatchers) { + if (matcher.matches(className)) { + found = true; + break; } - } else { + } + + if (found) { return clazz; } + + throw new ClassNotFoundException(); } - }) { + } + }) { return in.readObject(); } catch (IOException e) { throw new BufferDataException(e); @@ -2744,4 +2774,61 @@ public abstract class AbstractIoBuffer extends IoBuffer { 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); + } + }} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java index cbda630f6..df8ee883c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java @@ -35,7 +35,11 @@ import java.nio.charset.CharacterCodingException; 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; @@ -2111,4 +2115,39 @@ public abstract class IoBuffer implements Comparable<IoBuffer> { * @return the modified IoBuffer */ public abstract <E extends Enum<E>> IoBuffer putEnumSetLong(int index, 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); } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java index 26f51a098..bbfdf62b0 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java @@ -33,7 +33,13 @@ import java.nio.ShortBuffer; 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. @@ -1542,4 +1548,35 @@ public class IoBufferWrapper extends IoBuffer { 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); + } } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java new file mode 100644 index 000000000..b224ca834 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java @@ -0,0 +1,32 @@ +/* + * 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); +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java new file mode 100644 index 000000000..38212c791 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java @@ -0,0 +1,526 @@ +/* + * 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); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java new file mode 100644 index 000000000..9ff67ca05 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java @@ -0,0 +1,174 @@ +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)); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java new file mode 100644 index 000000000..7b7dd7163 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java @@ -0,0 +1,48 @@ +/* + * 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); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java new file mode 100644 index 000000000..b2a1c89cd --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java @@ -0,0 +1,275 @@ +/* + * 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; + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java new file mode 100644 index 000000000..46e879067 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java @@ -0,0 +1,56 @@ +/* + * 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(); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java new file mode 100644 index 000000000..a7b80f30a --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java @@ -0,0 +1,45 @@ +/* + * 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); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java b/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java index 31c72cb99..ce8404b00 100644 --- a/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java +++ b/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java @@ -20,7 +20,6 @@ package org.apache.mina.core.file; import java.io.File; -import java.io.IOException; import java.nio.channels.FileChannel; /** diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java index 695be5fe7..a3998739c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java +++ b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java @@ -696,16 +696,10 @@ public abstract class AbstractPollingIoProcessor<S extends AbstractIoSession> im // Disconnect all sessions immediately if disposal has been // requested so that we exit this loop eventually. if (isDisposing()) { - boolean hasKeys = false; - for (Iterator<S> i = allSessions(); i.hasNext();) { IoSession session = i.next(); scheduleRemove((S) session); - - if (session.isActive()) { - hasKeys = true; - } } wakeup(); diff --git a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java index a45871a45..021210753 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java @@ -309,6 +309,7 @@ public abstract class AbstractIoSession implements IoSession { /** * {@inheritDoc} */ + @Deprecated public final CloseFuture close(boolean rightNow) { if (rightNow) { return closeNow(); @@ -320,6 +321,7 @@ public abstract class AbstractIoSession implements IoSession { /** * {@inheritDoc} */ + @Deprecated public final CloseFuture close() { return closeNow(); } diff --git a/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java b/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java index 1d1e5fba2..9ec4fe1aa 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java @@ -64,6 +64,7 @@ public class DefaultWriteRequest implements WriteRequest { /** * {@inheritDoc} */ + @Deprecated @Override public void join() { // Do nothing @@ -72,6 +73,7 @@ public class DefaultWriteRequest implements WriteRequest { /** * {@inheritDoc} */ + @Deprecated @Override public boolean join(long timeoutInMillis) { return true; @@ -277,4 +279,4 @@ public class DefaultWriteRequest implements WriteRequest { public boolean isEncoded() { return false; } -} \ No newline at end of file +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java index d48dddab5..7e0a61907 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java +++ b/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,37 @@ public class ObjectSerializationCodecFactory implements ProtocolCodecFactory { 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)); + } + } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java index 542fb422e..a45d929fb 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java +++ b/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 @@ public class ObjectSerializationDecoder extends CumulativeProtocolDecoder { 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. @@ -93,8 +102,43 @@ public class ObjectSerializationDecoder extends CumulativeProtocolDecoder { if (!in.prefixedDataAvailable(4, maxObjectSize)) { 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)); + } + } } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java index bb153ece7..ee223c986 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java @@ -23,10 +23,8 @@ import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.Key; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; /** diff --git a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java index cfc2d7104..e969428a4 100644 --- a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java +++ b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java @@ -41,6 +41,8 @@ import java.util.Date; 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; @@ -372,6 +374,7 @@ public class IoBufferTest { List<Object> o = new ArrayList<>(); 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); @@ -387,12 +390,53 @@ public class IoBufferTest { @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(); @@ -400,6 +444,21 @@ public class IoBufferTest { 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; @@ -407,6 +466,7 @@ public class IoBufferTest { IoBuffer buffer = IoBuffer.allocate(16); buffer.setAutoExpand(true); buffer.putObject(c); + buffer.accept(NonserializableInterface.class.getName()); buffer.flip(); Object o = buffer.getObject(); @@ -947,6 +1007,7 @@ public class IoBufferTest { // Test writing an object. buf.putObject(expected); + buf.accept(Bar.class.getName()); // Test reading an object. buf.clear(); diff --git a/mina-example/pom.xml b/mina-example/pom.xml index c965067e4..b8e03d86f 100644 --- a/mina-example/pom.xml +++ b/mina-example/pom.xml @@ -73,5 +73,16 @@ <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> diff --git a/mina-legal/pom.xml b/mina-legal/pom.xml index 16daf82d7..c5c9c79b0 100644 --- a/mina-legal/pom.xml +++ b/mina-legal/pom.xml @@ -70,8 +70,8 @@ </dependency> <dependency> - <groupId>pmd</groupId> - <artifactId>pmd</artifactId> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-core</artifactId> </dependency> </dependencies> </project> diff --git a/pom.xml b/pom.xml index 7a798f015..76f2c6156 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ </parent> <prerequisites> - <maven>3.5.0</maven> + <maven>3.8.5</maven> </prerequisites> <organization> @@ -100,13 +100,14 @@ <version.build.helper.plugin>3.6.0</version.build.helper.plugin> <version.bundle.plugin>5.1.9</version.bundle.plugin> <version.changes.plugin>2.12.1</version.changes.plugin> - <version.checkstyle.plugin>3.5.0</version.checkstyle.plugin> + <version.checkstyle.plugin>3.6.0</version.checkstyle.plugin> <version.clean.plugin>3.4.0</version.clean.plugin> <version.clirr.plugin>2.8</version.clirr.plugin> <version.cobertura.plugin>2.7</version.cobertura.plugin> <version.compiler.plugin>3.13.0</version.compiler.plugin> + <version.cyclonedx.plugin>2.9.0</version.cyclonedx.plugin> <version.dashboard.plugin>1.0.0-beta-1</version.dashboard.plugin> - <version.dependency.plugin>3.8.0</version.dependency.plugin> + <version.dependency.plugin>3.8.1</version.dependency.plugin> <version.deploy.plugin>3.1.3</version.deploy.plugin> <version.docck.plugin>1.2</version.docck.plugin> <version.eclipse.plugin>2.10</version.eclipse.plugin> @@ -116,15 +117,15 @@ <version.install.plugin>3.1.3</version.install.plugin> <version.jar.plugin>3.4.2</version.jar.plugin> <version.javancss.plugin>2.1</version.javancss.plugin> - <version.javadoc.plugin>3.10.1</version.javadoc.plugin> + <version.javadoc.plugin>3.11.1</version.javadoc.plugin> <version.jdepend.plugin>2.1</version.jdepend.plugin> - <version.jxr.plugin>3.5.0</version.jxr.plugin> + <version.jxr.plugin>3.6.0</version.jxr.plugin> <version.model.plugin>3.9.4</version.model.plugin> <version.plexus.utils>4.0.0</version.plexus.utils> <version.plugin.plugin>4.0.0-beta-1</version.plugin.plugin> - <version.pmd.plugin>3.25.0</version.pmd.plugin> + <version.pmd.plugin>3.26.0</version.pmd.plugin> <version.project.plugin>3.0-alpha-2</version.project.plugin> - <version.project.info.report.plugin>3.7.0</version.project.info.report.plugin> + <version.project.info.report.plugin>3.8.0</version.project.info.report.plugin> <version.rat.maven.plugin>1.0-alpha-3</version.rat.maven.plugin> <version.release.plugin>3.1.1</version.release.plugin> <version.remote.resources.plugin>3.2.0</version.remote.resources.plugin> @@ -133,8 +134,8 @@ <version.site.plugin>4.0.0-M16</version.site.plugin> <version.source.plugin>3.3.1</version.source.plugin> <version.shade.plugin>3.5.0</version.shade.plugin> - <version.surefire.plugin>3.5.1</version.surefire.plugin> - <version.surfire.report.plugin>3.5.1</version.surfire.report.plugin> + <version.surefire.plugin>3.5.2</version.surefire.plugin> + <version.surfire.report.plugin>3.5.2</version.surfire.report.plugin> <version.taglist.plugin>3.2.1</version.taglist.plugin> <version.tools.maven.plugin>1.4</version.tools.maven.plugin> <version.versions.plugin>2.17.1</version.versions.plugin> @@ -149,7 +150,7 @@ <version.jzlib>1.1.3</version.jzlib> <version.log4j>1.2.17</version.log4j> <version.ognl>3.3.4</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.reload4j>1.7.36</version.slf4j.reload4j> @@ -309,8 +310,8 @@ </dependency> <dependency> - <groupId>pmd</groupId> - <artifactId>pmd</artifactId> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-core</artifactId> <version>${version.pmd}</version> </dependency> @@ -414,10 +415,7 @@ <goals> <goal>javadoc</goal> </goals> - <configuration> - <aggregate>true</aggregate> - <!-- additionalparam>-Xdoclint:none</additionalparam --> - </configuration> + <configuration/> </execution> </executions> </plugin> @@ -433,7 +431,7 @@ <profile> <id>java-8-compilation</id> <activation> - <jdk>[9,)</jdk> + <jdk>[11,)</jdk> </activation> <properties> <maven.compiler.release>8</maven.compiler.release> @@ -480,8 +478,9 @@ <artifactId>maven-compiler-plugin</artifactId> <version>${version.compiler.plugin}</version> <configuration> + <debug>true</debug> <showDeprecation>true</showDeprecation> - <encoding>ISO-8859-1</encoding> + <encoding>UTF-8</encoding> </configuration> </plugin> @@ -757,12 +756,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> @@ -771,31 +777,23 @@ <artifactId>versions-maven-plugin</artifactId> <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> - </configuration> + <outputName>${project.artifactId}-${project.version}-bom</outputName> + </configuration> </plugin> </plugins> </pluginManagement> @@ -803,11 +801,10 @@ <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> - <version>${version.compiler.plugin}</version> <configuration> <encoding>UTF-8</encoding> <debug>true</debug> - <showDeprecations>true</showDeprecations> + <showDeprecation>true</showDeprecation> </configuration> </plugin> @@ -913,7 +910,6 @@ <version>${version.javadoc.plugin}</version> <inherited>false</inherited> <configuration> - <aggregate>true</aggregate> <breakiterator>true</breakiterator> <charset>UTF-8</charset> <docencoding>UTF-8</docencoding> @@ -924,7 +920,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> </links> <locale>en_US</locale> </configuration> @@ -936,28 +931,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>
