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