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