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
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InvalidClassException;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectStreamClass;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.regex.Pattern;
29 import java.util.stream.Stream;
30
31 /**
32 * An {@link ObjectInputStream} that's restricted to deserialize
33 * a limited set of classes.
34 *
35 * <p>
36 * Various accept/reject methods allow for specifying which classes
37 * can be deserialized.
38 * </p>
39 *
40 * <p>
41 * Design inspired by <a
42 * href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM
43 * DeveloperWorks Article</a>.
44 * </p>
45 */
46 public class ValidatingObjectInputStream extends ObjectInputStream {
47 private final List<ClassNameMatcher> acceptMatchers = new ArrayList<>();
48 private final List<ClassNameMatcher> rejectMatchers = new ArrayList<>();
49
50 /**
51 * Constructs an object to deserialize the specified input stream.
52 * At least one accept method needs to be called to specify which
53 * classes can be deserialized, as by default no classes are
54 * accepted.
55 *
56 * @param input an input stream
57 * @throws IOException if an I/O error occurs while reading stream header
58 */
59 public ValidatingObjectInputStream(final InputStream input) throws IOException {
60 super(input);
61 }
62
63 /**
64 * Accept the specified classes for deserialization, unless they
65 * are otherwise rejected.
66 *
67 * @param classes Classes to accept
68 * @return this object
69 */
70 public ValidatingObjectInputStream accept(final Class<?>... classes) {
71 Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(acceptMatchers::add);
72 return this;
73 }
74
75 /**
76 * Accept class names where the supplied ClassNameMatcher matches for
77 * deserialization, unless they are otherwise rejected.
78 *
79 * @param m the matcher to use
80 * @return this object
81 */
82 public ValidatingObjectInputStream accept(final ClassNameMatcher m) {
83 acceptMatchers.add(m);
84 return this;
85 }
86
87 /**
88 * Accept class names that match the supplied pattern for
89 * deserialization, unless they are otherwise rejected.
90 *
91 * @param pattern standard Java regexp
92 * @return this object
93 */
94 public ValidatingObjectInputStream accept(final Pattern pattern) {
95 acceptMatchers.add(new RegexpClassNameMatcher(pattern));
96 return this;
97 }
98
99 /**
100 * Accept the wildcard specified classes for deserialization,
101 * unless they are otherwise rejected.
102 *
103 * @param patterns Wildcard file name patterns as defined by
104 * {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
105 * @return this object
106 */
107 public ValidatingObjectInputStream accept(final String... patterns) {
108 Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(acceptMatchers::add);
109 return this;
110 }
111
112 /**
113 * Checks that the class name conforms to requirements.
114 *
115 * @param name The class name
116 * @throws InvalidClassException when a non-accepted class is encountered
117 */
118 private void checkClassName(final String name) throws InvalidClassException {
119 // Reject has precedence over accept
120 for (final ClassNameMatcher m : rejectMatchers) {
121 if (m.matches(name)) {
122 invalidClassNameFound(name);
123 }
124 }
125
126 boolean ok = false;
127 for (final ClassNameMatcher m : acceptMatchers) {
128 if (m.matches(name)) {
129 ok = true;
130 break;
131 }
132 }
133 if (!ok) {
134 invalidClassNameFound(name);
135 }
136 }
137
138 /**
139 * Called to throw {@link InvalidClassException} if an invalid
140 * class name is found during deserialization. Can be overridden, for example
141 * to log those class names.
142 *
143 * @param className name of the invalid class
144 * @throws InvalidClassException if the specified class is not allowed
145 */
146 protected void invalidClassNameFound(final String className) throws InvalidClassException {
147 throw new InvalidClassException("Class name not accepted: " + className);
148 }
149
150 /**
151 * Reject the specified classes for deserialization, even if they
152 * are otherwise accepted.
153 *
154 * @param classes Classes to reject
155 * @return this object
156 */
157 public ValidatingObjectInputStream reject(final Class<?>... classes) {
158 Stream.of(classes).map(c -> new FullClassNameMatcher(c.getName())).forEach(rejectMatchers::add);
159 return this;
160 }
161
162 /**
163 * Reject class names where the supplied ClassNameMatcher matches for
164 * deserialization, even if they are otherwise accepted.
165 *
166 * @param m the matcher to use
167 * @return this object
168 */
169 public ValidatingObjectInputStream reject(final ClassNameMatcher m) {
170 rejectMatchers.add(m);
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 * Reject the wildcard specified classes for deserialization,
188 * even if they are otherwise accepted.
189 *
190 * @param patterns Wildcard file name patterns as defined by
191 * {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String) FilenameUtils.wildcardMatch}
192 * @return this object
193 */
194 public ValidatingObjectInputStream reject(final String... patterns) {
195 Stream.of(patterns).map(WildcardClassNameMatcher::new).forEach(rejectMatchers::add);
196 return this;
197 }
198
199 @Override
200 protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
201 checkClassName(osc.getName());
202 return super.resolveClass(osc);
203 }
204 }