1 /* 2 * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 4235519 8004212 8005394 8007298 8006295 8006315 8006530 8007379 8008925 27 * 8014217 8025003 8026330 8028397 8129544 8165243 8176379 28 * @summary tests java.util.Base64 29 * @library /test/lib 30 * @build jdk.test.lib.RandomFactory 31 * @run main TestBase64 32 * @key randomness 33 */ 34 35 import java.io.ByteArrayInputStream; 36 import java.io.ByteArrayOutputStream; 37 import java.io.InputStream; 38 import java.io.IOException; 39 import java.io.OutputStream; 40 import java.nio.ByteBuffer; 41 import java.util.Arrays; 42 import java.util.ArrayList; 43 import java.util.Base64; 44 import java.util.List; 45 import java.util.Random; 46 47 import jdk.test.lib.RandomFactory; 48 49 public class TestBase64 { 50 51 private static final Random rnd = RandomFactory.getRandom(); 52 53 public static void main(String args[]) throws Throwable { 54 int numRuns = 10; 55 int numBytes = 200; 56 if (args.length > 1) { 57 numRuns = Integer.parseInt(args[0]); 58 numBytes = Integer.parseInt(args[1]); 59 } 60 61 test(Base64.getEncoder(), Base64.getDecoder(), numRuns, numBytes); 62 test(Base64.getUrlEncoder(), Base64.getUrlDecoder(), numRuns, numBytes); 63 test(Base64.getMimeEncoder(), Base64.getMimeDecoder(), numRuns, numBytes); 64 65 byte[] nl_1 = new byte[] {'\n'}; 66 byte[] nl_2 = new byte[] {'\n', '\r'}; 67 byte[] nl_3 = new byte[] {'\n', '\r', '\n'}; 68 for (int i = 0; i < 10; i++) { 69 int len = rnd.nextInt(200) + 4; 70 test(Base64.getMimeEncoder(len, nl_1), 71 Base64.getMimeDecoder(), 72 numRuns, numBytes); 73 test(Base64.getMimeEncoder(len, nl_2), 74 Base64.getMimeDecoder(), 75 numRuns, numBytes); 76 test(Base64.getMimeEncoder(len, nl_3), 77 Base64.getMimeDecoder(), 78 numRuns, numBytes); 79 } 80 81 // test mime case with < 4 length 82 for (int len = 0; len < 4; len++) { 83 test(Base64.getMimeEncoder(len, nl_1), 84 Base64.getMimeDecoder(), 85 numRuns, numBytes); 86 87 test(Base64.getMimeEncoder(len, nl_2), 88 Base64.getMimeDecoder(), 89 numRuns, numBytes); 90 91 test(Base64.getMimeEncoder(len, nl_3), 92 Base64.getMimeDecoder(), 93 numRuns, numBytes); 94 } 95 96 testNull(Base64.getEncoder()); 97 testNull(Base64.getUrlEncoder()); 98 testNull(Base64.getMimeEncoder()); 99 testNull(Base64.getMimeEncoder(10, new byte[]{'\n'})); 100 testNull(Base64.getDecoder()); 101 testNull(Base64.getUrlDecoder()); 102 testNull(Base64.getMimeDecoder()); 103 checkNull(() -> Base64.getMimeEncoder(10, null)); 104 105 testIOE(Base64.getEncoder()); 106 testIOE(Base64.getUrlEncoder()); 107 testIOE(Base64.getMimeEncoder()); 108 testIOE(Base64.getMimeEncoder(10, new byte[]{'\n'})); 109 110 byte[] src = new byte[1024]; 111 rnd.nextBytes(src); 112 final byte[] decoded = Base64.getEncoder().encode(src); 113 testIOE(Base64.getDecoder(), decoded); 114 testIOE(Base64.getMimeDecoder(), decoded); 115 testIOE(Base64.getUrlDecoder(), Base64.getUrlEncoder().encode(src)); 116 117 // illegal line separator 118 checkIAE(() -> Base64.getMimeEncoder(10, new byte[]{'\r', 'N'})); 119 120 // malformed padding/ending 121 testMalformedPadding(); 122 123 // illegal base64 character 124 decoded[2] = (byte)0xe0; 125 checkIAE(() -> Base64.getDecoder().decode(decoded)); 126 checkIAE(() -> Base64.getDecoder().decode(decoded, new byte[1024])); 127 checkIAE(() -> Base64.getDecoder().decode(ByteBuffer.wrap(decoded))); 128 129 // test single-non-base64 character for mime decoding 130 testSingleNonBase64MimeDec(); 131 132 // test decoding of unpadded data 133 testDecodeUnpadded(); 134 135 // test mime decoding with ignored character after padding 136 testDecodeIgnoredAfterPadding(); 137 138 // given invalid args, encoder should not produce output 139 testEncoderKeepsSilence(Base64.getEncoder()); 140 testEncoderKeepsSilence(Base64.getUrlEncoder()); 141 testEncoderKeepsSilence(Base64.getMimeEncoder()); 142 143 // given invalid args, decoder should not consume input 144 testDecoderKeepsAbstinence(Base64.getDecoder()); 145 testDecoderKeepsAbstinence(Base64.getUrlDecoder()); 146 testDecoderKeepsAbstinence(Base64.getMimeDecoder()); 147 } 148 149 private static void test(Base64.Encoder enc, Base64.Decoder dec, 150 int numRuns, int numBytes) throws Throwable { 151 enc.encode(new byte[0]); 152 dec.decode(new byte[0]); 153 154 for (boolean withoutPadding : new boolean[] { false, true}) { 155 if (withoutPadding) { 156 enc = enc.withoutPadding(); 157 } 158 for (int i=0; i<numRuns; i++) { 159 for (int j=1; j<numBytes; j++) { 160 byte[] orig = new byte[j]; 161 rnd.nextBytes(orig); 162 163 // --------testing encode/decode(byte[])-------- 164 byte[] encoded = enc.encode(orig); 165 byte[] decoded = dec.decode(encoded); 166 167 checkEqual(orig, decoded, 168 "Base64 array encoding/decoding failed!"); 169 if (withoutPadding) { 170 if (encoded[encoded.length - 1] == '=') 171 throw new RuntimeException( 172 "Base64 enc.encode().withoutPadding() has padding!"); 173 } 174 // --------testing encodetoString(byte[])/decode(String)-------- 175 String str = enc.encodeToString(orig); 176 if (!Arrays.equals(str.getBytes("ASCII"), encoded)) { 177 throw new RuntimeException( 178 "Base64 encodingToString() failed!"); 179 } 180 byte[] buf = dec.decode(new String(encoded, "ASCII")); 181 checkEqual(buf, orig, "Base64 decoding(String) failed!"); 182 183 //-------- testing encode/decode(Buffer)-------- 184 testEncode(enc, ByteBuffer.wrap(orig), encoded); 185 ByteBuffer bin = ByteBuffer.allocateDirect(orig.length); 186 bin.put(orig).flip(); 187 testEncode(enc, bin, encoded); 188 189 testDecode(dec, ByteBuffer.wrap(encoded), orig); 190 bin = ByteBuffer.allocateDirect(encoded.length); 191 bin.put(encoded).flip(); 192 testDecode(dec, bin, orig); 193 194 // --------testing decode.wrap(input stream)-------- 195 // 1) random buf length 196 ByteArrayInputStream bais = new ByteArrayInputStream(encoded); 197 InputStream is = dec.wrap(bais); 198 buf = new byte[orig.length + 10]; 199 int len = orig.length; 200 int off = 0; 201 while (true) { 202 int n = rnd.nextInt(len); 203 if (n == 0) 204 n = 1; 205 n = is.read(buf, off, n); 206 if (n == -1) { 207 checkEqual(off, orig.length, 208 "Base64 stream decoding failed"); 209 break; 210 } 211 off += n; 212 len -= n; 213 if (len == 0) 214 break; 215 } 216 buf = Arrays.copyOf(buf, off); 217 checkEqual(buf, orig, "Base64 stream decoding failed!"); 218 219 // 2) read one byte each 220 bais.reset(); 221 is = dec.wrap(bais); 222 buf = new byte[orig.length + 10]; 223 off = 0; 224 int b; 225 while ((b = is.read()) != -1) { 226 buf[off++] = (byte)b; 227 } 228 buf = Arrays.copyOf(buf, off); 229 checkEqual(buf, orig, "Base64 stream decoding failed!"); 230 231 // --------testing encode.wrap(output stream)-------- 232 ByteArrayOutputStream baos = new ByteArrayOutputStream((orig.length + 2) / 3 * 4 + 10); 233 OutputStream os = enc.wrap(baos); 234 off = 0; 235 len = orig.length; 236 for (int k = 0; k < 5; k++) { 237 if (len == 0) 238 break; 239 int n = rnd.nextInt(len); 240 if (n == 0) 241 n = 1; 242 os.write(orig, off, n); 243 off += n; 244 len -= n; 245 } 246 if (len != 0) 247 os.write(orig, off, len); 248 os.close(); 249 buf = baos.toByteArray(); 250 checkEqual(buf, encoded, "Base64 stream encoding failed!"); 251 252 // 2) write one byte each 253 baos.reset(); 254 os = enc.wrap(baos); 255 off = 0; 256 while (off < orig.length) { 257 os.write(orig[off++]); 258 } 259 os.close(); 260 buf = baos.toByteArray(); 261 checkEqual(buf, encoded, "Base64 stream encoding failed!"); 262 263 // --------testing encode(in, out); -> bigger buf-------- 264 buf = new byte[encoded.length + rnd.nextInt(100)]; 265 int ret = enc.encode(orig, buf); 266 checkEqual(ret, encoded.length, 267 "Base64 enc.encode(src, null) returns wrong size!"); 268 buf = Arrays.copyOf(buf, ret); 269 checkEqual(buf, encoded, 270 "Base64 enc.encode(src, dst) failed!"); 271 272 // --------testing decode(in, out); -> bigger buf-------- 273 buf = new byte[orig.length + rnd.nextInt(100)]; 274 ret = dec.decode(encoded, buf); 275 checkEqual(ret, orig.length, 276 "Base64 enc.encode(src, null) returns wrong size!"); 277 buf = Arrays.copyOf(buf, ret); 278 checkEqual(buf, orig, 279 "Base64 dec.decode(src, dst) failed!"); 280 281 } 282 } 283 } 284 } 285 286 private static final byte[] ba_null = null; 287 private static final String str_null = null; 288 private static final ByteBuffer bb_null = null; 289 290 private static void testNull(Base64.Encoder enc) { 291 checkNull(() -> enc.encode(ba_null)); 292 checkNull(() -> enc.encodeToString(ba_null)); 293 checkNull(() -> enc.encode(ba_null, new byte[10])); 294 checkNull(() -> enc.encode(new byte[10], ba_null)); 295 checkNull(() -> enc.encode(bb_null)); 296 checkNull(() -> enc.wrap((OutputStream)null)); 297 } 298 299 private static void testNull(Base64.Decoder dec) { 300 checkNull(() -> dec.decode(ba_null)); 301 checkNull(() -> dec.decode(str_null)); 302 checkNull(() -> dec.decode(ba_null, new byte[10])); 303 checkNull(() -> dec.decode(new byte[10], ba_null)); 304 checkNull(() -> dec.decode(bb_null)); 305 checkNull(() -> dec.wrap((InputStream)null)); 306 } 307 308 @FunctionalInterface 309 private static interface Testable { 310 public void test() throws Throwable; 311 } 312 313 private static void testIOE(Base64.Encoder enc) throws Throwable { 314 ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); 315 OutputStream os = enc.wrap(baos); 316 os.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9}); 317 os.close(); 318 checkIOE(() -> os.write(10)); 319 checkIOE(() -> os.write(new byte[] {10})); 320 checkIOE(() -> os.write(new byte[] {10}, 1, 4)); 321 } 322 323 private static void testIOE(Base64.Decoder dec, byte[] decoded) throws Throwable { 324 ByteArrayInputStream bais = new ByteArrayInputStream(decoded); 325 InputStream is = dec.wrap(bais); 326 is.read(new byte[10]); 327 is.close(); 328 checkIOE(() -> is.read()); 329 checkIOE(() -> is.read(new byte[] {10})); 330 checkIOE(() -> is.read(new byte[] {10}, 1, 4)); 331 checkIOE(() -> is.available()); 332 checkIOE(() -> is.skip(20)); 333 } 334 335 private static final void checkNull(Runnable r) { 336 try { 337 r.run(); 338 throw new RuntimeException("NPE is not thrown as expected"); 339 } catch (NullPointerException npe) {} 340 } 341 342 private static final void checkIOE(Testable t) throws Throwable { 343 try { 344 t.test(); 345 throw new RuntimeException("IOE is not thrown as expected"); 346 } catch (IOException ioe) {} 347 } 348 349 private static final void checkIAE(Runnable r) throws Throwable { 350 try { 351 r.run(); 352 throw new RuntimeException("IAE is not thrown as expected"); 353 } catch (IllegalArgumentException iae) {} 354 } 355 356 private static void testDecodeIgnoredAfterPadding() throws Throwable { 357 for (byte nonBase64 : new byte[] {'#', '(', '!', '\\', '-', '_', '\n', '\r'}) { 358 byte[][] src = new byte[][] { 359 "A".getBytes("ascii"), 360 "AB".getBytes("ascii"), 361 "ABC".getBytes("ascii"), 362 "ABCD".getBytes("ascii"), 363 "ABCDE".getBytes("ascii") 364 }; 365 Base64.Encoder encM = Base64.getMimeEncoder(); 366 Base64.Decoder decM = Base64.getMimeDecoder(); 367 Base64.Encoder enc = Base64.getEncoder(); 368 Base64.Decoder dec = Base64.getDecoder(); 369 for (int i = 0; i < src.length; i++) { 370 // decode(byte[]) 371 byte[] encoded = encM.encode(src[i]); 372 encoded = Arrays.copyOf(encoded, encoded.length + 1); 373 encoded[encoded.length - 1] = nonBase64; 374 checkEqual(decM.decode(encoded), src[i], "Non-base64 char is not ignored"); 375 byte[] decoded = new byte[src[i].length]; 376 decM.decode(encoded, decoded); 377 checkEqual(decoded, src[i], "Non-base64 char is not ignored"); 378 379 try { 380 dec.decode(encoded); 381 throw new RuntimeException("No IAE for non-base64 char"); 382 } catch (IllegalArgumentException iae) {} 383 } 384 } 385 } 386 387 private static void testMalformedPadding() throws Throwable { 388 Object[] data = new Object[] { 389 "$=#", "", 0, // illegal ending unit 390 "A", "", 0, // dangling single byte 391 "A=", "", 0, 392 "A==", "", 0, 393 "QUJDA", "ABC", 4, 394 "QUJDA=", "ABC", 4, 395 "QUJDA==", "ABC", 4, 396 397 "=", "", 0, // unnecessary padding 398 "QUJD=", "ABC", 4, //"ABC".encode() -> "QUJD" 399 400 "AA=", "", 0, // incomplete padding 401 "QQ=", "", 0, 402 "QQ=N", "", 0, // incorrect padding 403 "QQ=?", "", 0, 404 "QUJDQQ=", "ABC", 4, 405 "QUJDQQ=N", "ABC", 4, 406 "QUJDQQ=?", "ABC", 4, 407 }; 408 409 Base64.Decoder[] decs = new Base64.Decoder[] { 410 Base64.getDecoder(), 411 Base64.getUrlDecoder(), 412 Base64.getMimeDecoder() 413 }; 414 415 for (Base64.Decoder dec : decs) { 416 for (int i = 0; i < data.length; i += 3) { 417 final String srcStr = (String)data[i]; 418 final byte[] srcBytes = srcStr.getBytes("ASCII"); 419 final ByteBuffer srcBB = ByteBuffer.wrap(srcBytes); 420 byte[] expected = ((String)data[i + 1]).getBytes("ASCII"); 421 int pos = (Integer)data[i + 2]; 422 423 // decode(byte[]) 424 checkIAE(() -> dec.decode(srcBytes)); 425 426 // decode(String) 427 checkIAE(() -> dec.decode(srcStr)); 428 429 // decode(ByteBuffer) 430 checkIAE(() -> dec.decode(srcBB)); 431 432 // wrap stream 433 checkIOE(new Testable() { 434 public void test() throws IOException { 435 try (InputStream is = dec.wrap(new ByteArrayInputStream(srcBytes))) { 436 while (is.read() != -1); 437 } 438 }}); 439 } 440 } 441 442 // anything left after padding is "invalid"/IAE, if 443 // not MIME. In case of MIME, non-base64 character(s) 444 // is ignored. 445 checkIAE(() -> Base64.getDecoder().decode("AA==\u00D2")); 446 checkIAE(() -> Base64.getUrlDecoder().decode("AA==\u00D2")); 447 Base64.getMimeDecoder().decode("AA==\u00D2"); 448 } 449 450 private static void testDecodeUnpadded() throws Throwable { 451 byte[] srcA = new byte[] { 'Q', 'Q' }; 452 byte[] srcAA = new byte[] { 'Q', 'Q', 'E'}; 453 Base64.Decoder dec = Base64.getDecoder(); 454 byte[] ret = dec.decode(srcA); 455 if (ret[0] != 'A') 456 throw new RuntimeException("Decoding unpadding input A failed"); 457 ret = dec.decode(srcAA); 458 if (ret[0] != 'A' && ret[1] != 'A') 459 throw new RuntimeException("Decoding unpadding input AA failed"); 460 ret = new byte[10]; 461 if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 1 && 462 ret[0] != 'A') 463 throw new RuntimeException("Decoding unpadding input A from stream failed"); 464 if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 2 && 465 ret[0] != 'A' && ret[1] != 'A') 466 throw new RuntimeException("Decoding unpadding input AA from stream failed"); 467 } 468 469 // single-non-base64-char should be ignored for mime decoding, but 470 // iae for basic decoding 471 private static void testSingleNonBase64MimeDec() throws Throwable { 472 for (String nonBase64 : new String[] {"#", "(", "!", "\\", "-", "_"}) { 473 if (Base64.getMimeDecoder().decode(nonBase64).length != 0) { 474 throw new RuntimeException("non-base64 char is not ignored"); 475 } 476 try { 477 Base64.getDecoder().decode(nonBase64); 478 throw new RuntimeException("No IAE for single non-base64 char"); 479 } catch (IllegalArgumentException iae) {} 480 } 481 } 482 483 private static final void testEncode(Base64.Encoder enc, ByteBuffer bin, byte[] expected) 484 throws Throwable { 485 486 ByteBuffer bout = enc.encode(bin); 487 byte[] buf = new byte[bout.remaining()]; 488 bout.get(buf); 489 if (bin.hasRemaining()) { 490 throw new RuntimeException( 491 "Base64 enc.encode(ByteBuffer) failed!"); 492 } 493 checkEqual(buf, expected, "Base64 enc.encode(bf, bf) failed!"); 494 } 495 496 private static final void testDecode(Base64.Decoder dec, ByteBuffer bin, byte[] expected) 497 throws Throwable { 498 499 ByteBuffer bout = dec.decode(bin); 500 byte[] buf = new byte[bout.remaining()]; 501 bout.get(buf); 502 checkEqual(buf, expected, "Base64 dec.decode(bf) failed!"); 503 } 504 505 private static final void checkEqual(int v1, int v2, String msg) 506 throws Throwable { 507 if (v1 != v2) { 508 System.out.printf(" v1=%d%n", v1); 509 System.out.printf(" v2=%d%n", v2); 510 throw new RuntimeException(msg); 511 } 512 } 513 514 private static final void checkEqual(byte[] r1, byte[] r2, String msg) 515 throws Throwable { 516 if (!Arrays.equals(r1, r2)) { 517 System.out.printf(" r1[%d]=[%s]%n", r1.length, new String(r1)); 518 System.out.printf(" r2[%d]=[%s]%n", r2.length, new String(r2)); 519 throw new RuntimeException(msg); 520 } 521 } 522 523 // remove line feeds, 524 private static final byte[] normalize(byte[] src) { 525 int n = 0; 526 boolean hasUrl = false; 527 for (int i = 0; i < src.length; i++) { 528 if (src[i] == '\r' || src[i] == '\n') 529 n++; 530 if (src[i] == '-' || src[i] == '_') 531 hasUrl = true; 532 } 533 if (n == 0 && hasUrl == false) 534 return src; 535 byte[] ret = new byte[src.length - n]; 536 int j = 0; 537 for (int i = 0; i < src.length; i++) { 538 if (src[i] == '-') 539 ret[j++] = '+'; 540 else if (src[i] == '_') 541 ret[j++] = '/'; 542 else if (src[i] != '\r' && src[i] != '\n') 543 ret[j++] = src[i]; 544 } 545 return ret; 546 } 547 548 private static void testEncoderKeepsSilence(Base64.Encoder enc) 549 throws Throwable { 550 List<Integer> vals = new ArrayList<>(List.of(Integer.MIN_VALUE, 551 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111, 552 Integer.MAX_VALUE - 1, Integer.MAX_VALUE)); 553 vals.addAll(List.of(rnd.nextInt(), rnd.nextInt(), rnd.nextInt(), 554 rnd.nextInt())); 555 byte[] buf = new byte[] {1, 0, 91}; 556 for (int off : vals) { 557 for (int len : vals) { 558 if (off >= 0 && len >= 0 && off <= buf.length - len) { 559 // valid args, skip them 560 continue; 561 } 562 // invalid args, test them 563 System.out.println("testing off=" + off + ", len=" + len); 564 565 ByteArrayOutputStream baos = new ByteArrayOutputStream(100); 566 try (OutputStream os = enc.wrap(baos)) { 567 os.write(buf, off, len); 568 throw new RuntimeException("Expected IOOBEx was not thrown"); 569 } catch (IndexOutOfBoundsException expected) { 570 } 571 if (baos.size() > 0) 572 throw new RuntimeException("No output was expected, but got " 573 + baos.size() + " bytes"); 574 } 575 } 576 } 577 578 private static void testDecoderKeepsAbstinence(Base64.Decoder dec) 579 throws Throwable { 580 List<Integer> vals = new ArrayList<>(List.of(Integer.MIN_VALUE, 581 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111, 582 Integer.MAX_VALUE - 1, Integer.MAX_VALUE)); 583 vals.addAll(List.of(rnd.nextInt(), rnd.nextInt(), rnd.nextInt(), 584 rnd.nextInt())); 585 byte[] buf = new byte[3]; 586 for (int off : vals) { 587 for (int len : vals) { 588 if (off >= 0 && len >= 0 && off <= buf.length - len) { 589 // valid args, skip them 590 continue; 591 } 592 // invalid args, test them 593 System.out.println("testing off=" + off + ", len=" + len); 594 595 String input = "AAAAAAAAAAAAAAAAAAAAAA"; 596 ByteArrayInputStream bais = 597 new ByteArrayInputStream(input.getBytes("Latin1")); 598 try (InputStream is = dec.wrap(bais)) { 599 is.read(buf, off, len); 600 throw new RuntimeException("Expected IOOBEx was not thrown"); 601 } catch (IndexOutOfBoundsException expected) { 602 } 603 if (bais.available() != input.length()) 604 throw new RuntimeException("No input should be consumed, " 605 + "but consumed " + (input.length() - bais.available()) 606 + " bytes"); 607 } 608 } 609 } 610 }