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 }