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