View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  
32  import org.apache.commons.codec.CodecPolicy;
33  import org.apache.commons.io.IOUtils;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * Tests {@link Base32InputStream}.
38   */
39  class Base32InputStreamTest {
40  
41      private static final String ENCODED_FOO = "MZXW6===";
42  
43      private static final byte[] CRLF = { (byte) '\r', (byte) '\n' };
44  
45      private static final byte[] LF = { (byte) '\n' };
46  
47      private static final String STRING_FIXTURE = "Hello World";
48  
49      /**
50       * Tests skipping past the end of a stream.
51       *
52       * @throws Throwable
53       *             for some failure scenarios.
54       */
55      @Test
56      void testAvailable() throws Throwable {
57          final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
58          try (Base32InputStream b32stream = new Base32InputStream(ins)) {
59              assertEquals(1, b32stream.available());
60              assertEquals(3, b32stream.skip(10));
61              // End of stream reached
62              assertEquals(0, b32stream.available());
63              assertEquals(-1, b32stream.read());
64              assertEquals(-1, b32stream.read());
65              assertEquals(0, b32stream.available());
66          }
67      }
68  
69      private void testBase32EmptyInputStream(final int chuckSize) throws Exception {
70          final byte[] emptyEncoded = {};
71          final byte[] emptyDecoded = {};
72          testByteByByte(emptyEncoded, emptyDecoded, chuckSize, CRLF);
73          testByChunk(emptyEncoded, emptyDecoded, chuckSize, CRLF);
74      }
75  
76      /**
77       * Tests the Base32InputStream implementation against empty input.
78       *
79       * @throws Exception
80       *             for some failure scenarios.
81       */
82      @Test
83      void testBase32EmptyInputStreamMimeChuckSize() throws Exception {
84          testBase32EmptyInputStream(BaseNCodec.MIME_CHUNK_SIZE);
85      }
86  
87      // /**
88      // * Test for the CODEC-101 bug: InputStream.read(byte[]) should never return 0
89      // * because Java's builtin InputStreamReader hates that.
90      // *
91      // * @throws Exception for some failure scenarios.
92      // */
93      // @Test
94      // void testCodec101() throws Exception {
95      // byte[] codec101 = StringUtils.getBytesUtf8(Base32TestData.CODEC_101_MULTIPLE_OF_3);
96      // ByteArrayInputStream bais = new ByteArrayInputStream(codec101);
97      // Base32InputStream in = new Base32InputStream(bais);
98      // byte[] result = new byte[8192];
99      // int c = in.read(result);
100     // assertTrue("Codec101: First read successful [c=" + c + "]", c > 0);
101     //
102     // c = in.read(result);
103     // assertTrue("Codec101: Second read should report end-of-stream [c=" + c + "]", c < 0);
104     // }
105 
106     /**
107      * Another test for the CODEC-101 bug: In commons-codec-1.4 this test shows InputStreamReader explicitly hating an
108      * InputStream.read(byte[]) return of 0:
109      *
110      * java.io.IOException: Underlying input stream returned zero bytes at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:268) at
111      * sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) at
112      * java.io.InputStreamReader.read(InputStreamReader.java:167) at java.io.BufferedReader.fill(BufferedReader.java:136) at
113      * java.io.BufferedReader.readLine(BufferedReader.java:299) at java.io.BufferedReader.readLine(BufferedReader.java:362) at
114      * org.apache.commons.codec.binary.Base32InputStreamTest.testInputStreamReader(Base32InputStreamTest.java:75)
115      *
116      * But in commons-codec-1.5 it's fixed. :-)
117      *
118      * @throws Exception
119      *             for some failure scenarios.
120      */
121     // @Test
122     // void testInputStreamReader() throws Exception {
123     // byte[] codec101 = StringUtils.getBytesUtf8(Base32TestData.CODEC_101_MULTIPLE_OF_3);
124     // ByteArrayInputStream bais = new ByteArrayInputStream(codec101);
125     // Base32InputStream in = new Base32InputStream(bais);
126     // InputStreamReader isr = new InputStreamReader(in);
127     // BufferedReader br = new BufferedReader(isr);
128     // String line = br.readLine();
129     // assertNotNull("Codec101:  InputStreamReader works!", line);
130     // }
131 
132     /**
133      * Test the Base32InputStream implementation against the special NPE inducing input identified in the CODEC-98 bug.
134      *
135      * @throws Exception
136      *             for some failure scenarios.
137      */
138     // @Test
139     // void testCodec98NPE() throws Exception {
140     // byte[] codec98 = StringUtils.getBytesUtf8(Base32TestData.CODEC_98_NPE);
141     // ByteArrayInputStream data = new ByteArrayInputStream(codec98);
142     // Base32InputStream stream = new Base32InputStream(data);
143     //
144     // // This line causes an NPE in commons-codec-1.4.jar:
145     // byte[] decodedBytes = Base32TestData.streamToBytes(stream, new byte[1024]);
146     //
147     // String decoded = StringUtils.newStringUtf8(decodedBytes);
148     // assertEquals(
149     // "codec-98 NPE Base32InputStream", Base32TestData.CODEC_98_NPE_DECODED, decoded
150     // );
151     // }
152 
153     /**
154      * Tests the Base32InputStream implementation against empty input.
155      *
156      * @throws Exception
157      *             for some failure scenarios.
158      */
159     @Test
160     void testBase32EmptyInputStreamPemChuckSize() throws Exception {
161         testBase32EmptyInputStream(BaseNCodec.PEM_CHUNK_SIZE);
162     }
163 
164     /**
165      * Tests the Base32InputStream implementation.
166      *
167      * @throws Exception
168      *             for some failure scenarios.
169      */
170     @Test
171     void testBase32InputStreamByChunk() throws Exception {
172         // Hello World test.
173         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
174         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
175         testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
176 
177         // Single Byte test.
178         encoded = StringUtils.getBytesUtf8("AA======\r\n");
179         decoded = new byte[] { (byte) 0 };
180         testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
181 
182         // // OpenSSL interop test.
183         // encoded = StringUtils.getBytesUtf8(Base32TestData.ENCODED_32_CHARS_PER_LINE);
184         // decoded = Base32TestData.DECODED;
185         // testByChunk(encoded, decoded, Base32.PEM_CHUNK_SIZE, LF);
186         //
187         // // Single Line test.
188         // String singleLine = Base32TestData.ENCODED_32_CHARS_PER_LINE.replaceAll("\n", "");
189         // encoded = StringUtils.getBytesUtf8(singleLine);
190         // decoded = Base32TestData.DECODED;
191         // testByChunk(encoded, decoded, 0, LF);
192 
193         // test random data of sizes 0 through 150
194         final BaseNCodec codec = new Base32();
195         for (int i = 0; i <= 150; i++) {
196             final byte[][] randomData = BaseNTestData.randomData(codec, i);
197             encoded = randomData[1];
198             decoded = randomData[0];
199             testByChunk(encoded, decoded, 0, LF);
200         }
201     }
202 
203     /**
204      * Tests the Base32InputStream implementation.
205      *
206      * @throws Exception
207      *             for some failure scenarios.
208      */
209     @Test
210     void testBase32InputStreamByteByByte() throws Exception {
211         // Hello World test.
212         byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
213         byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
214         testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
215 
216         // Single Byte test.
217         encoded = StringUtils.getBytesUtf8("AA======\r\n");
218         decoded = new byte[] { (byte) 0 };
219         testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
220 
221         // // Single Line test.
222         // String singleLine = Base32TestData.ENCODED_32_CHARS_PER_LINE.replaceAll("\n", "");
223         // encoded = StringUtils.getBytesUtf8(singleLine);
224         // decoded = Base32TestData.DECODED;
225         // testByteByByte(encoded, decoded, 0, LF);
226 
227         // test random data of sizes 0 through 150
228         final BaseNCodec codec = new Base32();
229         for (int i = 0; i <= 150; i++) {
230             final byte[][] randomData = BaseNTestData.randomData(codec, i);
231             encoded = randomData[1];
232             decoded = randomData[0];
233             testByteByByte(encoded, decoded, 0, LF);
234         }
235     }
236 
237     @Test
238     void testBuilder() {
239         assertNotNull(Base32InputStream.builder().getBaseNCodec());
240     }
241 
242     /**
243      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
244      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
245      * <p/>
246      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32InputStream wraps itself in encode and decode mode over and over
247      * again.
248      *
249      * @param encoded
250      *            base32 encoded data
251      * @param decoded
252      *            the data from above, but decoded
253      * @param chunkSize
254      *            chunk size (line-length) of the base32 encoded data.
255      * @param separator
256      *            Line separator in the base32 encoded data.
257      * @throws Exception
258      *             Usually signifies a bug in the Base32 commons-codec implementation.
259      */
260     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
261         // Start with encode.
262         try (InputStream in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, separator)) {
263             final byte[] output = IOUtils.toByteArray(in);
264             assertEquals(-1, in.read(), "EOF");
265             assertEquals(-1, in.read(), "Still EOF");
266             assertArrayEquals(encoded, output, "Streaming base32 encode");
267         }
268         // Now let's try to decode.
269         try (InputStream in = new Base32InputStream(new ByteArrayInputStream(encoded))) {
270             final byte[] output = IOUtils.toByteArray(in);
271 
272             assertEquals(-1, in.read(), "EOF");
273             assertEquals(-1, in.read(), "Still EOF");
274             assertArrayEquals(decoded, output, "Streaming base32 decode");
275         }
276         // I always wanted to do this! (wrap encoder with decoder etc.).
277         InputStream in = new ByteArrayInputStream(decoded);
278         for (int i = 0; i < 10; i++) {
279             in = new Base32InputStream(in, true, chunkSize, separator);
280             in = new Base32InputStream(in, false);
281         }
282         final InputStream in1 = in;
283         final byte[] output = IOUtils.toByteArray(in1);
284         assertEquals(-1, in.read(), "EOF");
285         assertEquals(-1, in.read(), "Still EOF");
286         assertArrayEquals(decoded, output, "Streaming base32 wrap-wrap-wrap!");
287         in.close();
288     }
289 
290     /**
291      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
292      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
293      * <p/>
294      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32InputStream wraps itself in encode and decode mode over and over
295      * again.
296      *
297      * @param encoded
298      *            base32 encoded data
299      * @param decoded
300      *            the data from above, but decoded
301      * @param chunkSize
302      *            chunk size (line-length) of the base32 encoded data.
303      * @param separator
304      *            Line separator in the base32 encoded data.
305      * @throws Exception
306      *             Usually signifies a bug in the Base32 commons-codec implementation.
307      */
308     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
309 
310         // Start with encode.
311         InputStream in;
312         in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, separator);
313         byte[] output = new byte[encoded.length];
314         for (int i = 0; i < output.length; i++) {
315             output[i] = (byte) in.read();
316         }
317 
318         assertEquals(-1, in.read(), "EOF");
319         assertEquals(-1, in.read(), "Still EOF");
320         assertArrayEquals(encoded, output, "Streaming base32 encode");
321 
322         in.close();
323 
324         // Now let's try to decode.
325         in = new Base32InputStream(new ByteArrayInputStream(encoded));
326         output = new byte[decoded.length];
327         for (int i = 0; i < output.length; i++) {
328             output[i] = (byte) in.read();
329         }
330 
331         assertEquals(-1, in.read(), "EOF");
332         assertEquals(-1, in.read(), "Still EOF");
333         assertArrayEquals(decoded, output, "Streaming base32 decode");
334 
335         in.close();
336 
337         // I always wanted to do this! (wrap encoder with decoder etc.).
338         in = new ByteArrayInputStream(decoded);
339         for (int i = 0; i < 10; i++) {
340             in = new Base32InputStream(in, true, chunkSize, separator);
341             in = new Base32InputStream(in, false);
342         }
343         output = new byte[decoded.length];
344         for (int i = 0; i < output.length; i++) {
345             output[i] = (byte) in.read();
346         }
347 
348         assertEquals(-1, in.read(), "EOF");
349         assertEquals(-1, in.read(), "Still EOF");
350         assertArrayEquals(decoded, output, "Streaming base32 wrap-wrap-wrap!");
351     }
352 
353     /**
354      * Tests the bug reported in CODEC-105. Bad interactions with InputStream when reading one byte at a time.
355      */
356     @Test
357     void testCodec105() throws IOException {
358         try (Base32InputStream in = new Base32InputStream(new Codec105ErrorInputStream(), true, 0, null)) {
359             for (int i = 0; i < 5; i++) {
360                 in.read();
361             }
362         }
363     }
364 
365     /**
366      * Tests the problem reported in CODEC-130. Missing / wrong implementation of skip.
367      */
368     @Test
369     void testCodec130() throws IOException {
370         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
371         try (Base32OutputStream base32os = new Base32OutputStream(bos)) {
372             base32os.write(StringUtils.getBytesUtf8(STRING_FIXTURE));
373         }
374 
375         final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
376         final Base32InputStream ins = new Base32InputStream(bis);
377 
378         // we skip the first character read from the reader
379         assertEquals(1, ins.skip(1));
380         final byte[] decodedBytes = IOUtils.toByteArray(ins);
381         final String str = StringUtils.newStringUtf8(decodedBytes);
382 
383         assertEquals(STRING_FIXTURE.substring(1), str);
384     }
385 
386     /**
387      * Tests markSupported.
388      *
389      * @throws Exception
390      *             for some failure scenarios.
391      */
392     @Test
393     void testMarkSupported() throws Exception {
394         final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
395         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
396         try (Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 })) {
397             // Always returns false for now.
398             assertFalse(in.markSupported(), "Base32InputStream.markSupported() is false");
399         }
400     }
401 
402     /**
403      * Tests read returning 0
404      *
405      * @throws Exception
406      *             for some failure scenarios.
407      */
408     @Test
409     void testRead0() throws Exception {
410         final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
411         final byte[] buf = new byte[1024];
412         int bytesRead = 0;
413         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
414         try (Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 })) {
415             bytesRead = in.read(buf, 0, 0);
416             assertEquals(0, bytesRead, "Base32InputStream.read(buf, 0, 0) returns 0");
417         }
418     }
419 
420     /**
421      * Tests read with null.
422      *
423      * @throws Exception
424      *             for some failure scenarios.
425      */
426     @Test
427     void testReadNull() throws Exception {
428         final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
429         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
430         try (Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] {0, 0, 0})) {
431             assertThrows(NullPointerException.class, () -> in.read(null, 0, 0));
432         }
433     }
434 
435     /**
436      * Tests read throwing IndexOutOfBoundsException
437      *
438      * @throws Exception
439      *             for some failure scenarios.
440      */
441     @Test
442     void testReadOutOfBounds() throws Exception {
443         final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
444         final byte[] buf = new byte[1024];
445         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
446         try (Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 })) {
447             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, -1, 0), "Base32InputStream.read(buf, -1, 0)");
448             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, 0, -1), "Base32InputStream.read(buf, 0, -1)");
449             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length + 1, 0), "Base32InputStream.read(buf, buf.length + 1, 0)");
450             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length - 1, 2), "Base32InputStream.read(buf, buf.length - 1, 2)");
451         }
452     }
453 
454     /**
455      * Tests skipping number of characters larger than the internal buffer.
456      *
457      * @throws Throwable
458      *             for some failure scenarios.
459      */
460     @Test
461     void testSkipBig() throws Throwable {
462         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
463         try (Base32InputStream b32stream = new Base32InputStream(ins)) {
464             assertEquals(3, b32stream.skip(1024));
465             // End of stream reached
466             assertEquals(-1, b32stream.read());
467             assertEquals(-1, b32stream.read());
468         }
469     }
470 
471     /**
472      * Tests skipping as a noop
473      *
474      * @throws Throwable
475      *             for some failure scenarios.
476      */
477     @Test
478     void testSkipNone() throws Throwable {
479         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
480         try (Base32InputStream b32stream = new Base32InputStream(ins)) {
481             final byte[] actualBytes = new byte[6];
482             assertEquals(0, b32stream.skip(0));
483             b32stream.read(actualBytes, 0, actualBytes.length);
484             assertArrayEquals(actualBytes, new byte[] { 102, 111, 111, 0, 0, 0 });
485             // End of stream reached
486             assertEquals(-1, b32stream.read());
487         }
488     }
489 
490     /**
491      * Tests skipping past the end of a stream.
492      *
493      * @throws Throwable
494      *             for some failure scenarios.
495      */
496     @Test
497     void testSkipPastEnd() throws Throwable {
498         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
499         try (Base32InputStream b32stream = new Base32InputStream(ins)) {
500             // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
501             assertEquals(3, b32stream.skip(10));
502             // End of stream reached
503             assertEquals(-1, b32stream.read());
504             assertEquals(-1, b32stream.read());
505         }
506 }
507 
508     /**
509      * Tests skipping to the end of a stream.
510      *
511      * @throws Throwable
512      *             for some failure scenarios.
513      */
514     @Test
515     void testSkipToEnd() throws Throwable {
516         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
517         try (Base32InputStream b32stream = new Base32InputStream(ins)) {
518             // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
519             assertEquals(3, b32stream.skip(3));
520             // End of stream reached
521             assertEquals(-1, b32stream.read());
522             assertEquals(-1, b32stream.read());
523         }
524     }
525 
526     /**
527      * Tests if negative arguments to skip are handled correctly.
528      *
529      * @throws Throwable
530      *             for some failure scenarios.
531      */
532     @Test
533     void testSkipWrongArgument() throws Throwable {
534         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
535         try (Base32InputStream b32stream = new Base32InputStream(ins)) {
536             assertThrows(IllegalArgumentException.class, () -> b32stream.skip(-10));
537         }
538     }
539 
540     /**
541      * Test strict decoding.
542      *
543      * @throws Exception
544      *             for some failure scenarios.
545      */
546     @Test
547     void testStrictDecoding() throws Exception {
548         for (final String s : Base32Test.BASE32_IMPOSSIBLE_CASES) {
549             final byte[] encoded = StringUtils.getBytesUtf8(s);
550             final Base32InputStream in = new Base32InputStream(new ByteArrayInputStream(encoded), false);
551             // Default is lenient decoding; it should not throw
552             assertFalse(in.isStrictDecoding());
553             IOUtils.toByteArray(in);
554             // Strict decoding should throw
555             final Base32InputStream in2 = new Base32InputStream(new ByteArrayInputStream(encoded), false, 0, null, CodecPolicy.STRICT);
556             assertTrue(in2.isStrictDecoding());
557             assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(in2));
558             // Same with a builder
559             try (Base32InputStream in3 = Base32InputStream.builder()
560                     .setByteArray(encoded)
561                     .setEncode(false)
562                     .setBaseNCodec(Base32.builder().setLineLength(0).setLineSeparator(null).setDecodingPolicy(CodecPolicy.STRICT).get())
563                     .get()) {
564                 assertTrue(in3.isStrictDecoding());
565                 assertThrows(IllegalArgumentException.class, () -> IOUtils.toByteArray(in3));
566             }
567         }
568     }
569 }