1 /*
   2  * Copyright (c) 2012, 2016, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util;
  27 
  28 import java.io.FilterOutputStream;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.io.OutputStream;
  32 import java.nio.ByteBuffer;
  33 import java.nio.charset.StandardCharsets;
  34 
  35 /**
  36  * This class consists exclusively of static methods for obtaining
  37  * encoders and decoders for the Base64 encoding scheme. The
  38  * implementation of this class supports the following types of Base64
  39  * as specified in
  40  * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
  41  * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
  42  *
  43  * <ul>
  44  * <li><a id="basic"><b>Basic</b></a>
  45  * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
  46  *     RFC 4648 and RFC 2045 for encoding and decoding operation.
  47  *     The encoder does not add any line feed (line separator)
  48  *     character. The decoder rejects data that contains characters
  49  *     outside the base64 alphabet.</p></li>
  50  *
  51  * <li><a id="url"><b>URL and Filename safe</b></a>
  52  * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
  53  *     in Table 2 of RFC 4648 for encoding and decoding. The
  54  *     encoder does not add any line feed (line separator) character.
  55  *     The decoder rejects data that contains characters outside the
  56  *     base64 alphabet.</p></li>
  57  *
  58  * <li><a id="mime"><b>MIME</b></a>
  59  * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
  60  *     RFC 2045 for encoding and decoding operation. The encoded output
  61  *     must be represented in lines of no more than 76 characters each
  62  *     and uses a carriage return {@code '\r'} followed immediately by
  63  *     a linefeed {@code '\n'} as the line separator. No line separator
  64  *     is added to the end of the encoded output. All line separators
  65  *     or other characters not found in the base64 alphabet table are
  66  *     ignored in decoding operation.</p></li>
  67  * </ul>
  68  *
  69  * <p> Unless otherwise noted, passing a {@code null} argument to a
  70  * method of this class will cause a {@link java.lang.NullPointerException
  71  * NullPointerException} to be thrown.
  72  *
  73  * @author  Xueming Shen
  74  * @since   1.8
  75  */
  76 
  77 public class Base64 {
  78 
  79     private Base64() {}
  80 
  81     /**
  82      * Returns a {@link Encoder} that encodes using the
  83      * <a href="#basic">Basic</a> type base64 encoding scheme.
  84      *
  85      * @return  A Base64 encoder.
  86      */
  87     public static Encoder getEncoder() {
  88          return Encoder.RFC4648;
  89     }
  90 
  91     /**
  92      * Returns a {@link Encoder} that encodes using the
  93      * <a href="#url">URL and Filename safe</a> type base64
  94      * encoding scheme.
  95      *
  96      * @return  A Base64 encoder.
  97      */
  98     public static Encoder getUrlEncoder() {
  99          return Encoder.RFC4648_URLSAFE;
 100     }
 101 
 102     /**
 103      * Returns a {@link Encoder} that encodes using the
 104      * <a href="#mime">MIME</a> type base64 encoding scheme.
 105      *
 106      * @return  A Base64 encoder.
 107      */
 108     public static Encoder getMimeEncoder() {
 109         return Encoder.RFC2045;
 110     }
 111 
 112     /**
 113      * Returns a {@link Encoder} that encodes using the
 114      * <a href="#mime">MIME</a> type base64 encoding scheme
 115      * with specified line length and line separators.
 116      *
 117      * @param   lineLength
 118      *          the length of each output line (rounded down to nearest multiple
 119      *          of 4). If the rounded down line length is not a positive value,
 120      *          the output will not be separated in lines
 121      * @param   lineSeparator
 122      *          the line separator for each output line
 123      *
 124      * @return  A Base64 encoder.
 125      *
 126      * @throws  IllegalArgumentException if {@code lineSeparator} includes any
 127      *          character of "The Base64 Alphabet" as specified in Table 1 of
 128      *          RFC 2045.
 129      */
 130     public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
 131          Objects.requireNonNull(lineSeparator);
 132          int[] base64 = Decoder.fromBase64;
 133          for (byte b : lineSeparator) {
 134              if (base64[b & 0xff] != -1)
 135                  throw new IllegalArgumentException(
 136                      "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
 137          }
 138          // round down to nearest multiple of 4
 139          lineLength &= ~0b11;
 140          if (lineLength <= 0) {
 141              return Encoder.RFC4648;
 142          }
 143          return new Encoder(false, lineSeparator, lineLength, true);
 144     }
 145 
 146     /**
 147      * Returns a {@link Decoder} that decodes using the
 148      * <a href="#basic">Basic</a> type base64 encoding scheme.
 149      *
 150      * @return  A Base64 decoder.
 151      */
 152     public static Decoder getDecoder() {
 153          return Decoder.RFC4648;
 154     }
 155 
 156     /**
 157      * Returns a {@link Decoder} that decodes using the
 158      * <a href="#url">URL and Filename safe</a> type base64
 159      * encoding scheme.
 160      *
 161      * @return  A Base64 decoder.
 162      */
 163     public static Decoder getUrlDecoder() {
 164          return Decoder.RFC4648_URLSAFE;
 165     }
 166 
 167     /**
 168      * Returns a {@link Decoder} that decodes using the
 169      * <a href="#mime">MIME</a> type base64 decoding scheme.
 170      *
 171      * @return  A Base64 decoder.
 172      */
 173     public static Decoder getMimeDecoder() {
 174          return Decoder.RFC2045;
 175     }
 176 
 177     /**
 178      * This class implements an encoder for encoding byte data using
 179      * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 180      *
 181      * <p> Instances of {@link Encoder} class are safe for use by
 182      * multiple concurrent threads.
 183      *
 184      * <p> Unless otherwise noted, passing a {@code null} argument to
 185      * a method of this class will cause a
 186      * {@link java.lang.NullPointerException NullPointerException} to
 187      * be thrown.
 188      *
 189      * @see     Decoder
 190      * @since   1.8
 191      */
 192     public static class Encoder {
 193 
 194         private final byte[] newline;
 195         private final int linemax;
 196         private final boolean isURL;
 197         private final boolean doPadding;
 198 
 199         private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
 200             this.isURL = isURL;
 201             this.newline = newline;
 202             this.linemax = linemax;
 203             this.doPadding = doPadding;
 204         }
 205 
 206         /**
 207          * This array is a lookup table that translates 6-bit positive integer
 208          * index values into their "Base64 Alphabet" equivalents as specified
 209          * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
 210          */
 211         private static final char[] toBase64 = {
 212             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 213             'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 214             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 215             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 216             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
 217         };
 218 
 219         /**
 220          * It's the lookup table for "URL and Filename safe Base64" as specified
 221          * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
 222          * '_'. This table is used when BASE64_URL is specified.
 223          */
 224         private static final char[] toBase64URL = {
 225             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 226             'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 227             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 228             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 229             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
 230         };
 231 
 232         private static final int MIMELINEMAX = 76;
 233         private static final byte[] CRLF = new byte[] {'\r', '\n'};
 234 
 235         static final Encoder RFC4648 = new Encoder(false, null, -1, true);
 236         static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
 237         static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
 238 
 239         private final int outLength(int srclen) {
 240             int len = 0;
 241             if (doPadding) {
 242                 len = 4 * ((srclen + 2) / 3);
 243             } else {
 244                 int n = srclen % 3;
 245                 len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
 246             }
 247             if (linemax > 0)                                  // line separators
 248                 len += (len - 1) / linemax * newline.length;
 249             return len;
 250         }
 251 
 252         /**
 253          * Encodes all bytes from the specified byte array into a newly-allocated
 254          * byte array using the {@link Base64} encoding scheme. The returned byte
 255          * array is of the length of the resulting bytes.
 256          *
 257          * @param   src
 258          *          the byte array to encode
 259          * @return  A newly-allocated byte array containing the resulting
 260          *          encoded bytes.
 261          */
 262         public byte[] encode(byte[] src) {
 263             int len = outLength(src.length);          // dst array size
 264             byte[] dst = new byte[len];
 265             int ret = encode0(src, 0, src.length, dst);
 266             if (ret != dst.length)
 267                  return Arrays.copyOf(dst, ret);
 268             return dst;
 269         }
 270 
 271         /**
 272          * Encodes all bytes from the specified byte array using the
 273          * {@link Base64} encoding scheme, writing the resulting bytes to the
 274          * given output byte array, starting at offset 0.
 275          *
 276          * <p> It is the responsibility of the invoker of this method to make
 277          * sure the output byte array {@code dst} has enough space for encoding
 278          * all bytes from the input byte array. No bytes will be written to the
 279          * output byte array if the output byte array is not big enough.
 280          *
 281          * @param   src
 282          *          the byte array to encode
 283          * @param   dst
 284          *          the output byte array
 285          * @return  The number of bytes written to the output byte array
 286          *
 287          * @throws  IllegalArgumentException if {@code dst} does not have enough
 288          *          space for encoding all input bytes.
 289          */
 290         public int encode(byte[] src, byte[] dst) {
 291             int len = outLength(src.length);         // dst array size
 292             if (dst.length < len)
 293                 throw new IllegalArgumentException(
 294                     "Output byte array is too small for encoding all input bytes");
 295             return encode0(src, 0, src.length, dst);
 296         }
 297 
 298         /**
 299          * Encodes the specified byte array into a String using the {@link Base64}
 300          * encoding scheme.
 301          *
 302          * <p> This method first encodes all input bytes into a base64 encoded
 303          * byte array and then constructs a new String by using the encoded byte
 304          * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
 305          * ISO-8859-1} charset.
 306          *
 307          * <p> In other words, an invocation of this method has exactly the same
 308          * effect as invoking
 309          * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
 310          *
 311          * @param   src
 312          *          the byte array to encode
 313          * @return  A String containing the resulting Base64 encoded characters
 314          */
 315         @SuppressWarnings("deprecation")
 316         public String encodeToString(byte[] src) {
 317             byte[] encoded = encode(src);
 318             return new String(encoded, 0, 0, encoded.length);
 319         }
 320 
 321         /**
 322          * Encodes all remaining bytes from the specified byte buffer into
 323          * a newly-allocated ByteBuffer using the {@link Base64} encoding
 324          * scheme.
 325          *
 326          * Upon return, the source buffer's position will be updated to
 327          * its limit; its limit will not have been changed. The returned
 328          * output buffer's position will be zero and its limit will be the
 329          * number of resulting encoded bytes.
 330          *
 331          * @param   buffer
 332          *          the source ByteBuffer to encode
 333          * @return  A newly-allocated byte buffer containing the encoded bytes.
 334          */
 335         public ByteBuffer encode(ByteBuffer buffer) {
 336             int len = outLength(buffer.remaining());
 337             byte[] dst = new byte[len];
 338             int ret = 0;
 339             if (buffer.hasArray()) {
 340                 ret = encode0(buffer.array(),
 341                               buffer.arrayOffset() + buffer.position(),
 342                               buffer.arrayOffset() + buffer.limit(),
 343                               dst);
 344                 buffer.position(buffer.limit());
 345             } else {
 346                 byte[] src = new byte[buffer.remaining()];
 347                 buffer.get(src);
 348                 ret = encode0(src, 0, src.length, dst);
 349             }
 350             if (ret != dst.length)
 351                  dst = Arrays.copyOf(dst, ret);
 352             return ByteBuffer.wrap(dst);
 353         }
 354 
 355         /**
 356          * Wraps an output stream for encoding byte data using the {@link Base64}
 357          * encoding scheme.
 358          *
 359          * <p> It is recommended to promptly close the returned output stream after
 360          * use, during which it will flush all possible leftover bytes to the underlying
 361          * output stream. Closing the returned output stream will close the underlying
 362          * output stream.
 363          *
 364          * @param   os
 365          *          the output stream.
 366          * @return  the output stream for encoding the byte data into the
 367          *          specified Base64 encoded format
 368          */
 369         public OutputStream wrap(OutputStream os) {
 370             Objects.requireNonNull(os);
 371             return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
 372                                        newline, linemax, doPadding);
 373         }
 374 
 375         /**
 376          * Returns an encoder instance that encodes equivalently to this one,
 377          * but without adding any padding character at the end of the encoded
 378          * byte data.
 379          *
 380          * <p> The encoding scheme of this encoder instance is unaffected by
 381          * this invocation. The returned encoder instance should be used for
 382          * non-padding encoding operation.
 383          *
 384          * @return an equivalent encoder that encodes without adding any
 385          *         padding character at the end
 386          */
 387         public Encoder withoutPadding() {
 388             if (!doPadding)
 389                 return this;
 390             return new Encoder(isURL, newline, linemax, false);
 391         }
 392 
 393         private int encode0(byte[] src, int off, int end, byte[] dst) {
 394             char[] base64 = isURL ? toBase64URL : toBase64;
 395             int sp = off;
 396             int slen = (end - off) / 3 * 3;
 397             int sl = off + slen;
 398             if (linemax > 0 && slen  > linemax / 4 * 3)
 399                 slen = linemax / 4 * 3;
 400             int dp = 0;
 401             while (sp < sl) {
 402                 int sl0 = Math.min(sp + slen, sl);
 403                 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 404                     int bits = (src[sp0++] & 0xff) << 16 |
 405                                (src[sp0++] & 0xff) <<  8 |
 406                                (src[sp0++] & 0xff);
 407                     dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 408                     dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 409                     dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 410                     dst[dp0++] = (byte)base64[bits & 0x3f];
 411                 }
 412                 int dlen = (sl0 - sp) / 3 * 4;
 413                 dp += dlen;
 414                 sp = sl0;
 415                 if (dlen == linemax && sp < end) {
 416                     for (byte b : newline){
 417                         dst[dp++] = b;
 418                     }
 419                 }
 420             }
 421             if (sp < end) {               // 1 or 2 leftover bytes
 422                 int b0 = src[sp++] & 0xff;
 423                 dst[dp++] = (byte)base64[b0 >> 2];
 424                 if (sp == end) {
 425                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 426                     if (doPadding) {
 427                         dst[dp++] = '=';
 428                         dst[dp++] = '=';
 429                     }
 430                 } else {
 431                     int b1 = src[sp++] & 0xff;
 432                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 433                     dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 434                     if (doPadding) {
 435                         dst[dp++] = '=';
 436                     }
 437                 }
 438             }
 439             return dp;
 440         }
 441     }
 442 
 443     /**
 444      * This class implements a decoder for decoding byte data using the
 445      * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 446      *
 447      * <p> The Base64 padding character {@code '='} is accepted and
 448      * interpreted as the end of the encoded byte data, but is not
 449      * required. So if the final unit of the encoded byte data only has
 450      * two or three Base64 characters (without the corresponding padding
 451      * character(s) padded), they are decoded as if followed by padding
 452      * character(s). If there is a padding character present in the
 453      * final unit, the correct number of padding character(s) must be
 454      * present, otherwise {@code IllegalArgumentException} (
 455      * {@code IOException} when reading from a Base64 stream) is thrown
 456      * during decoding.
 457      *
 458      * <p> Instances of {@link Decoder} class are safe for use by
 459      * multiple concurrent threads.
 460      *
 461      * <p> Unless otherwise noted, passing a {@code null} argument to
 462      * a method of this class will cause a
 463      * {@link java.lang.NullPointerException NullPointerException} to
 464      * be thrown.
 465      *
 466      * @see     Encoder
 467      * @since   1.8
 468      */
 469     public static class Decoder {
 470 
 471         private final boolean isURL;
 472         private final boolean isMIME;
 473 
 474         private Decoder(boolean isURL, boolean isMIME) {
 475             this.isURL = isURL;
 476             this.isMIME = isMIME;
 477         }
 478 
 479         /**
 480          * Lookup table for decoding unicode characters drawn from the
 481          * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
 482          * their 6-bit positive integer equivalents.  Characters that
 483          * are not in the Base64 alphabet but fall within the bounds of
 484          * the array are encoded to -1.
 485          *
 486          */
 487         private static final int[] fromBase64 = new int[256];
 488         static {
 489             Arrays.fill(fromBase64, -1);
 490             for (int i = 0; i < Encoder.toBase64.length; i++)
 491                 fromBase64[Encoder.toBase64[i]] = i;
 492             fromBase64['='] = -2;
 493         }
 494 
 495         /**
 496          * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
 497          * as specified in Table2 of the RFC 4648.
 498          */
 499         private static final int[] fromBase64URL = new int[256];
 500 
 501         static {
 502             Arrays.fill(fromBase64URL, -1);
 503             for (int i = 0; i < Encoder.toBase64URL.length; i++)
 504                 fromBase64URL[Encoder.toBase64URL[i]] = i;
 505             fromBase64URL['='] = -2;
 506         }
 507 
 508         static final Decoder RFC4648         = new Decoder(false, false);
 509         static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
 510         static final Decoder RFC2045         = new Decoder(false, true);
 511 
 512         /**
 513          * Decodes all bytes from the input byte array using the {@link Base64}
 514          * encoding scheme, writing the results into a newly-allocated output
 515          * byte array. The returned byte array is of the length of the resulting
 516          * bytes.
 517          *
 518          * @param   src
 519          *          the byte array to decode
 520          *
 521          * @return  A newly-allocated byte array containing the decoded bytes.
 522          *
 523          * @throws  IllegalArgumentException
 524          *          if {@code src} is not in valid Base64 scheme
 525          */
 526         public byte[] decode(byte[] src) {
 527             byte[] dst = new byte[outLength(src, 0, src.length)];
 528             int ret = decode0(src, 0, src.length, dst);
 529             if (ret != dst.length) {
 530                 dst = Arrays.copyOf(dst, ret);
 531             }
 532             return dst;
 533         }
 534 
 535         /**
 536          * Decodes a Base64 encoded String into a newly-allocated byte array
 537          * using the {@link Base64} encoding scheme.
 538          *
 539          * <p> An invocation of this method has exactly the same effect as invoking
 540          * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
 541          *
 542          * @param   src
 543          *          the string to decode
 544          *
 545          * @return  A newly-allocated byte array containing the decoded bytes.
 546          *
 547          * @throws  IllegalArgumentException
 548          *          if {@code src} is not in valid Base64 scheme
 549          */
 550         public byte[] decode(String src) {
 551             return decode(src.getBytes(StandardCharsets.ISO_8859_1));
 552         }
 553 
 554         /**
 555          * Decodes all bytes from the input byte array using the {@link Base64}
 556          * encoding scheme, writing the results into the given output byte array,
 557          * starting at offset 0.
 558          *
 559          * <p> It is the responsibility of the invoker of this method to make
 560          * sure the output byte array {@code dst} has enough space for decoding
 561          * all bytes from the input byte array. No bytes will be written to
 562          * the output byte array if the output byte array is not big enough.
 563          *
 564          * <p> If the input byte array is not in valid Base64 encoding scheme
 565          * then some bytes may have been written to the output byte array before
 566          * IllegalargumentException is thrown.
 567          *
 568          * @param   src
 569          *          the byte array to decode
 570          * @param   dst
 571          *          the output byte array
 572          *
 573          * @return  The number of bytes written to the output byte array
 574          *
 575          * @throws  IllegalArgumentException
 576          *          if {@code src} is not in valid Base64 scheme, or {@code dst}
 577          *          does not have enough space for decoding all input bytes.
 578          */
 579         public int decode(byte[] src, byte[] dst) {
 580             int len = outLength(src, 0, src.length);
 581             if (dst.length < len)
 582                 throw new IllegalArgumentException(
 583                     "Output byte array is too small for decoding all input bytes");
 584             return decode0(src, 0, src.length, dst);
 585         }
 586 
 587         /**
 588          * Decodes all bytes from the input byte buffer using the {@link Base64}
 589          * encoding scheme, writing the results into a newly-allocated ByteBuffer.
 590          *
 591          * <p> Upon return, the source buffer's position will be updated to
 592          * its limit; its limit will not have been changed. The returned
 593          * output buffer's position will be zero and its limit will be the
 594          * number of resulting decoded bytes
 595          *
 596          * <p> {@code IllegalArgumentException} is thrown if the input buffer
 597          * is not in valid Base64 encoding scheme. The position of the input
 598          * buffer will not be advanced in this case.
 599          *
 600          * @param   buffer
 601          *          the ByteBuffer to decode
 602          *
 603          * @return  A newly-allocated byte buffer containing the decoded bytes
 604          *
 605          * @throws  IllegalArgumentException
 606          *          if {@code src} is not in valid Base64 scheme.
 607          */
 608         public ByteBuffer decode(ByteBuffer buffer) {
 609             int pos0 = buffer.position();
 610             try {
 611                 byte[] src;
 612                 int sp, sl;
 613                 if (buffer.hasArray()) {
 614                     src = buffer.array();
 615                     sp = buffer.arrayOffset() + buffer.position();
 616                     sl = buffer.arrayOffset() + buffer.limit();
 617                     buffer.position(buffer.limit());
 618                 } else {
 619                     src = new byte[buffer.remaining()];
 620                     buffer.get(src);
 621                     sp = 0;
 622                     sl = src.length;
 623                 }
 624                 byte[] dst = new byte[outLength(src, sp, sl)];
 625                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
 626             } catch (IllegalArgumentException iae) {
 627                 buffer.position(pos0);
 628                 throw iae;
 629             }
 630         }
 631 
 632         /**
 633          * Returns an input stream for decoding {@link Base64} encoded byte stream.
 634          *
 635          * <p> The {@code read}  methods of the returned {@code InputStream} will
 636          * throw {@code IOException} when reading bytes that cannot be decoded.
 637          *
 638          * <p> Closing the returned input stream will close the underlying
 639          * input stream.
 640          *
 641          * @param   is
 642          *          the input stream
 643          *
 644          * @return  the input stream for decoding the specified Base64 encoded
 645          *          byte stream
 646          */
 647         public InputStream wrap(InputStream is) {
 648             Objects.requireNonNull(is);
 649             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 650         }
 651 
 652         private int outLength(byte[] src, int sp, int sl) {
 653             int[] base64 = isURL ? fromBase64URL : fromBase64;
 654             int paddings = 0;
 655             int len = sl - sp;
 656             if (len == 0)
 657                 return 0;
 658             if (len < 2) {
 659                 if (isMIME && base64[0] == -1)
 660                     return 0;
 661                 throw new IllegalArgumentException(
 662                     "Input byte[] should at least have 2 bytes for base64 bytes");
 663             }
 664             if (isMIME) {
 665                 // scan all bytes to fill out all non-alphabet. a performance
 666                 // trade-off of pre-scan or Arrays.copyOf
 667                 int n = 0;
 668                 while (sp < sl) {
 669                     int b = src[sp++] & 0xff;
 670                     if (b == '=') {
 671                         len -= (sl - sp + 1);
 672                         break;
 673                     }
 674                     if ((b = base64[b]) == -1)
 675                         n++;
 676                 }
 677                 len -= n;
 678             } else {
 679                 if (src[sl - 1] == '=') {
 680                     paddings++;
 681                     if (src[sl - 2] == '=')
 682                         paddings++;
 683                 }
 684             }
 685             if (paddings == 0 && (len & 0x3) !=  0)
 686                 paddings = 4 - (len & 0x3);
 687             return 3 * ((len + 3) / 4) - paddings;
 688         }
 689 
 690         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
 691             int[] base64 = isURL ? fromBase64URL : fromBase64;
 692             int dp = 0;
 693             int bits = 0;
 694             int shiftto = 18;       // pos of first byte of 4-byte atom
 695             while (sp < sl) {
 696                 int b = src[sp++] & 0xff;
 697                 if ((b = base64[b]) < 0) {
 698                     if (b == -2) {         // padding byte '='
 699                         // =     shiftto==18 unnecessary padding
 700                         // x=    shiftto==12 a dangling single x
 701                         // x     to be handled together with non-padding case
 702                         // xx=   shiftto==6&&sp==sl missing last =
 703                         // xx=y  shiftto==6 last is not =
 704                         if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
 705                             shiftto == 18) {
 706                             throw new IllegalArgumentException(
 707                                 "Input byte array has wrong 4-byte ending unit");
 708                         }
 709                         break;
 710                     }
 711                     if (isMIME)    // skip if for rfc2045
 712                         continue;
 713                     else
 714                         throw new IllegalArgumentException(
 715                             "Illegal base64 character " +
 716                             Integer.toString(src[sp - 1], 16));
 717                 }
 718                 bits |= (b << shiftto);
 719                 shiftto -= 6;
 720                 if (shiftto < 0) {
 721                     dst[dp++] = (byte)(bits >> 16);
 722                     dst[dp++] = (byte)(bits >>  8);
 723                     dst[dp++] = (byte)(bits);
 724                     shiftto = 18;
 725                     bits = 0;
 726                 }
 727             }
 728             // reached end of byte array or hit padding '=' characters.
 729             if (shiftto == 6) {
 730                 dst[dp++] = (byte)(bits >> 16);
 731             } else if (shiftto == 0) {
 732                 dst[dp++] = (byte)(bits >> 16);
 733                 dst[dp++] = (byte)(bits >>  8);
 734             } else if (shiftto == 12) {
 735                 // dangling single "x", incorrectly encoded.
 736                 throw new IllegalArgumentException(
 737                     "Last unit does not have enough valid bits");
 738             }
 739             // anything left is invalid, if is not MIME.
 740             // if MIME, ignore all non-base64 character
 741             while (sp < sl) {
 742                 if (isMIME && base64[src[sp++] & 0xff] < 0)
 743                     continue;
 744                 throw new IllegalArgumentException(
 745                     "Input byte array has incorrect ending byte at " + sp);
 746             }
 747             return dp;
 748         }
 749     }
 750 
 751     /*
 752      * An output stream for encoding bytes into the Base64.
 753      */
 754     private static class EncOutputStream extends FilterOutputStream {
 755 
 756         private int leftover = 0;
 757         private int b0, b1, b2;
 758         private boolean closed = false;
 759 
 760         private final char[] base64;    // byte->base64 mapping
 761         private final byte[] newline;   // line separator, if needed
 762         private final int linemax;
 763         private final boolean doPadding;// whether or not to pad
 764         private int linepos = 0;
 765 
 766         EncOutputStream(OutputStream os, char[] base64,
 767                         byte[] newline, int linemax, boolean doPadding) {
 768             super(os);
 769             this.base64 = base64;
 770             this.newline = newline;
 771             this.linemax = linemax;
 772             this.doPadding = doPadding;
 773         }
 774 
 775         @Override
 776         public void write(int b) throws IOException {
 777             byte[] buf = new byte[1];
 778             buf[0] = (byte)(b & 0xff);
 779             write(buf, 0, 1);
 780         }
 781 
 782         private void checkNewline() throws IOException {
 783             if (linepos == linemax) {
 784                 out.write(newline);
 785                 linepos = 0;
 786             }
 787         }
 788 
 789         @Override
 790         public void write(byte[] b, int off, int len) throws IOException {
 791             if (closed)
 792                 throw new IOException("Stream is closed");
 793             if (off < 0 || len < 0 || len > b.length - off)
 794                 throw new ArrayIndexOutOfBoundsException();
 795             if (len == 0)
 796                 return;
 797             if (leftover != 0) {
 798                 if (leftover == 1) {
 799                     b1 = b[off++] & 0xff;
 800                     len--;
 801                     if (len == 0) {
 802                         leftover++;
 803                         return;
 804                     }
 805                 }
 806                 b2 = b[off++] & 0xff;
 807                 len--;
 808                 checkNewline();
 809                 out.write(base64[b0 >> 2]);
 810                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 811                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
 812                 out.write(base64[b2 & 0x3f]);
 813                 linepos += 4;
 814             }
 815             int nBits24 = len / 3;
 816             leftover = len - (nBits24 * 3);
 817             while (nBits24-- > 0) {
 818                 checkNewline();
 819                 int bits = (b[off++] & 0xff) << 16 |
 820                            (b[off++] & 0xff) <<  8 |
 821                            (b[off++] & 0xff);
 822                 out.write(base64[(bits >>> 18) & 0x3f]);
 823                 out.write(base64[(bits >>> 12) & 0x3f]);
 824                 out.write(base64[(bits >>> 6)  & 0x3f]);
 825                 out.write(base64[bits & 0x3f]);
 826                 linepos += 4;
 827            }
 828             if (leftover == 1) {
 829                 b0 = b[off++] & 0xff;
 830             } else if (leftover == 2) {
 831                 b0 = b[off++] & 0xff;
 832                 b1 = b[off++] & 0xff;
 833             }
 834         }
 835 
 836         @Override
 837         public void close() throws IOException {
 838             if (!closed) {
 839                 closed = true;
 840                 if (leftover == 1) {
 841                     checkNewline();
 842                     out.write(base64[b0 >> 2]);
 843                     out.write(base64[(b0 << 4) & 0x3f]);
 844                     if (doPadding) {
 845                         out.write('=');
 846                         out.write('=');
 847                     }
 848                 } else if (leftover == 2) {
 849                     checkNewline();
 850                     out.write(base64[b0 >> 2]);
 851                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 852                     out.write(base64[(b1 << 2) & 0x3f]);
 853                     if (doPadding) {
 854                        out.write('=');
 855                     }
 856                 }
 857                 leftover = 0;
 858                 out.close();
 859             }
 860         }
 861     }
 862 
 863     /*
 864      * An input stream for decoding Base64 bytes
 865      */
 866     private static class DecInputStream extends InputStream {
 867 
 868         private final InputStream is;
 869         private final boolean isMIME;
 870         private final int[] base64;      // base64 -> byte mapping
 871         private int bits = 0;            // 24-bit buffer for decoding
 872         private int nextin = 18;         // next available "off" in "bits" for input;
 873                                          // -> 18, 12, 6, 0
 874         private int nextout = -8;        // next available "off" in "bits" for output;
 875                                          // -> 8, 0, -8 (no byte for output)
 876         private boolean eof = false;
 877         private boolean closed = false;
 878 
 879         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
 880             this.is = is;
 881             this.base64 = base64;
 882             this.isMIME = isMIME;
 883         }
 884 
 885         private byte[] sbBuf = new byte[1];
 886 
 887         @Override
 888         public int read() throws IOException {
 889             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
 890         }
 891 
 892         @Override
 893         public int read(byte[] b, int off, int len) throws IOException {
 894             if (closed)
 895                 throw new IOException("Stream is closed");
 896             if (eof && nextout < 0)    // eof and no leftover
 897                 return -1;
 898             if (off < 0 || len < 0 || len > b.length - off)
 899                 throw new IndexOutOfBoundsException();
 900             int oldOff = off;
 901             if (nextout >= 0) {       // leftover output byte(s) in bits buf
 902                 do {
 903                     if (len == 0)
 904                         return off - oldOff;
 905                     b[off++] = (byte)(bits >> nextout);
 906                     len--;
 907                     nextout -= 8;
 908                 } while (nextout >= 0);
 909                 bits = 0;
 910             }
 911             while (len > 0) {
 912                 int v = is.read();
 913                 if (v == -1) {
 914                     eof = true;
 915                     if (nextin != 18) {
 916                         if (nextin == 12)
 917                             throw new IOException("Base64 stream has one un-decoded dangling byte.");
 918                         // treat ending xx/xxx without padding character legal.
 919                         // same logic as v == '=' below
 920                         b[off++] = (byte)(bits >> (16));
 921                         len--;
 922                         if (nextin == 0) {           // only one padding byte
 923                             if (len == 0) {          // no enough output space
 924                                 bits >>= 8;          // shift to lowest byte
 925                                 nextout = 0;
 926                             } else {
 927                                 b[off++] = (byte) (bits >>  8);
 928                             }
 929                         }
 930                     }
 931                     if (off == oldOff)
 932                         return -1;
 933                     else
 934                         return off - oldOff;
 935                 }
 936                 if (v == '=') {                  // padding byte(s)
 937                     // =     shiftto==18 unnecessary padding
 938                     // x=    shiftto==12 dangling x, invalid unit
 939                     // xx=   shiftto==6 && missing last '='
 940                     // xx=y  or last is not '='
 941                     if (nextin == 18 || nextin == 12 ||
 942                         nextin == 6 && is.read() != '=') {
 943                         throw new IOException("Illegal base64 ending sequence:" + nextin);
 944                     }
 945                     b[off++] = (byte)(bits >> (16));
 946                     len--;
 947                     if (nextin == 0) {           // only one padding byte
 948                         if (len == 0) {          // no enough output space
 949                             bits >>= 8;          // shift to lowest byte
 950                             nextout = 0;
 951                         } else {
 952                             b[off++] = (byte) (bits >>  8);
 953                         }
 954                     }
 955                     eof = true;
 956                     break;
 957                 }
 958                 if ((v = base64[v]) == -1) {
 959                     if (isMIME)                 // skip if for rfc2045
 960                         continue;
 961                     else
 962                         throw new IOException("Illegal base64 character " +
 963                             Integer.toString(v, 16));
 964                 }
 965                 bits |= (v << nextin);
 966                 if (nextin == 0) {
 967                     nextin = 18;    // clear for next
 968                     nextout = 16;
 969                     while (nextout >= 0) {
 970                         b[off++] = (byte)(bits >> nextout);
 971                         len--;
 972                         nextout -= 8;
 973                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
 974                             return off - oldOff;
 975                         }
 976                     }
 977                     bits = 0;
 978                 } else {
 979                     nextin -= 6;
 980                 }
 981             }
 982             return off - oldOff;
 983         }
 984 
 985         @Override
 986         public int available() throws IOException {
 987             if (closed)
 988                 throw new IOException("Stream is closed");
 989             return is.available();   // TBD:
 990         }
 991 
 992         @Override
 993         public void close() throws IOException {
 994             if (!closed) {
 995                 closed = true;
 996                 is.close();
 997             }
 998         }
 999     }
1000 }