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 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 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(final byte[] encoded, final byte[] decoded, final int chunkSize, final 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(final byte[] encoded, final byte[] decoded, final int chunkSize, final 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 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 }