Skip to content

Commit

Permalink
Add ValidatingObjectInputStream.Builder and
Browse files Browse the repository at this point in the history
ValidatingObjectInputStream.builder()

Add site documentation section "Safe Deserialization" to the User Guide
  • Loading branch information
garydgregory committed Oct 6, 2024
1 parent a8dc9eb commit ba82fe6
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 161 deletions.
2 changes: 2 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="ggregory" type="fix" due-to="Gary Gregory">Clean ups in unit tests.</action>
<!-- ADD -->
<action dev="ggregory" type="add" due-to="Gary Gregory">Add @FunctionalInterface to ClassNameMatcher.</action>
<action dev="ggregory" type="add" due-to="Gary Gregory">Add ValidatingObjectInputStream.Builder and ValidatingObjectInputStream.builder().</action>
<action dev="ggregory" type="add" due-to="Gary Gregory">Add site documentation section "Safe Deserialization" to the User Guide.</action>
<!-- UPDATE -->
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 74 to 76 #670, #676.</action>
<action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.15.1 to 1.15.3 #672, #673.</action>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,111 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.commons.io.build.AbstractStreamBuilder;
import org.apache.commons.io.input.BOMInputStream;

/**
* An {@link ObjectInputStream} that's restricted to deserialize
* a limited set of classes.
* An {@link ObjectInputStream} that's restricted to deserialize a limited set of classes.
*
* <p>
* Various accept/reject methods allow for specifying which classes
* can be deserialized.
* Various accept/reject methods allow for specifying which classes can be deserialized.
* </p>
* // *
* <p>
* // * <b>Reading safely</b> // *
* </p>
* <h2>Reading safely</h2>
* <p>
* Here is the only way to safely read a HashMap of String keys and Integer values:
* </p>
*
* <pre>{@code
* // Data
* final HashMap<String, Integer> map1 = new HashMap<>();
* map1.put("1", 1);
* // Write
* final byte[] byteArray;
* try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
* final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
* oos.writeObject(map1);
* oos.flush();
* byteArray = baos.toByteArray();
* }
* // Read
* try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
* ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setInputStream(bais).get()) {
* // String.class is automatically accepted
* vois.accept(HashMap.class, Number.class, Integer.class);
* final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
* assertEquals(map1, map2);
* }
* }</pre>
* <p>
* Design inspired by <a
* href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM
* DeveloperWorks Article</a>.
* Design inspired by a <a href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM DeveloperWorks Article</a>.
* </p>
*
* @since 2.5
*/
public class ValidatingObjectInputStream extends ObjectInputStream {

// @formatter:off
/**
* Builds a new {@link BOMInputStream}.
*
* <h2>Using NIO</h2>
* <pre>{@code
* ValidatingObjectInputStream s = ValidatingObjectInputStream.builder()
* .setPath(Paths.get("MyFile.xml"))
* .get();}
* </pre>
* <h2>Using IO</h2>
* <pre>{@code
* ValidatingObjectInputStream s = ValidatingObjectInputStream.builder()
* .setFile(new File("MyFile.xml"))
* .get();}
* </pre>
*
* @see #get()
* @since 2.18.0
*/
// @formatter:on
public static class Builder extends AbstractStreamBuilder<ValidatingObjectInputStream, Builder> {

@Override
public ValidatingObjectInputStream get() throws IOException {
return new ValidatingObjectInputStream(getInputStream());
}

}

/**
* Constructs a new {@link Builder}.
*
* @return a new {@link Builder}.
* @since 2.18.0
*/
public static Builder builder() {
return new Builder();
}

private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();

/**
* Constructs an object 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.
* Constructs an object 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.
*
* @param input an input stream
* @throws IOException if an I/O error occurs while reading stream header
* @deprecated Use {@link Builder}.
*/
@Deprecated
public ValidatingObjectInputStream(final InputStream input) throws IOException {
super(input);
}

/**
* Accept the specified classes for deserialization, unless they
* are otherwise rejected.
* Accept the specified classes for deserialization, unless they are otherwise rejected.
*
* @param classes Classes to accept
* @return this object
Expand All @@ -73,8 +143,7 @@ public ValidatingObjectInputStream accept(final Class<?>... classes) {
}

/**
* Accept class names where the supplied ClassNameMatcher matches for
* deserialization, unless they are otherwise rejected.
* Accept class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected.
*
* @param m the matcher to use
* @return this object
Expand All @@ -85,8 +154,7 @@ public ValidatingObjectInputStream accept(final ClassNameMatcher m) {
}

/**
* Accept class names that match the supplied pattern for
* deserialization, unless they are otherwise rejected.
* Accept class names that match the supplied pattern for deserialization, unless they are otherwise rejected.
*
* @param pattern standard Java regexp
* @return this object
Expand All @@ -97,11 +165,10 @@ public ValidatingObjectInputStream accept(final Pattern pattern) {
}

/**
* Accept the wildcard specified classes for deserialization,
* unless they are otherwise rejected.
* 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}
* @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
* FilenameUtils.wildcardMatch}
* @return this object
*/
public ValidatingObjectInputStream accept(final String... patterns) {
Expand Down Expand Up @@ -136,9 +203,8 @@ private void checkClassName(final String name) throws InvalidClassException {
}

/**
* Called to throw {@link InvalidClassException} if an invalid
* class name is found during deserialization. Can be overridden, for example
* to log those class names.
* Called to throw {@link InvalidClassException} if an invalid class name is found during deserialization. Can be overridden, for example to log those class
* names.
*
* @param className name of the invalid class
* @throws InvalidClassException if the specified class is not allowed
Expand All @@ -148,8 +214,7 @@ protected void invalidClassNameFound(final String className) throws InvalidClass
}

/**
* Reject the specified classes for deserialization, even if they
* are otherwise accepted.
* Reject the specified classes for deserialization, even if they are otherwise accepted.
*
* @param classes Classes to reject
* @return this object
Expand All @@ -160,8 +225,7 @@ public ValidatingObjectInputStream reject(final Class<?>... classes) {
}

/**
* Reject class names where the supplied ClassNameMatcher matches for
* deserialization, even if they are otherwise accepted.
* Reject class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted.
*
* @param m the matcher to use
* @return this object
Expand All @@ -172,8 +236,7 @@ public ValidatingObjectInputStream reject(final ClassNameMatcher m) {
}

/**
* Reject class names that match the supplied pattern for
* deserialization, even if they are otherwise accepted.
* Reject class names that match the supplied pattern for deserialization, even if they are otherwise accepted.
*
* @param pattern standard Java regexp
* @return this object
Expand All @@ -184,11 +247,10 @@ public ValidatingObjectInputStream reject(final Pattern pattern) {
}

/**
* Reject the wildcard specified classes for deserialization,
* even if they are otherwise accepted.
* Reject the wildcard specified classes for deserialization, even if they are otherwise accepted.
*
* @param patterns Wildcard file name patterns as defined by
* {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
* @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
* FilenameUtils.wildcardMatch}
* @return this object
*/
public ValidatingObjectInputStream reject(final String... patterns) {
Expand All @@ -201,4 +263,4 @@ protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException,
checkClassName(osc.getName());
return super.resolveClass(osc);
}
}
}
Loading

0 comments on commit ba82fe6

Please sign in to comment.