001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.io.serialization;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InvalidClassException;
024import java.io.ObjectInputStream;
025import java.io.ObjectStreamClass;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.regex.Pattern;
029
030/**
031 * An <code>ObjectInputStream</code> that's restricted to deserialize
032 * a limited set of classes.
033 *
034 * <p>
035 * Various accept/reject methods allow for specifying which classes
036 * can be deserialized.
037 * </p>
038 *
039 * <p>
040 * Design inspired by <a
041 * href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM
042 * DeveloperWorks Article</a>.
043 * </p>
044 */
045public class ValidatingObjectInputStream extends ObjectInputStream {
046    private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
047    private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();
048
049    /**
050     * Constructs an object to deserialize the specified input stream.
051     * At least one accept method needs to be called to specify which
052     * classes can be deserialized, as by default no classes are
053     * accepted.
054     *
055     * @param input an input stream
056     * @throws IOException if an I/O error occurs while reading stream header
057     */
058    public ValidatingObjectInputStream(final InputStream input) throws IOException {
059        super(input);
060    }
061
062    /** Check that the classname conforms to requirements.
063     * @param name The class name
064     * @throws InvalidClassException when a non-accepted class is encountered
065     */
066    private void validateClassName(final String name) throws InvalidClassException {
067        // Reject has precedence over accept
068        for (final ClassNameMatcher m : rejectMatchers) {
069            if (m.matches(name)) {
070                invalidClassNameFound(name);
071            }
072        }
073
074        boolean ok = false;
075        for (final ClassNameMatcher m : acceptMatchers) {
076            if (m.matches(name)) {
077                ok = true;
078                break;
079            }
080        }
081        if (!ok) {
082            invalidClassNameFound(name);
083        }
084    }
085
086    /**
087     * Called to throw <code>InvalidClassException</code> if an invalid
088     * class name is found during deserialization. Can be overridden, for example
089     * to log those class names.
090     *
091     * @param className name of the invalid class
092     * @throws InvalidClassException if the specified class is not allowed
093     */
094    protected void invalidClassNameFound(final String className) throws InvalidClassException {
095        throw new InvalidClassException("Class name not accepted: " + className);
096    }
097
098    @Override
099    protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
100        validateClassName(osc.getName());
101        return super.resolveClass(osc);
102    }
103
104    /**
105     * Accept the specified classes for deserialization, unless they
106     * are otherwise rejected.
107     *
108     * @param classes Classes to accept
109     * @return this object
110     */
111    public ValidatingObjectInputStream accept(final Class<?>... classes) {
112        for (final Class<?> c : classes) {
113            acceptMatchers.add(new FullClassNameMatcher(c.getName()));
114        }
115        return this;
116    }
117
118    /**
119     * Reject the specified classes for deserialization, even if they
120     * are otherwise accepted.
121     *
122     * @param classes Classes to reject
123     * @return this object
124     */
125    public ValidatingObjectInputStream reject(final Class<?>... classes) {
126        for (final Class<?> c : classes) {
127            rejectMatchers.add(new FullClassNameMatcher(c.getName()));
128        }
129        return this;
130    }
131
132    /**
133     * Accept the wildcard specified classes for deserialization,
134     * unless they are otherwise rejected.
135     *
136     * @param patterns Wildcard file name patterns as defined by
137     *                  {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
138     * @return this object
139     */
140    public ValidatingObjectInputStream accept(final String... patterns) {
141        for (final String pattern : patterns) {
142            acceptMatchers.add(new WildcardClassNameMatcher(pattern));
143        }
144        return this;
145    }
146
147    /**
148     * Reject the wildcard specified classes for deserialization,
149     * even if they are otherwise accepted.
150     *
151     * @param patterns Wildcard file name patterns as defined by
152     *                  {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
153     * @return this object
154     */
155    public ValidatingObjectInputStream reject(final String... patterns) {
156        for (final String pattern : patterns) {
157            rejectMatchers.add(new WildcardClassNameMatcher(pattern));
158        }
159        return this;
160    }
161
162    /**
163     * Accept class names that match the supplied pattern for
164     * deserialization, unless they are otherwise rejected.
165     *
166     * @param pattern standard Java regexp
167     * @return this object
168     */
169    public ValidatingObjectInputStream accept(final Pattern pattern) {
170        acceptMatchers.add(new RegexpClassNameMatcher(pattern));
171        return this;
172    }
173
174    /**
175     * Reject class names that match the supplied pattern for
176     * deserialization, even if they are otherwise accepted.
177     *
178     * @param pattern standard Java regexp
179     * @return this object
180     */
181    public ValidatingObjectInputStream reject(final Pattern pattern) {
182        rejectMatchers.add(new RegexpClassNameMatcher(pattern));
183        return this;
184    }
185
186    /**
187     * Accept class names where the supplied ClassNameMatcher matches for
188     * deserialization, unless they are otherwise rejected.
189     *
190     * @param m the matcher to use
191     * @return this object
192     */
193    public ValidatingObjectInputStream accept(final ClassNameMatcher m) {
194        acceptMatchers.add(m);
195        return this;
196    }
197
198    /**
199     * Reject class names where the supplied ClassNameMatcher matches for
200     * deserialization, even if they are otherwise accepted.
201     *
202     * @param m the matcher to use
203     * @return this object
204     */
205    public ValidatingObjectInputStream reject(final ClassNameMatcher m) {
206        rejectMatchers.add(m);
207        return this;
208    }
209}