001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.binary;
019
020import static org.junit.Assert.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.ByteArrayInputStream;
027import java.io.ByteArrayOutputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.util.Arrays;
031
032import org.junit.Test;
033
034public class Base32InputStreamTest {
035
036    private static final String ENCODED_FOO = "MZXW6===";
037
038    private final static byte[] CRLF = { (byte) '\r', (byte) '\n' };
039
040    private final static byte[] LF = { (byte) '\n' };
041
042    private static final String STRING_FIXTURE = "Hello World";
043
044    /**
045     * Tests the problem reported in CODEC-130. Missing / wrong implementation of skip.
046     */
047    @Test
048    public void testCodec130() throws IOException {
049        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
050        final Base32OutputStream base32os = new Base32OutputStream(bos);
051
052        base32os.write(StringUtils.getBytesUtf8(STRING_FIXTURE));
053        base32os.close();
054
055        final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
056        final Base32InputStream ins = new Base32InputStream(bis);
057
058        // we skip the first character read from the reader
059        ins.skip(1);
060        final byte[] decodedBytes = Base32TestData.streamToBytes(ins, new byte[64]);
061        final String str = StringUtils.newStringUtf8(decodedBytes);
062
063        assertEquals(STRING_FIXTURE.substring(1), str);
064    }
065
066    /**
067     * Tests the bug reported in CODEC-105. Bad interactions with InputStream when reading one byte at a time.
068     */
069    @Test
070    public void testCodec105() throws IOException {
071        final Base32InputStream in = new Base32InputStream(new Codec105ErrorInputStream(), true, 0, null);
072        try {
073            for (int i = 0; i < 5; i++) {
074                in.read();
075            }
076        } finally {
077            in.close();
078        }
079    }
080
081    // /**
082    // * Test for the CODEC-101 bug: InputStream.read(byte[]) should never return 0
083    // * because Java's builtin InputStreamReader hates that.
084    // *
085    // * @throws Exception for some failure scenarios.
086    // */
087    // @Test
088    // public void testCodec101() throws Exception {
089    // byte[] codec101 = StringUtils.getBytesUtf8(Base32TestData.CODEC_101_MULTIPLE_OF_3);
090    // ByteArrayInputStream bais = new ByteArrayInputStream(codec101);
091    // Base32InputStream in = new Base32InputStream(bais);
092    // byte[] result = new byte[8192];
093    // int c = in.read(result);
094    // assertTrue("Codec101: First read successful [c=" + c + "]", c > 0);
095    //
096    // c = in.read(result);
097    // assertTrue("Codec101: Second read should report end-of-stream [c=" + c + "]", c < 0);
098    // }
099
100    /**
101     * Another test for the CODEC-101 bug: In commons-codec-1.4 this test shows InputStreamReader explicitly hating an
102     * InputStream.read(byte[]) return of 0:
103     *
104     * java.io.IOException: Underlying input stream returned zero bytes at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:268) at
105     * sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) at
106     * java.io.InputStreamReader.read(InputStreamReader.java:167) at java.io.BufferedReader.fill(BufferedReader.java:136) at
107     * java.io.BufferedReader.readLine(BufferedReader.java:299) at java.io.BufferedReader.readLine(BufferedReader.java:362) at
108     * org.apache.commons.codec.binary.Base32InputStreamTest.testInputStreamReader(Base32InputStreamTest.java:75)
109     *
110     * But in commons-codec-1.5 it's fixed. :-)
111     *
112     * @throws Exception
113     *             for some failure scenarios.
114     */
115    // @Test
116    // public void testInputStreamReader() throws Exception {
117    // byte[] codec101 = StringUtils.getBytesUtf8(Base32TestData.CODEC_101_MULTIPLE_OF_3);
118    // ByteArrayInputStream bais = new ByteArrayInputStream(codec101);
119    // Base32InputStream in = new Base32InputStream(bais);
120    // InputStreamReader isr = new InputStreamReader(in);
121    // BufferedReader br = new BufferedReader(isr);
122    // String line = br.readLine();
123    // assertNotNull("Codec101:  InputStreamReader works!", line);
124    // }
125
126    /**
127     * Test the Base32InputStream implementation against the special NPE inducing input identified in the CODEC-98 bug.
128     *
129     * @throws Exception
130     *             for some failure scenarios.
131     */
132    // @Test
133    // public void testCodec98NPE() throws Exception {
134    // byte[] codec98 = StringUtils.getBytesUtf8(Base32TestData.CODEC_98_NPE);
135    // ByteArrayInputStream data = new ByteArrayInputStream(codec98);
136    // Base32InputStream stream = new Base32InputStream(data);
137    //
138    // // This line causes an NPE in commons-codec-1.4.jar:
139    // byte[] decodedBytes = Base32TestData.streamToBytes(stream, new byte[1024]);
140    //
141    // String decoded = StringUtils.newStringUtf8(decodedBytes);
142    // assertEquals(
143    // "codec-98 NPE Base32InputStream", Base32TestData.CODEC_98_NPE_DECODED, decoded
144    // );
145    // }
146
147    /**
148     * Tests skipping past the end of a stream.
149     *
150     * @throws Throwable
151     */
152    @Test
153    public void testAvailable() throws Throwable {
154        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
155        final Base32InputStream b32stream = new Base32InputStream(ins);
156        assertEquals(1, b32stream.available());
157        assertEquals(3, b32stream.skip(10));
158        // End of stream reached
159        assertEquals(0, b32stream.available());
160        assertEquals(-1, b32stream.read());
161        assertEquals(-1, b32stream.read());
162        assertEquals(0, b32stream.available());
163        b32stream.close();
164    }
165
166    /**
167     * Tests the Base32InputStream implementation against empty input.
168     *
169     * @throws Exception
170     *             for some failure scenarios.
171     */
172    @Test
173    public void testBase32EmptyInputStreamMimeChuckSize() throws Exception {
174        testBase32EmptyInputStream(BaseNCodec.MIME_CHUNK_SIZE);
175    }
176
177    /**
178     * Tests the Base32InputStream implementation against empty input.
179     *
180     * @throws Exception
181     *             for some failure scenarios.
182     */
183    @Test
184    public void testBase32EmptyInputStreamPemChuckSize() throws Exception {
185        testBase32EmptyInputStream(BaseNCodec.PEM_CHUNK_SIZE);
186    }
187
188    private void testBase32EmptyInputStream(final int chuckSize) throws Exception {
189        final byte[] emptyEncoded = new byte[0];
190        final byte[] emptyDecoded = new byte[0];
191        testByteByByte(emptyEncoded, emptyDecoded, chuckSize, CRLF);
192        testByChunk(emptyEncoded, emptyDecoded, chuckSize, CRLF);
193    }
194
195    /**
196     * Tests the Base32InputStream implementation.
197     *
198     * @throws Exception
199     *             for some failure scenarios.
200     */
201    @Test
202    public void testBase32InputStreamByChunk() throws Exception {
203        // Hello World test.
204        byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
205        byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
206        testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
207
208        // Single Byte test.
209        encoded = StringUtils.getBytesUtf8("AA======\r\n");
210        decoded = new byte[] { (byte) 0 };
211        testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
212
213        // // OpenSSL interop test.
214        // encoded = StringUtils.getBytesUtf8(Base32TestData.ENCODED_32_CHARS_PER_LINE);
215        // decoded = Base32TestData.DECODED;
216        // testByChunk(encoded, decoded, Base32.PEM_CHUNK_SIZE, LF);
217        //
218        // // Single Line test.
219        // String singleLine = Base32TestData.ENCODED_32_CHARS_PER_LINE.replaceAll("\n", "");
220        // encoded = StringUtils.getBytesUtf8(singleLine);
221        // decoded = Base32TestData.DECODED;
222        // testByChunk(encoded, decoded, 0, LF);
223
224        // test random data of sizes 0 thru 150
225        final BaseNCodec codec = new Base32();
226        for (int i = 0; i <= 150; i++) {
227            final byte[][] randomData = Base32TestData.randomData(codec, i);
228            encoded = randomData[1];
229            decoded = randomData[0];
230            testByChunk(encoded, decoded, 0, LF);
231        }
232    }
233
234    /**
235     * Tests the Base32InputStream implementation.
236     *
237     * @throws Exception
238     *             for some failure scenarios.
239     */
240    @Test
241    public void testBase32InputStreamByteByByte() throws Exception {
242        // Hello World test.
243        byte[] encoded = StringUtils.getBytesUtf8(Base32TestData.BASE32_FIXTURE);
244        byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
245        testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
246
247        // Single Byte test.
248        encoded = StringUtils.getBytesUtf8("AA======\r\n");
249        decoded = new byte[] { (byte) 0 };
250        testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
251
252        // // Single Line test.
253        // String singleLine = Base32TestData.ENCODED_32_CHARS_PER_LINE.replaceAll("\n", "");
254        // encoded = StringUtils.getBytesUtf8(singleLine);
255        // decoded = Base32TestData.DECODED;
256        // testByteByByte(encoded, decoded, 0, LF);
257
258        // test random data of sizes 0 thru 150
259        final BaseNCodec codec = new Base32();
260        for (int i = 0; i <= 150; i++) {
261            final byte[][] randomData = Base32TestData.randomData(codec, i);
262            encoded = randomData[1];
263            decoded = randomData[0];
264            testByteByByte(encoded, decoded, 0, LF);
265        }
266    }
267
268    /**
269     * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
270     * ---[WRAP-WRAP-WRAP-etc...] --> decoded
271     * <p/>
272     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32InputStream wraps itself in encode and decode mode over and over
273     * again.
274     *
275     * @param encoded
276     *            base32 encoded data
277     * @param decoded
278     *            the data from above, but decoded
279     * @param chunkSize
280     *            chunk size (line-length) of the base32 encoded data.
281     * @param separator
282     *            Line separator in the base32 encoded data.
283     * @throws Exception
284     *             Usually signifies a bug in the Base32 commons-codec implementation.
285     */
286    private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
287
288        // Start with encode.
289        InputStream in;
290
291        in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, separator);
292        byte[] output = Base32TestData.streamToBytes(in);
293
294        assertEquals("EOF", -1, in.read());
295        assertEquals("Still EOF", -1, in.read());
296        assertTrue("Streaming base32 encode", Arrays.equals(output, encoded));
297
298        // Now let's try decode.
299        in = new Base32InputStream(new ByteArrayInputStream(encoded));
300        output = Base32TestData.streamToBytes(in);
301
302        assertEquals("EOF", -1, in.read());
303        assertEquals("Still EOF", -1, in.read());
304        assertTrue("Streaming base32 decode", Arrays.equals(output, decoded));
305
306        // I always wanted to do this! (wrap encoder with decoder etc etc).
307        in = new ByteArrayInputStream(decoded);
308        for (int i = 0; i < 10; i++) {
309            in = new Base32InputStream(in, true, chunkSize, separator);
310            in = new Base32InputStream(in, false);
311        }
312        output = Base32TestData.streamToBytes(in);
313
314        assertEquals("EOF", -1, in.read());
315        assertEquals("Still EOF", -1, in.read());
316        assertTrue("Streaming base32 wrap-wrap-wrap!", Arrays.equals(output, decoded));
317        in.close();
318    }
319
320    /**
321     * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
322     * ---[WRAP-WRAP-WRAP-etc...] --> decoded
323     * <p/>
324     * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base32InputStream wraps itself in encode and decode mode over and over
325     * again.
326     *
327     * @param encoded
328     *            base32 encoded data
329     * @param decoded
330     *            the data from above, but decoded
331     * @param chunkSize
332     *            chunk size (line-length) of the base32 encoded data.
333     * @param separator
334     *            Line separator in the base32 encoded data.
335     * @throws Exception
336     *             Usually signifies a bug in the Base32 commons-codec implementation.
337     */
338    private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
339
340        // Start with encode.
341        InputStream in;
342        in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, separator);
343        byte[] output = new byte[encoded.length];
344        for (int i = 0; i < output.length; i++) {
345            output[i] = (byte) in.read();
346        }
347
348        assertEquals("EOF", -1, in.read());
349        assertEquals("Still EOF", -1, in.read());
350        assertTrue("Streaming base32 encode", Arrays.equals(output, encoded));
351
352        in.close();
353
354        // Now let's try decode.
355        in = new Base32InputStream(new ByteArrayInputStream(encoded));
356        output = new byte[decoded.length];
357        for (int i = 0; i < output.length; i++) {
358            output[i] = (byte) in.read();
359        }
360
361        assertEquals("EOF", -1, in.read());
362        assertEquals("Still EOF", -1, in.read());
363        assertTrue("Streaming base32 decode", Arrays.equals(output, decoded));
364
365        in.close();
366
367        // I always wanted to do this! (wrap encoder with decoder etc etc).
368        in = new ByteArrayInputStream(decoded);
369        for (int i = 0; i < 10; i++) {
370            in = new Base32InputStream(in, true, chunkSize, separator);
371            in = new Base32InputStream(in, false);
372        }
373        output = new byte[decoded.length];
374        for (int i = 0; i < output.length; i++) {
375            output[i] = (byte) in.read();
376        }
377
378        assertEquals("EOF", -1, in.read());
379        assertEquals("Still EOF", -1, in.read());
380        assertTrue("Streaming base32 wrap-wrap-wrap!", Arrays.equals(output, decoded));
381    }
382
383    /**
384     * Tests markSupported.
385     *
386     * @throws Exception
387     */
388    @Test
389    public void testMarkSupported() throws Exception {
390        final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
391        final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
392        final Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 });
393        // Always returns false for now.
394        assertFalse("Base32InputStream.markSupported() is false", in.markSupported());
395        in.close();
396    }
397
398    /**
399     * Tests read returning 0
400     *
401     * @throws Exception
402     */
403    @Test
404    public void testRead0() throws Exception {
405        final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
406        final byte[] buf = new byte[1024];
407        int bytesRead = 0;
408        final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
409        final Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 });
410        bytesRead = in.read(buf, 0, 0);
411        assertEquals("Base32InputStream.read(buf, 0, 0) returns 0", 0, bytesRead);
412        in.close();
413    }
414
415    /**
416     * Tests read with null.
417     *
418     * @throws Exception
419     *             for some failure scenarios.
420     */
421    @Test
422    public void testReadNull() throws Exception {
423        final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
424        final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
425        final Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 });
426        try {
427            in.read(null, 0, 0);
428            fail("Base32InputStream.read(null, 0, 0) to throw a NullPointerException");
429        } catch (final NullPointerException e) {
430            // Expected
431        }
432        in.close();
433    }
434
435    /**
436     * Tests read throwing IndexOutOfBoundsException
437     *
438     * @throws Exception
439     */
440    @Test
441    public void testReadOutOfBounds() throws Exception {
442        final byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
443        final byte[] buf = new byte[1024];
444        final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
445        final Base32InputStream in = new Base32InputStream(bin, true, 4, new byte[] { 0, 0, 0 });
446
447        try {
448            in.read(buf, -1, 0);
449            fail("Expected Base32InputStream.read(buf, -1, 0) to throw IndexOutOfBoundsException");
450        } catch (final IndexOutOfBoundsException e) {
451            // Expected
452        }
453
454        try {
455            in.read(buf, 0, -1);
456            fail("Expected Base32InputStream.read(buf, 0, -1) to throw IndexOutOfBoundsException");
457        } catch (final IndexOutOfBoundsException e) {
458            // Expected
459        }
460
461        try {
462            in.read(buf, buf.length + 1, 0);
463            fail("Base32InputStream.read(buf, buf.length + 1, 0) throws IndexOutOfBoundsException");
464        } catch (final IndexOutOfBoundsException e) {
465            // Expected
466        }
467
468        try {
469            in.read(buf, buf.length - 1, 2);
470            fail("Base32InputStream.read(buf, buf.length - 1, 2) throws IndexOutOfBoundsException");
471        } catch (final IndexOutOfBoundsException e) {
472            // Expected
473        }
474        in.close();
475    }
476
477    /**
478     * Tests skipping as a noop
479     *
480     * @throws Throwable
481     */
482    @Test
483    public void testSkipNone() throws Throwable {
484        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
485        final Base32InputStream b32stream = new Base32InputStream(ins);
486        final byte[] actualBytes = new byte[6];
487        assertEquals(0, b32stream.skip(0));
488        b32stream.read(actualBytes, 0, actualBytes.length);
489        assertArrayEquals(actualBytes, new byte[] { 102, 111, 111, 0, 0, 0 });
490        // End of stream reached
491        assertEquals(-1, b32stream.read());
492        b32stream.close();
493    }
494
495    /**
496     * Tests skipping number of characters larger than the internal buffer.
497     *
498     * @throws Throwable
499     */
500    @Test
501    public void testSkipBig() throws Throwable {
502        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
503        final Base32InputStream b32stream = new Base32InputStream(ins);
504        assertEquals(3, b32stream.skip(1024));
505        // End of stream reached
506        assertEquals(-1, b32stream.read());
507        assertEquals(-1, b32stream.read());
508        b32stream.close();
509    }
510
511    /**
512     * Tests skipping past the end of a stream.
513     *
514     * @throws Throwable
515     */
516    @Test
517    public void testSkipPastEnd() throws Throwable {
518        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
519        final Base32InputStream b32stream = new Base32InputStream(ins);
520        // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
521        assertEquals(3, b32stream.skip(10));
522        // End of stream reached
523        assertEquals(-1, b32stream.read());
524        assertEquals(-1, b32stream.read());
525        b32stream.close();
526}
527
528    /**
529     * Tests skipping to the end of a stream.
530     *
531     * @throws Throwable
532     */
533    @Test
534    public void testSkipToEnd() throws Throwable {
535        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
536        final Base32InputStream b32stream = new Base32InputStream(ins);
537        // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
538        assertEquals(3, b32stream.skip(3));
539        // End of stream reached
540        assertEquals(-1, b32stream.read());
541        assertEquals(-1, b32stream.read());
542        b32stream.close();
543    }
544
545    /**
546     * Tests if negative arguments to skip are handled correctly.
547     *
548     * @throws Throwable
549     */
550    @Test(expected=IllegalArgumentException.class)
551    public void testSkipWrongArgument() throws Throwable {
552        final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
553        final Base32InputStream b32stream = new Base32InputStream(ins);
554        b32stream.skip(-10);
555        b32stream.close();
556    }
557}