ValidatingObjectInputStream.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *   http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.io.serialization;

  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.InvalidClassException;
  23. import java.io.ObjectInputStream;
  24. import java.io.ObjectStreamClass;
  25. import java.util.regex.Pattern;

  26. import org.apache.commons.io.build.AbstractStreamBuilder;

  27. /**
  28.  * An {@link ObjectInputStream} that's restricted to deserialize a limited set of classes.
  29.  *
  30.  * <p>
  31.  * Various accept/reject methods allow for specifying which classes can be deserialized.
  32.  * </p>
  33.  * <h2>Reading safely</h2>
  34.  * <p>
  35.  * Here is the only way to safely read a HashMap of String keys and Integer values:
  36.  * </p>
  37.  *
  38.  * <pre>{@code
  39.  * // Defining Object fixture
  40.  * final HashMap<String, Integer> map1 = new HashMap<>();
  41.  * map1.put("1", 1);
  42.  * // Writing serialized fixture
  43.  * final byte[] byteArray;
  44.  * try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
  45.  *         final ObjectOutputStream oos = new ObjectOutputStream(baos)) {
  46.  *     oos.writeObject(map1);
  47.  *     oos.flush();
  48.  *     byteArray = baos.toByteArray();
  49.  * }
  50.  * // Reading
  51.  * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
  52.  *         ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
  53.  *             .accept(HashMap.class, Number.class, Integer.class)
  54.  *             .setInputStream(bais)
  55.  *             .get()) {
  56.  *     // String.class is automatically accepted
  57.  *     final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
  58.  *     assertEquals(map1, map2);
  59.  * }
  60.  * // Reusing a configuration
  61.  * final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate()
  62.  *     .accept(HashMap.class, Number.class, Integer.class);
  63.  * try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
  64.  *         ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
  65.  *             .setPredicate(predicate)
  66.  *             .setInputStream(bais)
  67.  *             .get()) {
  68.  *     // String.class is automatically accepted
  69.  *     final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
  70.  *     assertEquals(map1, map2);
  71.  * }
  72.  * }</pre>
  73.  * <p>
  74.  * Design inspired by a <a href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM DeveloperWorks Article</a>.
  75.  * </p>
  76.  *
  77.  * @since 2.5
  78.  */
  79. public class ValidatingObjectInputStream extends ObjectInputStream {

  80.     // @formatter:off
  81.     /**
  82.      * Builds a new {@link ValidatingObjectInputStream}.
  83.      *
  84.      * <h2>Using NIO</h2>
  85.      * <pre>{@code
  86.      * ValidatingObjectInputStream s = ValidatingObjectInputStream.builder()
  87.      *   .setPath(Paths.get("MyFile.ser"))
  88.      *   .get();}
  89.      * </pre>
  90.      * <h2>Using IO</h2>
  91.      * <pre>{@code
  92.      * ValidatingObjectInputStream s = ValidatingObjectInputStream.builder()
  93.      *   .setFile(new File("MyFile.ser"))
  94.      *   .get();}
  95.      * </pre>
  96.      *
  97.      * @see #get()
  98.      * @since 2.18.0
  99.      */
  100.     // @formatter:on
  101.     public static class Builder extends AbstractStreamBuilder<ValidatingObjectInputStream, Builder> {

  102.         private ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate();

  103.         /**
  104.          * Constructs a new builder of {@link ValidatingObjectInputStream}.
  105.          *
  106.          * @deprecated Use {@link #builder()}.
  107.          */
  108.         @Deprecated
  109.         public Builder() {
  110.             // empty
  111.         }

  112.         /**
  113.          * Accepts the specified classes for deserialization, unless they are otherwise rejected.
  114.          *
  115.          * @param classes Classes to accept
  116.          * @return this object
  117.          * @since 2.18.0
  118.          */
  119.         public Builder accept(final Class<?>... classes) {
  120.             predicate.accept(classes);
  121.             return this;
  122.         }

  123.         /**
  124.          * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected.
  125.          *
  126.          * @param matcher a class name matcher to <em>accept</em> objects.
  127.          * @return this instance.
  128.          * @since 2.18.0
  129.          */
  130.         public Builder accept(final ClassNameMatcher matcher) {
  131.             predicate.accept(matcher);
  132.             return this;
  133.         }

  134.         /**
  135.          * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected.
  136.          *
  137.          * @param pattern a Pattern for compiled regular expression.
  138.          * @return this instance.
  139.          * @since 2.18.0
  140.          */
  141.         public Builder accept(final Pattern pattern) {
  142.             predicate.accept(pattern);
  143.             return this;
  144.         }

  145.         /**
  146.          * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected.
  147.          *
  148.          * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
  149.          *                 FilenameUtils.wildcardMatch}
  150.          * @return this instance.
  151.          * @since 2.18.0
  152.          */
  153.         public Builder accept(final String... patterns) {
  154.             predicate.accept(patterns);
  155.             return this;
  156.         }

  157.         /**
  158.          * Builds a new {@link ValidatingObjectInputStream}.
  159.          * <p>
  160.          * You must set an aspect that supports {@link #getInputStream()} on this builder, otherwise, this method throws an exception.
  161.          * </p>
  162.          * <p>
  163.          * This builder uses the following aspects:
  164.          * </p>
  165.          * <ul>
  166.          * <li>{@link #getInputStream()} gets the target aspect.</li>
  167.          * <li>predicate</li>
  168.          * <li>charsetDecoder</li>
  169.          * <li>writeImmediately</li>
  170.          * </ul>
  171.          *
  172.          * @return a new instance.
  173.          * @throws UnsupportedOperationException if the origin cannot provide a {@link InputStream}.
  174.          * @throws IOException                   if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}.
  175.          * @see #getWriter()
  176.          * @see #getUnchecked()
  177.          */
  178.         @Override
  179.         public ValidatingObjectInputStream get() throws IOException {
  180.             return new ValidatingObjectInputStream(getInputStream(), predicate);
  181.         }

  182.         /**
  183.          * Gets the predicate.
  184.          *
  185.          * @return the predicate.
  186.          * @since 2.18.0
  187.          */
  188.         public ObjectStreamClassPredicate getPredicate() {
  189.             return predicate;
  190.         }

  191.         /**
  192.          * Rejects the specified classes for deserialization, even if they are otherwise accepted.
  193.          *
  194.          * @param classes Classes to reject
  195.          * @return this instance.
  196.          * @since 2.18.0
  197.          */
  198.         public Builder reject(final Class<?>... classes) {
  199.             predicate.reject(classes);
  200.             return this;
  201.         }

  202.         /**
  203.          * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted.
  204.          *
  205.          * @param matcher the matcher to use
  206.          * @return this instance.
  207.          * @since 2.18.0
  208.          */
  209.         public Builder reject(final ClassNameMatcher matcher) {
  210.             predicate.reject(matcher);
  211.             return this;
  212.         }

  213.         /**
  214.          * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted.
  215.          *
  216.          * @param pattern standard Java regexp
  217.          * @return this instance.
  218.          * @since 2.18.0
  219.          */
  220.         public Builder reject(final Pattern pattern) {
  221.             predicate.reject(pattern);
  222.             return this;
  223.         }

  224.         /**
  225.          * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted.
  226.          *
  227.          * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
  228.          *                 FilenameUtils.wildcardMatch}
  229.          * @return this instance.
  230.          * @since 2.18.0
  231.          */
  232.         public Builder reject(final String... patterns) {
  233.             predicate.reject(patterns);
  234.             return this;
  235.         }

  236.         /**
  237.          * Sets the predicate, null resets to an empty new ObjectStreamClassPredicate.
  238.          *
  239.          * @param predicate the predicate.
  240.          * @return this instance.
  241.          * @since 2.18.0
  242.          */
  243.         public Builder setPredicate(final ObjectStreamClassPredicate predicate) {
  244.             this.predicate = predicate != null ? predicate : new ObjectStreamClassPredicate();
  245.             return this;
  246.         }

  247.     }

  248.     /**
  249.      * Constructs a new {@link Builder}.
  250.      *
  251.      * @return a new {@link Builder}.
  252.      * @since 2.18.0
  253.      */
  254.     public static Builder builder() {
  255.         return new Builder();
  256.     }

  257.     private final ObjectStreamClassPredicate predicate;

  258.     /**
  259.      * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be
  260.      * deserialized, as by default no classes are accepted.
  261.      *
  262.      * @param input an input stream
  263.      * @throws IOException if an I/O error occurs while reading stream header
  264.      * @deprecated Use {@link #builder()}.
  265.      */
  266.     @Deprecated
  267.     public ValidatingObjectInputStream(final InputStream input) throws IOException {
  268.         this(input, new ObjectStreamClassPredicate());
  269.     }

  270.     /**
  271.      * Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be
  272.      * deserialized, as by default no classes are accepted.
  273.      *
  274.      * @param input     an input stream.
  275.      * @param predicate how to accept and reject classes.
  276.      * @throws IOException if an I/O error occurs while reading stream header.
  277.      */
  278.     private ValidatingObjectInputStream(final InputStream input, final ObjectStreamClassPredicate predicate) throws IOException {
  279.         super(input);
  280.         this.predicate = predicate;
  281.     }

  282.     /**
  283.      * Accepts the specified classes for deserialization, unless they are otherwise rejected.
  284.      * <p>
  285.      * The reject list takes precedence over the accept list.
  286.      * </p>
  287.      *
  288.      * @param classes Classes to accept
  289.      * @return this instance.
  290.      */
  291.     public ValidatingObjectInputStream accept(final Class<?>... classes) {
  292.         predicate.accept(classes);
  293.         return this;
  294.     }

  295.     /**
  296.      * Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected.
  297.      * <p>
  298.      * The reject list takes precedence over the accept list.
  299.      * </p>
  300.      *
  301.      * @param matcher a class name matcher to <em>accept</em> objects.
  302.      * @return this instance.
  303.      */
  304.     public ValidatingObjectInputStream accept(final ClassNameMatcher matcher) {
  305.         predicate.accept(matcher);
  306.         return this;
  307.     }

  308.     /**
  309.      * Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected.
  310.      * <p>
  311.      * The reject list takes precedence over the accept list.
  312.      * </p>
  313.      *
  314.      * @param pattern a Pattern for compiled regular expression.
  315.      * @return this instance.
  316.      */
  317.     public ValidatingObjectInputStream accept(final Pattern pattern) {
  318.         predicate.accept(pattern);
  319.         return this;
  320.     }

  321.     /**
  322.      * Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected.
  323.      * <p>
  324.      * The reject list takes precedence over the accept list.
  325.      * </p>
  326.      *
  327.      * @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
  328.      *                 FilenameUtils.wildcardMatch}.
  329.      * @return this instance.
  330.      */
  331.     public ValidatingObjectInputStream accept(final String... patterns) {
  332.         predicate.accept(patterns);
  333.         return this;
  334.     }

  335.     /**
  336.      * Checks that the class name conforms to requirements.
  337.      * <p>
  338.      * The reject list takes precedence over the accept list.
  339.      * </p>
  340.      *
  341.      * @param name The class name to test.
  342.      * @throws InvalidClassException Thrown when a rejected or non-accepted class is found.
  343.      */
  344.     private void checkClassName(final String name) throws InvalidClassException {
  345.         if (!predicate.test(name)) {
  346.             invalidClassNameFound(name);
  347.         }
  348.     }

  349.     /**
  350.      * Called to throw {@link InvalidClassException} if an invalid class name is found during deserialization. Can be overridden, for example to log those class
  351.      * names.
  352.      *
  353.      * @param className name of the invalid class.
  354.      * @throws InvalidClassException Thrown with a message containing the class name.
  355.      */
  356.     protected void invalidClassNameFound(final String className) throws InvalidClassException {
  357.         throw new InvalidClassException("Class name not accepted: " + className);
  358.     }

  359.     /**
  360.      * Delegates to {@link #readObject()} and casts to the generic {@code T}.
  361.      *
  362.      * @param <T> The return type.
  363.      * @return Result from {@link #readObject()}.
  364.      * @throws ClassNotFoundException Thrown by {@link #readObject()}.
  365.      * @throws IOException            Thrown by {@link #readObject()}.
  366.      * @throws ClassCastException     Thrown when {@link #readObject()} does not match {@code T}.
  367.      * @since 2.18.0
  368.      */
  369.     @SuppressWarnings("unchecked")
  370.     public <T> T readObjectCast() throws ClassNotFoundException, IOException {
  371.         return (T) super.readObject();
  372.     }

  373.     /**
  374.      * Rejects the specified classes for deserialization, even if they are otherwise accepted.
  375.      * <p>
  376.      * The reject list takes precedence over the accept list.
  377.      * </p>
  378.      *
  379.      * @param classes Classes to reject.
  380.      * @return this instance.
  381.      */
  382.     public ValidatingObjectInputStream reject(final Class<?>... classes) {
  383.         predicate.reject(classes);
  384.         return this;
  385.     }

  386.     /**
  387.      * Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted.
  388.      * <p>
  389.      * The reject list takes precedence over the accept list.
  390.      * </p>
  391.      *
  392.      * @param matcher a class name matcher to <em>reject</em> objects.
  393.      * @return this instance.
  394.      */
  395.     public ValidatingObjectInputStream reject(final ClassNameMatcher matcher) {
  396.         predicate.reject(matcher);
  397.         return this;
  398.     }

  399.     /**
  400.      * Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted.
  401.      * <p>
  402.      * The reject list takes precedence over the accept list.
  403.      * </p>
  404.      *
  405.      * @param pattern a Pattern for compiled regular expression.
  406.      * @return this instance.
  407.      */
  408.     public ValidatingObjectInputStream reject(final Pattern pattern) {
  409.         predicate.reject(pattern);
  410.         return this;
  411.     }

  412.     /**
  413.      * Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted.
  414.      * <p>
  415.      * The reject list takes precedence over the accept list.
  416.      * </p>
  417.      *
  418.      * @param patterns An array of wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
  419.      *                 FilenameUtils.wildcardMatch}
  420.      * @return this instance.
  421.      */
  422.     public ValidatingObjectInputStream reject(final String... patterns) {
  423.         predicate.reject(patterns);
  424.         return this;
  425.     }

  426.     @Override
  427.     protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
  428.         checkClassName(osc.getName());
  429.         return super.resolveClass(osc);
  430.     }
  431. }