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