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