Skip to content

Commit d9fa90c

Browse files
committed
Add a strict mode to ValidatingObjectInputStream
Allows interfaces and annotations to be rejected.
1 parent bd21a5c commit d9fa90c

2 files changed

Lines changed: 288 additions & 22 deletions

File tree

src/main/java/org/apache/commons/io/serialization/ValidatingObjectInputStream.java

Lines changed: 115 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.io.InvalidClassException;
2424
import java.io.ObjectInputStream;
2525
import java.io.ObjectStreamClass;
26+
import java.lang.annotation.Annotation;
2627
import java.util.regex.Pattern;
2728

2829
import org.apache.commons.io.build.AbstractStreamBuilder;
@@ -145,6 +146,8 @@ public static class Builder extends AbstractStreamBuilder<ValidatingObjectInputS
145146

146147
private ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate();
147148

149+
private boolean strict;
150+
148151
/**
149152
* Constructs a new builder of {@link ValidatingObjectInputStream}.
150153
*
@@ -157,6 +160,9 @@ public Builder() {
157160

158161
/**
159162
* Accepts the specified classes for deserialization, unless they are otherwise rejected.
163+
* <p>
164+
* The reject list takes precedence over the accept list.
165+
* </p>
160166
*
161167
* @param classes Classes to accept.
162168
* @return this object.
@@ -169,6 +175,9 @@ public Builder accept(final Class<?>... classes) {
169175

170176
/**
171177
* Accepts class names where the supplied ClassNameMatcher matches for deserialization, unless they are otherwise rejected.
178+
* <p>
179+
* The reject list takes precedence over the accept list.
180+
* </p>
172181
*
173182
* @param matcher a class name matcher to <em>accept</em> objects.
174183
* @return {@code this} instance.
@@ -181,6 +190,9 @@ public Builder accept(final ClassNameMatcher matcher) {
181190

182191
/**
183192
* Accepts class names that match the supplied pattern for deserialization, unless they are otherwise rejected.
193+
* <p>
194+
* The reject list takes precedence over the accept list.
195+
* </p>
184196
*
185197
* @param pattern a Pattern for compiled regular expression.
186198
* @return {@code this} instance.
@@ -193,6 +205,9 @@ public Builder accept(final Pattern pattern) {
193205

194206
/**
195207
* Accepts the wildcard specified classes for deserialization, unless they are otherwise rejected.
208+
* <p>
209+
* The reject list takes precedence over the accept list.
210+
* </p>
196211
*
197212
* @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
198213
* FilenameUtils.wildcardMatch}
@@ -207,6 +222,9 @@ public Builder accept(final String... patterns) {
207222
/**
208223
* Builds a new {@link ValidatingObjectInputStream}.
209224
* <p>
225+
* The reject list takes precedence over the accept list.
226+
* </p>
227+
* <p>
210228
* You must set an aspect that supports {@link #getInputStream()} on this builder, otherwise, this method throws an exception.
211229
* </p>
212230
* <p>
@@ -242,6 +260,9 @@ public ObjectStreamClassPredicate getPredicate() {
242260

243261
/**
244262
* Rejects the specified classes for deserialization, even if they are otherwise accepted.
263+
* <p>
264+
* The reject list takes precedence over the accept list.
265+
* </p>
245266
*
246267
* @param classes Classes to reject.
247268
* @return {@code this} instance.
@@ -254,6 +275,9 @@ public Builder reject(final Class<?>... classes) {
254275

255276
/**
256277
* Rejects class names where the supplied ClassNameMatcher matches for deserialization, even if they are otherwise accepted.
278+
* <p>
279+
* The reject list takes precedence over the accept list.
280+
* </p>
257281
*
258282
* @param matcher the matcher to use.
259283
* @return {@code this} instance.
@@ -266,6 +290,9 @@ public Builder reject(final ClassNameMatcher matcher) {
266290

267291
/**
268292
* Rejects class names that match the supplied pattern for deserialization, even if they are otherwise accepted.
293+
* <p>
294+
* The reject list takes precedence over the accept list.
295+
* </p>
269296
*
270297
* @param pattern standard Java regexp.
271298
* @return {@code this} instance.
@@ -278,6 +305,9 @@ public Builder reject(final Pattern pattern) {
278305

279306
/**
280307
* Rejects the wildcard specified classes for deserialization, even if they are otherwise accepted.
308+
* <p>
309+
* The reject list takes precedence over the accept list.
310+
* </p>
281311
*
282312
* @param patterns Wildcard file name patterns as defined by {@link org.apache.commons.io.FilenameUtils#wildcardMatch(String, String)
283313
* FilenameUtils.wildcardMatch}
@@ -291,6 +321,9 @@ public Builder reject(final String... patterns) {
291321

292322
/**
293323
* Sets the predicate, null resets to an empty new ObjectStreamClassPredicate.
324+
* <p>
325+
* The reject list takes precedence over the accept list.
326+
* </p>
294327
*
295328
* @param predicate the predicate.
296329
* @return {@code this} instance.
@@ -301,10 +334,39 @@ public Builder setPredicate(final ObjectStreamClassPredicate predicate) {
301334
return this;
302335
}
303336

337+
/**
338+
* If true, checks that all interfaces and annotations for a given type are not rejected and accepted.
339+
* <p>
340+
* For compatibility with previous versions, this is false by default.
341+
* </p>
342+
* <p>
343+
* Checks:
344+
* </p>
345+
* <ol>
346+
* <li>The type name.</li>
347+
* <li>The interfaces implemented by the class, recursively.</li>
348+
* <li>The annotation on this type, recursively.</li>
349+
* </ol>
350+
* <p>
351+
* The reject list takes precedence over the accept list.
352+
* </p>
353+
*
354+
* @param strict If true, checks that all interfaces and annotations for a given type are not rejected and accepted.
355+
* @return {@code this} instance.
356+
* @since 2.23.0
357+
*/
358+
public Builder setStrict(final boolean strict) {
359+
this.strict = strict;
360+
return this;
361+
}
362+
304363
}
305364

