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