View Javadoc
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   *   https://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 static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InvalidClassException;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.util.HashMap;
33  import java.util.UUID;
34  import java.util.regex.Pattern;
35  
36  import org.apache.commons.io.input.BoundedInputStream;
37  import org.apache.commons.io.serialization.ValidatingObjectInputStream.Builder;
38  import org.apache.commons.lang3.SerializationUtils;
39  import org.junit.jupiter.api.BeforeEach;
40  import org.junit.jupiter.api.Test;
41  
42  /**
43   * Tests {@link ValidatingObjectInputStream}.
44   */
45  class ValidatingObjectInputStreamTest extends AbstractCloseableListTest {
46  
47      private static final ClassNameMatcher ALWAYS_TRUE = className -> true;
48      private MockSerializedClass testObject;
49  
50      private InputStream testStream;
51  
52      private void assertSerialization(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
53          final MockSerializedClass result = (MockSerializedClass) ois.readObject();
54          assertEquals(testObject, result);
55      }
56  
57      private Builder newBuilder() {
58          return ValidatingObjectInputStream.builder().setInputStream(testStream);
59      }
60  
61      private ValidatingObjectInputStream newFixture() throws IOException {
62          return newBuilder().get();
63      }
64  
65      @BeforeEach
66      public void setupMockSerializedClass() throws IOException {
67          testObject = new MockSerializedClass(UUID.randomUUID().toString());
68          final ByteArrayOutputStream bos = addCloseable(new ByteArrayOutputStream());
69          final ObjectOutputStream oos = addCloseable(new ObjectOutputStream(bos));
70          oos.writeObject(testObject);
71          testStream = addCloseable(new ByteArrayInputStream(bos.toByteArray()));
72      }
73  
74      @Test
75      void testAcceptCustomMatcherBuilder() throws Exception {
76          assertSerialization(addCloseable(newBuilder().accept(ALWAYS_TRUE).get()));
77      }
78  
79      @Test
80      void testAcceptCustomMatcherInstance() throws Exception {
81          assertSerialization(addCloseable(newFixture()).accept(ALWAYS_TRUE));
82      }
83  
84      @Test
85      void testAcceptOneFail() throws Exception {
86          assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(Integer.class)));
87      }
88  
89      @Test
90      void testAcceptOnePassBuilder() throws Exception {
91          assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).get()));
92      }
93  
94      @Test
95      void testAcceptOnePassInstance() throws Exception {
96          assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class));
97      }
98  
99      @Test
100     void testAcceptPatternBuilder() throws Exception {
101         assertSerialization(addCloseable(newBuilder().accept(Pattern.compile(".*MockSerializedClass.*")).get()));
102     }
103 
104     @Test
105     void testAcceptPatternInstance() throws Exception {
106         assertSerialization(addCloseable(newFixture()).accept(Pattern.compile(".*MockSerializedClass.*")));
107     }
108 
109     @Test
110     void testAcceptWildcardBuilder() throws Exception {
111         assertSerialization(addCloseable(newBuilder().accept("org.apache.commons.io.*").get()));
112     }
113 
114     @Test
115     void testAcceptWildcardInstance() throws Exception {
116         assertSerialization(addCloseable(newFixture()).accept("org.apache.commons.io.*"));
117     }
118 
119     @Test
120     void testBuildDefault() throws Exception {
121         final byte[] serialized = SerializationUtils.serialize("");
122         try (InputStream is = newBuilder().setInputStream(new ByteArrayInputStream(serialized)).get()) {
123             // empty
124         }
125     }
126 
127     @Test
128     void testConstructor() throws Exception {
129         assertSerialization(addCloseable(newFixture()).accept(ALWAYS_TRUE));
130     }
131 
132     @Test
133     void testCustomInvalidMethod() {
134         final class CustomVOIS extends ValidatingObjectInputStream {
135             CustomVOIS(final InputStream is) throws IOException {
136                 super(is);
137             }
138 
139             @Override
140             protected void invalidClassNameFound(final String className) throws InvalidClassException {
141                 throw new RuntimeException("Custom exception");
142             }
143         }
144 
145         assertThrows(RuntimeException.class, () -> assertSerialization(addCloseable(new CustomVOIS(testStream)).reject(Integer.class)));
146     }
147 
148     @Test
149     void testExceptionIncludesClassName() throws Exception {
150         final InvalidClassException ice = assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture())));
151         final String name = MockSerializedClass.class.getName();
152         assertTrue(ice.getMessage().contains(name), "Expecting message to contain " + name);
153     }
154 
155     /**
156      * Javadoc example.
157      */
158     @SuppressWarnings({ "unchecked" })
159     @Test
160     void testJavadocExample() throws Exception {
161         // @formatter:off
162         // Defining Object fixture
163         final HashMap<String, Integer> map1 = new HashMap<>();
164         map1.put("1", 1);
165         // Writing serialized fixture
166         final byte[] byteArray;
167         try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
168                 ObjectOutputStream oos = new ObjectOutputStream(baos)) {
169             oos.writeObject(map1);
170             oos.flush();
171             byteArray = baos.toByteArray();
172         }
173         // Deserializing
174         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
175                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
176                     .accept(HashMap.class, Number.class, Integer.class)
177                     .setInputStream(bais)
178                     .get()) {
179             // String.class is automatically accepted
180             final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
181             assertEquals(map1, map2);
182         }
183         // Reusing a configuration
184         final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate()
185                 .accept(HashMap.class, Number.class, Integer.class);
186         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
187                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
188                     .setPredicate(predicate)
189                     .setInputStream(bais)
190                     .get()) {
191             // String.class is automatically accepted
192             final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
193             assertEquals(map1, map2);
194         }
195         // Deserializing with a size limit successfully
196         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
197                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
198                     .accept(HashMap.class, Number.class, Integer.class)
199                     .setInputStream(BoundedInputStream.builder()
200                         .setMaxCount(10_000)
201                         .setOnMaxCount((remains, count) -> {
202                             throw new IllegalArgumentException("Input exceeds limit.");
203                         })
204                         .setInputStream(bais)
205                         .get())
206                     .get()) {
207             // String.class is automatically accepted
208             final HashMap<String, Integer> map2 = (HashMap<String, Integer>) vois.readObject();
209             assertEquals(map1, map2);
210         }
211         // Deserializing with a size limit reaching the limit
212         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
213                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder()
214                     .accept(HashMap.class, Number.class, Integer.class)
215                     .setInputStream(BoundedInputStream.builder()
216                         .setMaxCount(10)
217                         .setOnMaxCount((remains, count) -> {
218                             throw new IllegalArgumentException("Input exceeds limit.");
219                         })
220                         .setInputStream(bais)
221                         .get())
222                     .get()) {
223             // String.class is automatically accepted
224             final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> vois.readObject());
225             assertEquals("Input exceeds limit.", e.getMessage());
226         }
227         // @formatter:on
228     }
229 
230     @Test
231     void testNoAccept() {
232         assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture())));
233     }
234 
235     @Test
236     void testOurTestClassAcceptedFirst() throws Exception {
237         assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class, Integer.class));
238     }
239 
240     @Test
241     void testOurTestClassAcceptedFirstWildcard() throws Exception {
242         assertSerialization(addCloseable(newFixture()).accept("*MockSerializedClass", "*Integer"));
243     }
244 
245     @Test
246     void testOurTestClassAcceptedSecond() throws Exception {
247         assertSerialization(addCloseable(newFixture()).accept(Integer.class, MockSerializedClass.class));
248     }
249 
250     @Test
251     void testOurTestClassAcceptedSecondWildcard() throws Exception {
252         assertSerialization(addCloseable(newFixture()).accept("*Integer", "*MockSerializedClass"));
253     }
254 
255     @Test
256     void testOurTestClassNotAccepted() {
257         assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(Integer.class)));
258     }
259 
260     @Test
261     void testOurTestClassOnlyAccepted() throws Exception {
262         assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class));
263     }
264 
265     @Test
266     void testRejectBuilder() {
267         assertThrows(InvalidClassException.class,
268                 () -> assertSerialization(addCloseable(newBuilder().accept(Long.class).reject(MockSerializedClass.class, Integer.class).get())));
269     }
270 
271     @Test
272     void testRejectCustomMatcherBuilder() {
273         assertThrows(InvalidClassException.class,
274                 () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(ALWAYS_TRUE).get())));
275     }
276 
277     @Test
278     void testRejectCustomMatcherInstance() {
279         assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(ALWAYS_TRUE)));
280     }
281 
282     @Test
283     void testRejectInstance() {
284         assertThrows(InvalidClassException.class,
285                 () -> assertSerialization(addCloseable(newFixture()).accept(Long.class).reject(MockSerializedClass.class, Integer.class)));
286     }
287 
288     @Test
289     void testRejectOnly() {
290         assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).reject(Integer.class)));
291     }
292 
293     @Test
294     void testRejectPattern() {
295         assertThrows(InvalidClassException.class,
296                 () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(Pattern.compile("org\\..*"))));
297     }
298 
299     @Test
300     void testRejectPatternBuilder() {
301         assertThrows(InvalidClassException.class,
302                 () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(Pattern.compile("org\\..*")).get())));
303     }
304 
305     @Test
306     void testRejectPrecedenceBuilder() {
307         assertThrows(InvalidClassException.class,
308                 () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject(MockSerializedClass.class, Integer.class).get())));
309     }
310 
311     @Test
312     void testRejectPrecedenceInstance() {
313         assertThrows(InvalidClassException.class,
314                 () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject(MockSerializedClass.class, Integer.class)));
315     }
316 
317     @Test
318     void testRejectWildcardBuilder() {
319         assertThrows(InvalidClassException.class,
320                 () -> assertSerialization(addCloseable(newBuilder().accept(MockSerializedClass.class).reject("org.*").get())));
321     }
322 
323     @Test
324     void testRejectWildcardInstance() {
325         assertThrows(InvalidClassException.class, () -> assertSerialization(addCloseable(newFixture()).accept(MockSerializedClass.class).reject("org.*")));
326     }
327 
328     @Test
329     void testReuseConfiguration() throws Exception {
330         // Defining Object fixture
331         final HashMap<String, Integer> map1 = new HashMap<>();
332         map1.put("1", 1);
333         // Writing serialized fixture
334         final byte[] byteArray;
335         try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
336                 ObjectOutputStream oos = new ObjectOutputStream(baos)) {
337             oos.writeObject(map1);
338             oos.flush();
339             byteArray = baos.toByteArray();
340         }
341         // Reusing a configuration: ObjectStreamClassPredicate
342         final ObjectStreamClassPredicate predicate = new ObjectStreamClassPredicate().accept(HashMap.class, Number.class, Integer.class);
343         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
344                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get()) {
345             // String.class is automatically accepted
346             assertEquals(map1, vois.readObjectCast());
347         }
348         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
349                 ValidatingObjectInputStream vois = ValidatingObjectInputStream.builder().setPredicate(predicate).setInputStream(bais).get()) {
350             // String.class is automatically accepted
351             assertEquals(map1, vois.readObjectCast());
352         }
353         // Reusing a configuration: Builder and ObjectStreamClassPredicate
354         final Builder builder = ValidatingObjectInputStream.builder().setPredicate(predicate);
355         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
356                 ValidatingObjectInputStream vois = builder.setInputStream(bais).get()) {
357             // String.class is automatically accepted
358             assertEquals(map1, vois.readObjectCast());
359         }
360         try (ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
361                 ValidatingObjectInputStream vois = builder.setInputStream(bais).get()) {
362             // String.class is automatically accepted
363             assertEquals(map1, vois.readObjectCast());
364         }
365     }
366 }