306365
/**
307366
* Constructs a new {@link Builder}.
367+
* <p>
368+
* The reject list takes precedence over the accept list.
369+
* </p>
308370
*
309371
* @return a new {@link Builder}.
310372
* @since 2.18.0
@@ -314,36 +376,38 @@ public static Builder builder() {
314376
}
315377

316378
private final ObjectStreamClassPredicate predicate;
317-
318-
@SuppressWarnings("resource") // caller closes/
319-
private ValidatingObjectInputStream(final Builder builder) throws IOException {
320-
this(builder.getInputStream(), builder.predicate);
321-
}
379+
private final boolean strict;
322380

323381
/**
324382
* Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be
325383
* deserialized, as by default no classes are accepted.
384+
* <p>
385+
* The reject list takes precedence over the accept list.
386+
* </p>
326387
*
327-
* @param input an input stream.
328-
* @throws IOException if an I/O error occurs while reading stream header.
329-
* @deprecated Use {@link #builder()}.
388+
* @param builder The builder.
330389
*/
331-
@Deprecated
332-
public ValidatingObjectInputStream(final InputStream input) throws IOException {
333-
this(input, new ObjectStreamClassPredicate());
390+
@SuppressWarnings("resource") // caller closes
391+
private ValidatingObjectInputStream(final Builder builder) throws IOException {
392+
super(builder.getInputStream());
393+
this.predicate = builder.predicate;
394+
this.strict = builder.strict;
334395
}
335396

336397
/**
337398
* Constructs an instance to deserialize the specified input stream. At least one accept method needs to be called to specify which classes can be
338399
* deserialized, as by default no classes are accepted.
400+
* <p>
401+
* The reject list takes precedence over the accept list.
402+
* </p>
339403
*
340-
* @param input an input stream.
341-
* @param predicate how to accept and reject classes.
404+
* @param input an input stream.
342405
* @throws IOException if an I/O error occurs while reading stream header.
406+
* @deprecated Use {@link #builder()}.
343407
*/
344-
private ValidatingObjectInputStream(final InputStream input, final ObjectStreamClassPredicate predicate) throws IOException {
345-
super(input);
346-
this.predicate = predicate;
408+
@Deprecated
409+
public ValidatingObjectInputStream(final InputStream input) throws IOException {
410+
this(builder().setInputStream(input).setPredicate(new ObjectStreamClassPredicate()));
347411
}
348412

349413
/**
@@ -404,15 +468,38 @@ public ValidatingObjectInputStream accept(final String... patterns) {
404468
}
405469

406470
/**
407-
* Checks that the class name conforms to requirements.
471+
* Checks that the given type can be legally resolved as configured.
472+
* <p>
473+
* Checks:
474+
* <ol>
475+
* <li>The type name.</li>
476+
* <li>The interfaces implemented by the class, recursively.</li>
477+
* <li>The annotation on this type, recursively.</li>
478+
* </ol>
479+
*
480+
* @param result
481+
* @throws InvalidClassException
482+
*/
483+
private void check(final Class<?> result) throws InvalidClassException {
484+
checkTypeName(result.getName());
485+
for (final Class<?> iface : result.getInterfaces()) {
486+
check(iface);
487+
}
488+
for (final Annotation annotation : result.getAnnotations()) {
489+
check(annotation.annotationType());
490+
}
491+
}
492+
493+
/**
494+
* Checks that the type name conforms to requirements.
408495
* <p>
409496
* The reject list takes precedence over the accept list.
410497
* </p>
411498
*
412-
* @param name The class name to test.
499+
* @param name The type name to test.
413500
* @throws InvalidClassException Thrown when a rejected or non-accepted class is found.
414501
*/
415-
private void checkClassName(final String name) throws InvalidClassException {
502+
private void checkTypeName(final String name) throws InvalidClassException {
416503
if (!predicate.test(name)) {
417504
invalidClassNameFound(name);
418505
}
@@ -509,8 +596,14 @@ public ValidatingObjectInputStream reject(final String... patterns) {
509596
*/
510597
@Override
511598
protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException, ClassNotFoundException {
512-
checkClassName(osc.getName());
513-
return super.resolveClass(osc);
599+
checkTypeName(osc.getName());
600+
// resolveClass() calls Class.forName(String, boolean, ClassLoader) with initialize set to false.
601+
// The result Class is therefore not initialized.
602+
final Class<?> result = super.resolveClass(osc);
603+
if (strict) {
604+
check(result);
605+
}
606+
return result;
514607
}
515608

516609
/**
@@ -522,7 +615,7 @@ protected Class<?> resolveClass(final ObjectStreamClass osc) throws IOException,
522615
@Override
523616
protected Class<?> resolveProxyClass(final String[] interfaces) throws IOException, ClassNotFoundException {
524617
for (final String interfaceName : interfaces) {
525-
checkClassName(interfaceName);
618+
checkTypeName(interfaceName);
526619
}
527620
return super.resolveProxyClass(interfaces);
528621
}

0 commit comments

Comments
 (0)