Class ValidatingObjectInputStream

java.lang.Object
java.io.InputStream
java.io.ObjectInputStream
org.apache.commons.io.serialization.ValidatingObjectInputStream
All Implemented Interfaces:
Closeable, DataInput, ObjectInput, ObjectStreamConstants, AutoCloseable

An ObjectInputStream that's restricted to deserialize a limited set of classes.

Various accept/reject methods allow for specifying which classes can be deserialized.

Deserlizing safely

Here is the only way to safely read a HashMap of String keys and Integer values:

// Defining Object fixture
final HashMap<String, Integer> map1 = new HashMap<>();
map1.put("1", 1);
// Writing serialized fixture
final byte[] byteArray;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
    oos.writeObject(map1);
    oos.flush();
    byteArray = baos.toByteArray();
}
// Deserializing
try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
        ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
            .accept(HashMap.class, Number.class, Integer.class)
            .setInputStream(bais)
            .get()) {
    // String.class is automatically accepted
    final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
    assertEquals(map1, map2);
}
// Reusing a configuration
final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate()
    .accept(HashMap.class, Number.class, Integer.class);
try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
        ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
            .setPredicate(predicate)
            .setInputStream(bais)
            .get()) {
    // String.class is automatically accepted
    final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
    assertEquals(map1, map2);
}

This design was inspired by a IBM DeveloperWorks Article.

Deserlizing with a size boundary

You can further guard your application againt untrusted input by limiting how much data to process using a BoundedInputStream. For example:

// Deserializing with a size limit successfully
try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
        ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
            .accept(HashMap.class, Number.class, Integer.class)
            .setInputStream(BoundedInputStream.builder()
                .setMaxCount(10_000)
                .setOnMaxCount((max, count) -> {
                    throw new IllegalArgumentException("Input exceeds limit.");
                })
                .setInputStream(bais)
                .get())
            .get()) {
    // String.class is automatically accepted
    final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
    assertEquals(map1, map2);
}
// Deserializing with a size limit reaching the limit
try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
        ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
            .accept(HashMap.class, Number.class, Integer.class)
            .setInputStream(BoundedInputStream.builder()
                .setMaxCount(10)
                .setOnMaxCount((max, count) -> {
                    throw new IllegalArgumentException("Input exceeds limit.");
                })
                .setInputStream(bais)
                .get())
            .get()) {
    // String.class is automatically accepted
    final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> vois.readObject());
    assertEquals("Input exceeds limit.", e.getMessage());
}
Since:
2.5