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