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