From d3cc2994bee0edd49130bb3409ce194b11d08577 Mon Sep 17 00:00:00 2001
From: Gary Gregory
+ * The reject list takes precedence over the accept list. + *
* * @param matcher a class name matcher to accept objects. * @return {@code this} instance. @@ -181,6 +190,9 @@ public Builder accept(final ClassNameMatcher matcher) { /** * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected. + *+ * The reject list takes precedence over the accept list. + *
* * @param pattern a Pattern for compiled regular expression. * @return {@code this} instance. @@ -193,6 +205,9 @@ public Builder accept(final Pattern pattern) { /** * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected. + *+ * The reject list takes precedence over the accept list. + *
* * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) * FilenameUtils.wildcardMatch} @@ -207,6 +222,9 @@ public Builder accept(final String... patterns) { /** * Builds a new {@link ValidatingObjectInputStream}. *+ * The reject list takes precedence over the accept list. + *
+ ** You must set an aspect that supports {@link #getInputStream()} on this builder, otherwise, this method throws an exception. *
*@@ -242,6 +260,9 @@ public ObjectStreamClassPredicate getPredicate() { /** * Rejects the specified classes for deserialization, even if they are otherwise accepted. + *
+ * The reject list takes precedence over the accept list. + *
* * @param classes Classes to reject. * @return {@code this} instance. @@ -254,6 +275,9 @@ public Builder reject(final Class>... classes) { /** * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted. + *+ * The reject list takes precedence over the accept list. + *
* * @param matcher the matcher to use. * @return {@code this} instance. @@ -266,6 +290,9 @@ public Builder reject(final ClassNameMatcher matcher) { /** * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted. + *+ * The reject list takes precedence over the accept list. + *
* * @param pattern standard Java regexp. * @return {@code this} instance. @@ -278,6 +305,9 @@ public Builder reject(final Pattern pattern) { /** * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted. + *+ * The reject list takes precedence over the accept list. + *
* * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) * FilenameUtils.wildcardMatch} @@ -291,6 +321,9 @@ public Builder reject(final String... patterns) { /** * Sets the predicate, null resets to an empty new ObjectStreamClassPredicate. + *+ * The reject list takes precedence over the accept list. + *
* * @param predicate the predicate. * @return {@code this} instance. @@ -301,10 +334,39 @@ public Builder setPredicate(final ObjectStreamClassPredicate predicate) { return this; } + /** + * If true, checks that all interfaces and annotations for a given type are not rejected and accepted. + *+ * For compatibility with previous versions, this is false by default. + *
+ *+ * Checks: + *
+ *+ * The reject list takes precedence over the accept list. + *
+ * + * @param strict If true, checks that all interfaces and annotations for a given type are not rejected and accepted. + * @return {@code this} instance. + * @since 2.23.0 + */ + public Builder setStrict(final boolean strict) { + this.strict = strict; + return this; + } + } /** * Constructs a new {@link Builder}. + *+ * The reject list takes precedence over the accept list. + *
* * @return a new {@link Builder}. * @since 2.18.0 @@ -314,36 +376,38 @@ public static Builder builder() { } private final ObjectStreamClassPredicate predicate; - - @SuppressWarnings("resource") // caller closes/ - private ValidatingObjectInputStream(final Builder builder) throws IOException { - this(builder.getInputStream(), builder.predicate); - } + private final boolean strict; /** * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be * deserialized, as by default no classes are accepted. + *+ * The reject list takes precedence over the accept list. + *
* - * @param input an input stream. - * @throws IOException if an I/O error occurs while reading stream header. - * @deprecated Use {@link #builder()}. + * @param builder The builder. */ - @Deprecated - public ValidatingObjectInputStream(final InputStream input) throws IOException { - this(input, new ObjectStreamClassPredicate()); + @SuppressWarnings("resource") // caller closes + private ValidatingObjectInputStream(final Builder builder) throws IOException { + super(builder.getInputStream()); + this.predicate = builder.predicate; + this.strict = builder.strict; } /** * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be * deserialized, as by default no classes are accepted. + *+ * The reject list takes precedence over the accept list. + *
* - * @param input an input stream. - * @param predicate how to accept and reject classes. + * @param input an input stream. * @throws IOException if an I/O error occurs while reading stream header. + * @deprecated Use {@link #builder()}. */ - private ValidatingObjectInputStream(final InputStream input, final ObjectStreamClassPredicate predicate) throws IOException { - super(input); - this.predicate = predicate; + @Deprecated + public ValidatingObjectInputStream(final InputStream input) throws IOException { + this(builder().setInputStream(input).setPredicate(new ObjectStreamClassPredicate())); } /** @@ -404,15 +468,38 @@ public ValidatingObjectInputStream accept(final String... patterns) { } /** - * Checks that the class name conforms to requirements. + * Checks that the given type can be legally resolved as configured. + *+ * Checks: + *
* The reject list takes precedence over the accept list. *
* - * @param name The class name to test. + * @param name The type name to test. * @throws InvalidClassException Thrown when a rejected or non-accepted class is found. */ - private void checkClassName(final String name) throws InvalidClassException { + private void checkTypeName(final String name) throws InvalidClassException { if (!predicate.test(name)) { invalidClassNameFound(name); } @@ -509,8 +596,14 @@ public ValidatingObjectInputStream reject(final String... patterns) { */ @Override protected Class> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException { - checkClassName(osc.getName()); - return super.resolveClass(osc); + checkTypeName(osc.getName()); + // resolveClass() calls Class.forName(String, boolean, ClassLoader) with initialize set to false. + // The result Class is therefore not initialized. + final Class> result = super.resolveClass(osc); + if (strict) { + check(result); + } + return result; } /** @@ -522,7 +615,7 @@ protected Class> resolveClass(final ObjectStreamClass osc) throws IOException, @Override protected Class> resolveProxyClass(final String[] interfaces) throws IOException, ClassNotFoundException { for (final String interfaceName : interfaces) { - checkClassName(interfaceName); + checkTypeName(interfaceName); } return super.resolveProxyClass(interfaces); } diff --git a/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStream2Test.java b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStream2Test.java new file mode 100644 index 00000000000..b81ec744900 --- /dev/null +++ b/src/test/java/org/apache/commons/io/serialization/ValidatingObjectInputStream2Test.java @@ -0,0 +1,172 @@ +/* + * 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 + * + * https://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.commons.io.serialization; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.Serializable; + +import org.apache.commons.lang3.SerializationUtils; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link ValidatingObjectInputStream}. + */ +class ValidatingObjectInputStream2Test { + + abstract static class AbtractFoo implements IFoo { + + private static final long serialVersionUID = 1L; + + } + + public static class FixtureObject implements IFoo { + + private static final long serialVersionUID = 1L; + + @Override + public void foo() { + // empty + } + } + + static class FooImpl extends AbtractFoo { + + private static final long serialVersionUID = 1L; + + @Override + public void foo() { + // empty + } + + } + + @FunctionalInterface + public interface IFoo extends Serializable { + + void foo(); + } + + @Test + void testAcceptAbstractClass() throws IOException, ClassNotFoundException { + final FooImpl object = new FooImpl(); + final byte[] serialized = SerializationUtils.serialize(object); + final Class