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