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    
018    package org.apache.commons.codec.binary;
019    
020    import static org.junit.Assert.assertArrayEquals;
021    import static org.junit.Assert.assertEquals;
022    import static org.junit.Assert.assertFalse;
023    import static org.junit.Assert.assertTrue;
024    import static org.junit.Assert.fail;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.ByteArrayOutputStream;
028    import java.io.IOException;
029    import java.io.InputStream;
030    import java.util.Arrays;
031    
032    import org.junit.Test;
033    
034    public 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            ByteArrayOutputStream bos = new ByteArrayOutputStream();
050            Base32OutputStream base32os = new Base32OutputStream(bos);
051    
052            base32os.write(StringUtils.getBytesUtf8(STRING_FIXTURE));
053            base32os.close();
054    
055            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
056            Base32InputStream ins = new Base32InputStream(bis);
057    
058            // we skip the first character read from the reader
059            ins.skip(1);
060            byte[] decodedBytes = Base32TestData.streamToBytes(ins, new byte[64]);
061            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            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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
155            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(int chuckSize) throws Exception {
189            byte[] emptyEncoded = new byte[0];
190            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            BaseNCodec codec = new Base32();
226            for (int i = 0; i <= 150; i++) {
227                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            BaseNCodec codec = new Base32();
260            for (int i = 0; i <= 150; i++) {
261                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 seperator
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(byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator) throws Exception {
287    
288            // Start with encode.
289            InputStream in;
290            
291            in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, seperator);
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, seperator);
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 seperator
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(byte[] encoded, byte[] decoded, int chunkSize, byte[] seperator) throws Exception {
339    
340            // Start with encode.
341            InputStream in;
342            in = new Base32InputStream(new ByteArrayInputStream(decoded), true, chunkSize, seperator);
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, seperator);
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            byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
391            ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
392            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            byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
406            byte[] buf = new byte[1024];
407            int bytesRead = 0;
408            ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
409            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            byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
424            ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
425            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 (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            byte[] decoded = StringUtils.getBytesUtf8(Base32TestData.STRING_FIXTURE);
443            byte[] buf = new byte[1024];
444            ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
445            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 (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 (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 (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 (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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
485            Base32InputStream b32stream = new Base32InputStream(ins);
486            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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
503            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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
519            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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
536            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            InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_FOO));
553            Base32InputStream b32stream = new Base32InputStream(ins);
554            b32stream.skip(-10);
555            b32stream.close();
556        }
557    }