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