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 }