1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  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  * <p>
  44  * <ul>
  45  * <a name="basic">
  46  * <li><b>Basic</b>
  47  * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
  48  *     RFC 4648 and RFC 2045 for encoding and decoding operation.
  49  *     The encoder does not add any line feed (line separator)
  50  *     character. The decoder rejects data that contains characters
  51  *     outside the base64 alphabet.</p></li>
  52  *
  53  * <a name="url">
  54  * <li><b>URL and Filename safe</b>
  55  * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
  56  *     in Table 2 of RFC 4648 for encoding and decoding. The
  57  *     encoder does not add any line feed (line separator) character.
  58  *     The decoder rejects data that contains characters outside the
  59  *     base64 alphabet.</p></li>
  60  *
  61  * <a name="mime">
  62  * <li><b>MIME</b>
  63  * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
  64  *     RFC 2045 for encoding and decoding operation. The encoded output
  65  *     must be represented in lines of no more than 76 characters each
  66  *     and uses a carriage return {@code '\r'} followed immediately by
  67  *     a linefeed {@code '\n'} as the line separator. No line separator
  68  *     is added to the end of the encoded output. All line separators
  69  *     or other characters not found in the base64 alphabet table are
  70  *     ignored in decoding operation.</p></li>
  71  * </ul>
  72  *
  73  * <p> Unless otherwise noted, passing a {@code null} argument to a
  74  * method of this class will cause a {@link java.lang.NullPointerException
  75  * NullPointerException} to be thrown.
  76  *
  77  * @author  Xueming Shen
  78  * @since   1.8
  79  */
  80 
  81 public class Base64 {
  82 
  83     private Base64() {}
  84 
  85     /**
  86      * Returns a {@link Encoder} that encodes using the
  87      * <a href="#basic">Basic</a> type base64 encoding scheme.
  88      *
  89      * @return  A Base64 encoder.
  90      */
  91     public static Encoder getEncoder() {
  92          return Encoder.RFC4648;
  93     }
  94 
  95     /**
  96      * Returns a {@link Encoder} that encodes using the
  97      * <a href="#url">URL and Filename safe</a> type base64
  98      * encoding scheme.
  99      *
 100      * @return  A Base64 encoder.
 101      */
 102     public static Encoder getUrlEncoder() {
 103          return Encoder.RFC4648_URLSAFE;
 104     }
 105 
 106     /**
 107      * Returns a {@link Encoder} that encodes using the
 108      * <a href="#mime">MIME</a> type base64 encoding scheme.
 109      *
 110      * @return  A Base64 encoder.
 111      */
 112     public static Encoder getMimeEncoder() {
 113         return Encoder.RFC2045;
 114     }
 115 
 116     /**
 117      * Returns a {@link Encoder} that encodes using the
 118      * <a href="#mime">MIME</a> type base64 encoding scheme
 119      * with specified line length and line separators.
 120      *
 121      * @param   lineLength
 122      *          the length of each output line (rounded down to nearest multiple
 123      *          of 4). If {@code lineLength <= 0} the output will not be separated
 124      *          in lines
 125      * @param   lineSeparator
 126      *          the line separator for each output line
 127      *
 128      * @return  A Base64 encoder.
 129      *
 130      * @throws  IllegalArgumentException if {@code lineSeparator} includes any
 131      *          character of "The Base64 Alphabet" as specified in Table 1 of
 132      *          RFC 2045.
 133      */
 134     public static Encoder getEncoder(int lineLength, byte[] lineSeparator) {
 135          Objects.requireNonNull(lineSeparator);
 136          int[] base64 = Decoder.fromBase64;
 137          for (byte b : lineSeparator) {
 138              if (base64[b & 0xff] != -1)
 139                  throw new IllegalArgumentException(
 140                      "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
 141          }
 142          return new Encoder(false, lineSeparator, lineLength >> 2 << 2);
 143     }
 144 
 145     /**
 146      * Returns a {@link Decoder} that decodes using the
 147      * <a href="#basic">Basic</a> type base64 encoding scheme.
 148      *
 149      * @return  A Base64 decoder.
 150      */
 151     public static Decoder getDecoder() {
 152          return Decoder.RFC4648;
 153     }
 154 
 155     /**
 156      * Returns a {@link Decoder} that decodes using the
 157      * <a href="#url">URL and Filename safe</a> type base64
 158      * encoding scheme.
 159      *
 160      * @return  A Base64 decoder.
 161      */
 162     public static Decoder getUrlDecoder() {
 163          return Decoder.RFC4648_URLSAFE;
 164     }
 165 
 166     /**
 167      * Returns a {@link Decoder} that decodes using the
 168      * <a href="#mime">MIME</a> type base64 decoding scheme.
 169      *
 170      * @return  A Base64 decoder.
 171      */
 172     public static Decoder getMimeDecoder() {
 173          return Decoder.RFC2045;
 174     }
 175 
 176     /**
 177      * This class implements an encoder for encoding byte data using
 178      * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 179      *
 180      * <p> Instances of {@link Encoder} class are safe for use by
 181      * multiple concurrent threads.
 182      *
 183      * <p> Unless otherwise noted, passing a {@code null} argument to
 184      * a method of this class will cause a
 185      * {@link java.lang.NullPointerException NullPointerException} to
 186      * be thrown.
 187      *
 188      * @see     Decoder
 189      * @since   1.8
 190      */
 191     public static class Encoder {
 192 
 193         private final byte[] newline;
 194         private final int linemax;
 195         private final boolean isURL;
 196 
 197         private Encoder(boolean isURL, byte[] newline, int linemax) {
 198             this.isURL = isURL;
 199             this.newline = newline;
 200             this.linemax = linemax;
 201         }
 202 
 203         /**
 204          * This array is a lookup table that translates 6-bit positive integer
 205          * index values into their "Base64 Alphabet" equivalents as specified
 206          * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
 207          */
 208         private static final char[] toBase64 = {
 209             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 210             'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 211             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 212             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 213             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
 214         };
 215 
 216         /**
 217          * It's the lookup table for "URL and Filename safe Base64" as specified
 218          * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
 219          * '_'. This table is used when BASE64_URL is specified.
 220          */
 221         private static final char[] toBase64URL = {
 222             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 223             'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
 224             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 225             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 226             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
 227         };
 228 
 229         private static final int MIMELINEMAX = 76;
 230         private static final byte[] CRLF = new byte[] {'\r', '\n'};
 231 
 232         static final Encoder RFC4648 = new Encoder(false, null, -1);
 233         static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1);
 234         static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX);
 235 
 236         /**
 237          * Encodes all bytes from the specified byte array into a newly-allocated
 238          * byte array using the {@link Base64} encoding scheme. The returned byte
 239          * array is of the length of the resulting bytes.
 240          *
 241          * @param   src
 242          *          the byte array to encode
 243          * @return  A newly-allocated byte array containing the resulting
 244          *          encoded bytes.
 245          */
 246         public byte[] encode(byte[] src) {
 247             int len = 4 * ((src.length + 2) / 3);    // dst array size
 248             if (linemax > 0)                          // line separators
 249                 len += (len - 1) / linemax * newline.length;
 250             byte[] dst = new byte[len];
 251             int ret = encode0(src, 0, src.length, dst);
 252             if (ret != dst.length)
 253                  return Arrays.copyOf(dst, ret);
 254             return dst;
 255         }
 256 
 257         /**
 258          * Encodes all bytes from the specified byte array using the
 259          * {@link Base64} encoding scheme, writing the resulting bytes to the
 260          * given output byte array, starting at offset 0.
 261          *
 262          * <p> It is the responsibility of the invoker of this method to make
 263          * sure the output byte array {@code dst} has enough space for encoding
 264          * all bytes from the input byte array. No bytes will be written to the
 265          * output byte array if the output byte array is not big enough.
 266          *
 267          * @param   src
 268          *          the byte array to encode
 269          * @param   dst
 270          *          the output byte array
 271          * @return  The number of bytes written to the output byte array
 272          *
 273          * @throws  IllegalArgumentException if {@code dst} does not have enough
 274          *          space for encoding all input bytes.
 275          */
 276         public int encode(byte[] src, byte[] dst) {
 277             int len = 4 * ((src.length + 2) / 3);    // dst array size
 278             if (linemax > 0) {
 279                 len += (len - 1) / linemax * newline.length;
 280             }
 281             if (dst.length < len)
 282                 throw new IllegalArgumentException(
 283                     "Output byte array is too small for encoding all input bytes");
 284             return encode0(src, 0, src.length, dst);
 285         }
 286 
 287         /**
 288          * Encodes the specified byte array into a String using the {@link Base64}
 289          * encoding scheme.
 290          *
 291          * <p> This method first encodes all input bytes into a base64 encoded
 292          * byte array and then constructs a new String by using the encoded byte
 293          * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
 294          * ISO-8859-1} charset.
 295          *
 296          * <p> In other words, an invocation of this method has exactly the same
 297          * effect as invoking
 298          * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
 299          *
 300          * @param   src
 301          *          the byte array to encode
 302          * @return  A String containing the resulting Base64 encoded characters
 303          */
 304         @SuppressWarnings("deprecation")
 305         public String encodeToString(byte[] src) {
 306             byte[] encoded = encode(src);
 307             return new String(encoded, 0, 0, encoded.length);
 308         }
 309 
 310         /**
 311          * Encodes all remaining bytes from the specified byte buffer into
 312          * a newly-allocated ByteBuffer using the {@link Base64} encoding
 313          * scheme.
 314          *
 315          * Upon return, the source buffer's position will be updated to
 316          * its limit; its limit will not have been changed. The returned
 317          * output buffer's position will be zero and its limit will be the
 318          * number of resulting encoded bytes.
 319          *
 320          * @param   buffer
 321          *          the source ByteBuffer to encode
 322          * @return  A newly-allocated byte buffer containing the encoded bytes.
 323          */
 324         public ByteBuffer encode(ByteBuffer buffer) {
 325             int len = 4 * ((buffer.remaining() + 2) / 3);
 326             if (linemax > 0)
 327                 len += (len - 1) / linemax * newline.length;
 328             byte[] dst = new byte[len];
 329             int ret = 0;
 330             if (buffer.hasArray()) {
 331                 ret = encode0(buffer.array(),
 332                               buffer.arrayOffset() + buffer.position(),
 333                               buffer.arrayOffset() + buffer.limit(),
 334                               dst);
 335                 buffer.position(buffer.limit());
 336             } else {
 337                 byte[] src = new byte[buffer.remaining()];
 338                 buffer.get(src);
 339                 ret = encode0(src, 0, src.length, dst);
 340             }
 341             if (ret != dst.length)
 342                  dst = Arrays.copyOf(dst, ret);
 343             return ByteBuffer.wrap(dst);
 344         }
 345 
 346         /**
 347          * Encodes as many bytes as possible from the input byte buffer
 348          * using the {@link Base64} encoding scheme, writing the resulting
 349          * bytes to the given output byte buffer.
 350          *
 351          * <p>The buffers are read from, and written to, starting at their
 352          * current positions. Upon return, the input and output buffers'
 353          * positions will be advanced to reflect the bytes read and written,
 354          * but their limits will not be modified.
 355          *
 356          * <p>The encoding operation will stop and return if either all
 357          * remaining bytes in the input buffer have been encoded and written
 358          * to the output buffer, or the output buffer has insufficient space
 359          * to encode any more input bytes. The encoding operation can be
 360          * continued, if there is more bytes in input buffer to be encoded,
 361          * by invoking this method again with an output buffer that has more
 362          * {@linkplain java.nio.Buffer#remaining remaining} bytes. This is
 363          * typically done by draining any encoded bytes from the output buffer.
 364          * The value returned from last invocation needs to be passed in as the
 365          * third parameter {@code bytesOut} if it is to continue an unfinished
 366          * encoding, 0 otherwise.
 367          *
 368          * <p><b>Recommended Usage Example</b>
 369          * <pre>
 370          *    ByteBuffer src = ...;
 371          *    ByteBuffer dst = ...;
 372          *    Base64.Encoder enc = Base64.getMimeDecoder();
 373          *
 374          *    int bytesOut = 0;
 375          *    while (src.hasRemaining()) {
 376          *        // clear output buffer for decoding
 377          *        dst.clear();
 378          *        bytesOut = enc.encode(src, dst, bytesOut);
 379          *
 380          *        // read encoded bytes out of "dst"
 381          *        dst.flip();
 382          *        ...
 383          *    }
 384          * </pre>
 385          *
 386          * @param   src
 387          *          the input byte buffer to encode
 388          * @param   dst
 389          *          the output byte buffer
 390          * @param   bytesOut
 391          *          the return value of last invocation if this is to continue
 392          *          an unfinished encoding operation, 0 otherwise
 393          * @return  The sum total of {@code bytesOut} and the number of bytes
 394          *          written to the output ByteBuffer during this invocation.
 395          */
 396         public int encode(ByteBuffer src, ByteBuffer dst, int bytesOut) {
 397             if (src.hasArray() && dst.hasArray())
 398                 return encodeArray(src, dst, bytesOut);
 399             return encodeBuffer(src, dst, bytesOut);
 400         }
 401 
 402         /**
 403          * Wraps an output stream for encoding byte data using the {@link Base64}
 404          * encoding scheme.
 405          *
 406          * <p> It is recommended to promptly close the returned output stream after
 407          * use, during which it will flush all possible leftover bytes to the underlying
 408          * output stream. Closing the returned output stream will close the underlying
 409          * output stream.
 410          *
 411          * @param   os
 412          *          the output stream.
 413          * @return  the output stream for encoding the byte data into the
 414          *          specified Base64 encoded format
 415          */
 416         public OutputStream wrap(OutputStream os) {
 417             Objects.requireNonNull(os);
 418             return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
 419                                        newline, linemax);
 420         }
 421 
 422         private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) {
 423             char[] base64 = isURL? toBase64URL : toBase64;
 424             byte[] sa = src.array();
 425             int    sp = src.arrayOffset() + src.position();
 426             int    sl = src.arrayOffset() + src.limit();
 427             byte[] da = dst.array();
 428             int    dp = dst.arrayOffset() + dst.position();
 429             int    dl = dst.arrayOffset() + dst.limit();
 430             int    dp00 = dp;
 431             int    dpos = 0;        // dp of each line
 432             if (linemax > 0 && bytesOut > 0)
 433                 dpos = bytesOut % (linemax + newline.length);
 434             try {
 435                 if (dpos == linemax && sp < src.limit()) {
 436                     if (dp + newline.length > dl)
 437                         return  dp - dp00 + bytesOut;
 438                     for (byte b : newline){
 439                         dst.put(dp++, b);
 440                     }
 441                     dpos = 0;
 442                 }
 443                 sl = sp + (sl - sp) / 3 * 3;
 444                 while (sp < sl) {
 445                     int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3
 446                                              : sl - sp;
 447                     int sl0 = Math.min(sp + slen, sl);
 448                     for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 449                         if (dp0 + 4 > dl) {
 450                             sp = sp0; dp = dp0;
 451                             return  dp0 - dp00 + bytesOut;
 452                         }
 453                         int bits = (sa[sp0++] & 0xff) << 16 |
 454                                    (sa[sp0++] & 0xff) <<  8 |
 455                                    (sa[sp0++] & 0xff);
 456                         da[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 457                         da[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 458                         da[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 459                         da[dp0++] = (byte)base64[bits & 0x3f];
 460                     }
 461                     int n = (sl0 - sp) / 3 * 4;
 462                     dpos += n;
 463                     dp += n;
 464                     sp = sl0;
 465                     if (dpos == linemax && sp < src.limit()) {
 466                         if (dp + newline.length > dl)
 467                             return  dp - dp00 + bytesOut;
 468                         for (byte b : newline){
 469                             da[dp++] = b;
 470                         }
 471                         dpos = 0;
 472                     }
 473                 }
 474                 sl = src.arrayOffset() + src.limit();
 475                 if (sp < sl && dl >= dp + 4) {       // 1 or 2 leftover bytes
 476                     int b0 = sa[sp++] & 0xff;
 477                     da[dp++] = (byte)base64[b0 >> 2];
 478                     if (sp == sl) {
 479                         da[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 480                         da[dp++] = '=';
 481                         da[dp++] = '=';
 482                     } else {
 483                         int b1 = sa[sp++] & 0xff;
 484                         da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 485                         da[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 486                         da[dp++] = '=';
 487                     }
 488                 }
 489                 return dp - dp00 + bytesOut;
 490             } finally {
 491                 src.position(sp - src.arrayOffset());
 492                 dst.position(dp - dst.arrayOffset());
 493             }
 494         }
 495 
 496         private int encodeBuffer(ByteBuffer src, ByteBuffer dst, int bytesOut) {
 497             char[] base64 = isURL? toBase64URL : toBase64;
 498             int sp = src.position();
 499             int sl = src.limit();
 500             int dp = dst.position();
 501             int dl = dst.limit();
 502             int dp00 = dp;
 503 
 504             int dpos = 0;        // dp of each line
 505             if (linemax > 0 && bytesOut > 0)
 506                 dpos = bytesOut % (linemax + newline.length);
 507             try {
 508                 if (dpos == linemax && sp < src.limit()) {
 509                     if (dp + newline.length > dl)
 510                         return  dp - dp00 + bytesOut;
 511                     for (byte b : newline){
 512                         dst.put(dp++, b);
 513                     }
 514                     dpos = 0;
 515                 }
 516                 sl = sp + (sl - sp) / 3 * 3;
 517                 while (sp < sl) {
 518                     int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3
 519                                              : sl - sp;
 520                     int sl0 = Math.min(sp + slen, sl);
 521                     for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 522                         if (dp0 + 4 > dl) {
 523                             sp = sp0; dp = dp0;
 524                             return  dp0 - dp00 + bytesOut;
 525                         }
 526                         int bits = (src.get(sp0++) & 0xff) << 16 |
 527                                    (src.get(sp0++) & 0xff) <<  8 |
 528                                    (src.get(sp0++) & 0xff);
 529                         dst.put(dp0++, (byte)base64[(bits >>> 18) & 0x3f]);
 530                         dst.put(dp0++, (byte)base64[(bits >>> 12) & 0x3f]);
 531                         dst.put(dp0++, (byte)base64[(bits >>> 6)  & 0x3f]);
 532                         dst.put(dp0++, (byte)base64[bits & 0x3f]);
 533                     }
 534                     int n = (sl0 - sp) / 3 * 4;
 535                     dpos += n;
 536                     dp += n;
 537                     sp = sl0;
 538                     if (dpos == linemax && sp < src.limit()) {
 539                         if (dp + newline.length > dl)
 540                             return  dp - dp00 + bytesOut;
 541                         for (byte b : newline){
 542                             dst.put(dp++, b);
 543                         }
 544                         dpos = 0;
 545                     }
 546                 }
 547                 if (sp < src.limit() && dl >= dp + 4) {       // 1 or 2 leftover bytes
 548                     int b0 = src.get(sp++) & 0xff;
 549                     dst.put(dp++, (byte)base64[b0 >> 2]);
 550                     if (sp == src.limit()) {
 551                         dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]);
 552                         dst.put(dp++, (byte)'=');
 553                         dst.put(dp++, (byte)'=');
 554                     } else {
 555                         int b1 = src.get(sp++) & 0xff;
 556                         dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 557                         dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]);
 558                         dst.put(dp++, (byte)'=');
 559                     }
 560                 }
 561                 return dp - dp00 + bytesOut;
 562             } finally {
 563                 src.position(sp);
 564                 dst.position(dp);
 565             }
 566         }
 567 
 568         private int encode0(byte[] src, int off, int end, byte[] dst) {
 569             char[] base64 = isURL ? toBase64URL : toBase64;
 570             int sp = off;
 571             int slen = (end - off) / 3 * 3;
 572             int sl = off + slen;
 573             if (linemax > 0 && slen  > linemax / 4 * 3)
 574                 slen = linemax / 4 * 3;
 575             int dp = 0;
 576             while (sp < sl) {
 577                 int sl0 = Math.min(sp + slen, sl);
 578                 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 579                     int bits = (src[sp0++] & 0xff) << 16 |
 580                                (src[sp0++] & 0xff) <<  8 |
 581                                (src[sp0++] & 0xff);
 582                     dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 583                     dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 584                     dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 585                     dst[dp0++] = (byte)base64[bits & 0x3f];
 586                 }
 587                 int dlen = (sl0 - sp) / 3 * 4;
 588                 dp += dlen;
 589                 sp = sl0;
 590                 if (dlen == linemax && sp < end) {
 591                     for (byte b : newline){
 592                         dst[dp++] = b;
 593                     }
 594                 }
 595             }
 596             if (sp < end) {               // 1 or 2 leftover bytes
 597                 int b0 = src[sp++] & 0xff;
 598                 dst[dp++] = (byte)base64[b0 >> 2];
 599                 if (sp == end) {
 600                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 601                     dst[dp++] = '=';
 602                     dst[dp++] = '=';
 603                 } else {
 604                     int b1 = src[sp++] & 0xff;
 605                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 606                     dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 607                     dst[dp++] = '=';
 608                 }
 609             }
 610             return dp;
 611         }
 612     }
 613 
 614     /**
 615      * This class implements a decoder for decoding byte data using the
 616      * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 617      *
 618      * <p> The Base64 padding character {@code '='} is accepted and
 619      * interpreted as the end of the encoded byte data, but is not
 620      * required. So if the final unit of the encoded byte data only has
 621      * two or three Base64 characters (without the corresponding padding
 622      * character(s) padded), they are decoded as if followed by padding
 623      * character(s).
 624      *
 625      * <p> Instances of {@link Decoder} class are safe for use by
 626      * multiple concurrent threads.
 627      *
 628      * <p> Unless otherwise noted, passing a {@code null} argument to
 629      * a method of this class will cause a
 630      * {@link java.lang.NullPointerException NullPointerException} to
 631      * be thrown.
 632      *
 633      * @see     Encoder
 634      * @since   1.8
 635      */
 636     public static class Decoder {
 637 
 638         private final boolean isURL;
 639         private final boolean isMIME;
 640 
 641         private Decoder(boolean isURL, boolean isMIME) {
 642             this.isURL = isURL;
 643             this.isMIME = isMIME;
 644         }
 645 
 646         /**
 647          * Lookup table for decoding unicode characters drawn from the
 648          * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
 649          * their 6-bit positive integer equivalents.  Characters that
 650          * are not in the Base64 alphabet but fall within the bounds of
 651          * the array are encoded to -1.
 652          *
 653          */
 654         private static final int[] fromBase64 = new int[256];
 655         static {
 656             Arrays.fill(fromBase64, -1);
 657             for (int i = 0; i < Encoder.toBase64.length; i++)
 658                 fromBase64[Encoder.toBase64[i]] = i;
 659             fromBase64['='] = -2;
 660         }
 661 
 662         /**
 663          * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
 664          * as specified in Table2 of the RFC 4648.
 665          */
 666         private static final int[] fromBase64URL = new int[256];
 667 
 668         static {
 669             Arrays.fill(fromBase64URL, -1);
 670             for (int i = 0; i < Encoder.toBase64URL.length; i++)
 671                 fromBase64URL[Encoder.toBase64URL[i]] = i;
 672             fromBase64URL['='] = -2;
 673         }
 674 
 675         static final Decoder RFC4648         = new Decoder(false, false);
 676         static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
 677         static final Decoder RFC2045         = new Decoder(false, true);
 678 
 679         /**
 680          * Decodes all bytes from the input byte array using the {@link Base64}
 681          * encoding scheme, writing the results into a newly-allocated output
 682          * byte array. The returned byte array is of the length of the resulting
 683          * bytes.
 684          *
 685          * @param   src
 686          *          the byte array to decode
 687          *
 688          * @return  A newly-allocated byte array containing the decoded bytes.
 689          *
 690          * @throws  IllegalArgumentException
 691          *          if {@code src} is not in valid Base64 scheme
 692          */
 693         public byte[] decode(byte[] src) {
 694             byte[] dst = new byte[outLength(src, 0, src.length)];
 695             int ret = decode0(src, 0, src.length, dst);
 696             if (ret != dst.length) {
 697                 dst = Arrays.copyOf(dst, ret);
 698             }
 699             return dst;
 700         }
 701 
 702         /**
 703          * Decodes a Base64 encoded String into a newly-allocated byte array
 704          * using the {@link Base64} encoding scheme.
 705          *
 706          * <p> An invocation of this method has exactly the same effect as invoking
 707          * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
 708          *
 709          * @param   src
 710          *          the string to decode
 711          *
 712          * @return  A newly-allocated byte array containing the decoded bytes.
 713          *
 714          * @throws  IllegalArgumentException
 715          *          if {@code src} is not in valid Base64 scheme
 716          */
 717         public byte[] decode(String src) {
 718             return decode(src.getBytes(StandardCharsets.ISO_8859_1));
 719         }
 720 
 721         /**
 722          * Decodes all bytes from the input byte array using the {@link Base64}
 723          * encoding scheme, writing the results into the given output byte array,
 724          * starting at offset 0.
 725          *
 726          * <p> It is the responsibility of the invoker of this method to make
 727          * sure the output byte array {@code dst} has enough space for decoding
 728          * all bytes from the input byte array. No bytes will be be written to
 729          * the output byte array if the output byte array is not big enough.
 730          *
 731          * <p> If the input byte array is not in valid Base64 encoding scheme
 732          * then some bytes may have been written to the output byte array before
 733          * IllegalargumentException is thrown.
 734          *
 735          * @param   src
 736          *          the byte array to decode
 737          * @param   dst
 738          *          the output byte array
 739          *
 740          * @return  The number of bytes written to the output byte array
 741          *
 742          * @throws  IllegalArgumentException
 743          *          if {@code src} is not in valid Base64 scheme, or {@code dst}
 744          *          does not have enough space for decoding all input bytes.
 745          */
 746         public int decode(byte[] src, byte[] dst) {
 747             int len = outLength(src, 0, src.length);
 748             if (dst.length < len)
 749                 throw new IllegalArgumentException(
 750                     "Output byte array is too small for decoding all input bytes");
 751             return decode0(src, 0, src.length, dst);
 752         }
 753 
 754         /**
 755          * Decodes all bytes from the input byte buffer using the {@link Base64}
 756          * encoding scheme, writing the results into a newly-allocated ByteBuffer.
 757          *
 758          * <p> Upon return, the source buffer's position will be updated to
 759          * its limit; its limit will not have been changed. The returned
 760          * output buffer's position will be zero and its limit will be the
 761          * number of resulting decoded bytes
 762          *
 763          * @param   buffer
 764          *          the ByteBuffer to decode
 765          *
 766          * @return  A newly-allocated byte buffer containing the decoded bytes
 767          *
 768          * @throws  IllegalArgumentException
 769          *          if {@code src} is not in valid Base64 scheme.
 770          */
 771         public ByteBuffer decode(ByteBuffer buffer) {
 772             int pos0 = buffer.position();
 773             try {
 774                 byte[] src;
 775                 int sp, sl;
 776                 if (buffer.hasArray()) {
 777                     src = buffer.array();
 778                     sp = buffer.arrayOffset() + buffer.position();
 779                     sl = buffer.arrayOffset() + buffer.limit();
 780                     buffer.position(buffer.limit());
 781                 } else {
 782                     src = new byte[buffer.remaining()];
 783                     buffer.get(src);
 784                     sp = 0;
 785                     sl = src.length;
 786                 }
 787                 byte[] dst = new byte[outLength(src, sp, sl)];
 788                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
 789             } catch (IllegalArgumentException iae) {
 790                 buffer.position(pos0);
 791                 throw iae;
 792             }
 793         }
 794 
 795         /**
 796          * Decodes as many bytes as possible from the input byte buffer
 797          * using the {@link Base64} encoding scheme, writing the resulting
 798          * bytes to the given output byte buffer.
 799          *
 800          * <p>The buffers are read from, and written to, starting at their
 801          * current positions. Upon return, the input and output buffers'
 802          * positions will be advanced to reflect the bytes read and written,
 803          * but their limits will not be modified.
 804          *
 805          * <p> If the input buffer is not in valid Base64 encoding scheme
 806          * then some bytes may have been written to the output buffer
 807          * before IllegalArgumentException is thrown. The positions of
 808          * both input and output buffer will not be advanced in this case.
 809          *
 810          * <p>The decoding operation will end and return if all remaining
 811          * bytes in the input buffer have been decoded and written to the
 812          * output buffer.
 813          *
 814          * <p> The decoding operation will stop and return if the output
 815          * buffer has insufficient space to decode any more input bytes.
 816          * The decoding operation can be continued, if there is more bytes
 817          * in input buffer to be decoded, by invoking this method again with
 818          * an output buffer that has more {@linkplain java.nio.Buffer#remaining
 819          * remaining} bytes. This is typically done by draining any decoded
 820          * bytes from the output buffer.
 821          *
 822          * <p><b>Recommended Usage Example</b>
 823          * <pre>
 824          *    ByteBuffer src = ...;
 825          *    ByteBuffer dst = ...;
 826          *    Base64.Decoder dec = Base64.getDecoder();
 827          *
 828          *    while (src.hasRemaining()) {
 829          *
 830          *        // prepare the output byte buffer
 831          *        dst.clear();
 832          *        dec.decode(src, dst);
 833          *
 834          *        // read bytes from the output buffer
 835          *        dst.flip();
 836          *        ...
 837          *    }
 838          * </pre>
 839          *
 840          * @param   src
 841          *          the input byte buffer to decode
 842          * @param   dst
 843          *          the output byte buffer
 844          *
 845          * @return  The number of bytes written to the output byte buffer during
 846          *          this decoding invocation
 847          *
 848          * @throws  IllegalArgumentException
 849          *          if {@code src} is not in valid Base64 scheme.
 850          */
 851         public int decode(ByteBuffer src, ByteBuffer dst) {
 852             int sp0 = src.position();
 853             int dp0 = dst.position();
 854             try {
 855                 if (src.hasArray() && dst.hasArray())
 856                     return decodeArray(src, dst);
 857                 return decodeBuffer(src, dst);
 858             } catch (IllegalArgumentException iae) {
 859                 src.position(sp0);
 860                 dst.position(dp0);
 861                 throw iae;
 862             }
 863         }
 864 
 865         /**
 866          * Returns an input stream for decoding {@link Base64} encoded byte stream.
 867          *
 868          * <p> The {@code read}  methods of the returned {@code InputStream} will
 869          * throw {@code IOException} when reading bytes that cannot be decoded.
 870          *
 871          * <p> Closing the returned input stream will close the underlying
 872          * input stream.
 873          *
 874          * @param   is
 875          *          the input stream
 876          *
 877          * @return  the input stream for decoding the specified Base64 encoded
 878          *          byte stream
 879          */
 880         public InputStream wrap(InputStream is) {
 881             Objects.requireNonNull(is);
 882             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 883         }
 884 
 885         private int decodeArray(ByteBuffer src, ByteBuffer dst) {
 886             int[] base64 = isURL ? fromBase64URL : fromBase64;
 887             int   bits = 0;
 888             int   shiftto = 18;       // pos of first byte of 4-byte atom
 889             byte[] sa = src.array();
 890             int    sp = src.arrayOffset() + src.position();
 891             int    sl = src.arrayOffset() + src.limit();
 892             byte[] da = dst.array();
 893             int    dp = dst.arrayOffset() + dst.position();
 894             int    dl = dst.arrayOffset() + dst.limit();
 895             int    dp0 = dp;
 896             int    mark = sp;
 897             try {
 898                 while (sp < sl) {
 899                     int b = sa[sp++] & 0xff;
 900                     if ((b = base64[b]) < 0) {
 901                         if (b == -2) {   // padding byte
 902                             if (shiftto == 6 && (sp == sl || sa[sp++] != '=') ||
 903                                 shiftto == 18) {
 904                                 throw new IllegalArgumentException(
 905                                      "Input byte array has wrong 4-byte ending unit");
 906                             }
 907                             break;
 908                         }
 909                         if (isMIME)     // skip if for rfc2045
 910                             continue;
 911                         else
 912                             throw new IllegalArgumentException(
 913                                 "Illegal base64 character " +
 914                                 Integer.toString(sa[sp - 1], 16));
 915                     }
 916                     bits |= (b << shiftto);
 917                     shiftto -= 6;
 918                     if (shiftto < 0) {
 919                         if (dl < dp + 3)
 920                             return dp - dp0;
 921                         da[dp++] = (byte)(bits >> 16);
 922                         da[dp++] = (byte)(bits >>  8);
 923                         da[dp++] = (byte)(bits);
 924                         shiftto = 18;
 925                         bits = 0;
 926                         mark = sp;
 927                     }
 928                 }
 929                 if (shiftto == 6) {
 930                     if (dl - dp < 1)
 931                         return dp - dp0;
 932                     da[dp++] = (byte)(bits >> 16);
 933                 } else if (shiftto == 0) {
 934                     if (dl - dp < 2)
 935                         return dp - dp0;
 936                     da[dp++] = (byte)(bits >> 16);
 937                     da[dp++] = (byte)(bits >>  8);
 938                 } else if (shiftto == 12) {
 939                     throw new IllegalArgumentException(
 940                         "Last unit does not have enough valid bits");
 941                 }
 942                 while (sp < sl) {
 943                     if (isMIME && base64[sa[sp++]] < 0)
 944                         continue;
 945                     throw new IllegalArgumentException(
 946                         "Input byte array has incorrect ending byte at " + sp);
 947                 }
 948                 mark = sp;
 949                 return dp - dp0;
 950             } finally {
 951                 src.position(mark);
 952                 dst.position(dp);
 953             }
 954         }
 955 
 956         private int decodeBuffer(ByteBuffer src, ByteBuffer dst) {
 957             int[] base64 = isURL ? fromBase64URL : fromBase64;
 958             int   bits = 0;
 959             int   shiftto = 18;       // pos of first byte of 4-byte atom
 960             int    sp = src.position();
 961             int    sl = src.limit();
 962             int    dp = dst.position();
 963             int    dl = dst.limit();
 964             int    dp0 = dp;
 965             int    mark = sp;
 966             try {
 967                 while (sp < sl) {
 968                     int b = src.get(sp++) & 0xff;
 969                     if ((b = base64[b]) < 0) {
 970                         if (b == -2) {  // padding byte
 971                             if (shiftto == 6 && (sp == sl || src.get(sp++) != '=') ||
 972                                 shiftto == 18) {
 973                                 throw new IllegalArgumentException(
 974                                      "Input byte array has wrong 4-byte ending unit");
 975                             }
 976                             break;
 977                         }
 978                         if (isMIME)     // skip if for rfc2045
 979                             continue;
 980                         else
 981                             throw new IllegalArgumentException(
 982                                 "Illegal base64 character " +
 983                                 Integer.toString(src.get(sp - 1), 16));
 984                     }
 985                     bits |= (b << shiftto);
 986                     shiftto -= 6;
 987                     if (shiftto < 0) {
 988                         if (dl < dp + 3)
 989                             return dp - dp0;
 990                         dst.put(dp++, (byte)(bits >> 16));
 991                         dst.put(dp++, (byte)(bits >>  8));
 992                         dst.put(dp++, (byte)(bits));
 993                         shiftto = 18;
 994                         bits = 0;
 995                         mark = sp;
 996                     }
 997                 }
 998                 if (shiftto == 6) {
 999                     if (dl - dp < 1)
1000                         return dp - dp0;
1001                      dst.put(dp++, (byte)(bits >> 16));
1002                 } else if (shiftto == 0) {
1003                     if (dl - dp < 2)
1004                         return dp - dp0;
1005                     dst.put(dp++, (byte)(bits >> 16));
1006                     dst.put(dp++, (byte)(bits >>  8));
1007                 } else if (shiftto == 12) {
1008                     throw new IllegalArgumentException(
1009                         "Last unit does not have enough valid bits");
1010                 }
1011                 while (sp < sl) {
1012                     if (isMIME && base64[src.get(sp++)] < 0)
1013                         continue;
1014                     throw new IllegalArgumentException(
1015                         "Input byte array has incorrect ending byte at " + sp);
1016                 }
1017                 mark = sp;
1018                 return dp - dp0;
1019             } finally {
1020                 src.position(mark);
1021                 dst.position(dp);
1022             }
1023         }
1024 
1025         private int outLength(byte[] src, int sp, int sl) {
1026             int[] base64 = isURL ? fromBase64URL : fromBase64;
1027             int paddings = 0;
1028             int len = sl - sp;
1029             if (len == 0)
1030                 return 0;
1031             if (len < 2) {
1032                 if (isMIME && base64[0] == -1)
1033                     return 0;
1034                 throw new IllegalArgumentException(
1035                     "Input byte[] should at least have 2 bytes for base64 bytes");
1036             }
1037             if (src[sl - 1] == '=') {
1038                 paddings++;
1039                 if (src[sl - 2] == '=')
1040                     paddings++;
1041             }
1042             if (isMIME) {
1043                 // scan all bytes to fill out all non-alphabet. a performance
1044                 // trade-off of pre-scan or Arrays.copyOf
1045                 int n = 0;
1046                 while (sp < sl) {
1047                     int b = src[sp++] & 0xff;
1048                     if (b == '=')
1049                         break;
1050                     if ((b = base64[b]) == -1)
1051                         n++;
1052                 }
1053                 len -= n;
1054             }
1055             if (paddings == 0 && (len & 0x3) !=  0)
1056                 paddings = 4 - (len & 0x3);
1057             return 3 * ((len + 3) / 4) - paddings;
1058         }
1059 
1060         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
1061             int[] base64 = isURL ? fromBase64URL : fromBase64;
1062             int dp = 0;
1063             int bits = 0;
1064             int shiftto = 18;       // pos of first byte of 4-byte atom
1065             while (sp < sl) {
1066                 int b = src[sp++] & 0xff;
1067                 if ((b = base64[b]) < 0) {
1068                     if (b == -2) {     // padding byte '='
1069                         // xx=   shiftto==6&&sp==sl missing last =
1070                         // xx=y  shiftto==6 last is not =
1071                         // =     shiftto==18 unnecessary padding
1072                         // x=    shiftto==12 be taken care later
1073                         //       together with single x, invalid anyway
1074                         if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
1075                             shiftto == 18) {
1076                             throw new IllegalArgumentException(
1077                                 "Input byte array has wrong 4-byte ending unit");
1078                         }
1079                         break;
1080                     }
1081                     if (isMIME)    // skip if for rfc2045
1082                         continue;
1083                     else
1084                         throw new IllegalArgumentException(
1085                             "Illegal base64 character " +
1086                             Integer.toString(src[sp - 1], 16));
1087                 }
1088                 bits |= (b << shiftto);
1089                 shiftto -= 6;
1090                 if (shiftto < 0) {
1091                     dst[dp++] = (byte)(bits >> 16);
1092                     dst[dp++] = (byte)(bits >>  8);
1093                     dst[dp++] = (byte)(bits);
1094                     shiftto = 18;
1095                     bits = 0;
1096                 }
1097             }
1098             // reached end of byte array or hit padding '=' characters.
1099             if (shiftto == 6) {
1100                 dst[dp++] = (byte)(bits >> 16);
1101             } else if (shiftto == 0) {
1102                 dst[dp++] = (byte)(bits >> 16);
1103                 dst[dp++] = (byte)(bits >>  8);
1104             } else if (shiftto == 12) {
1105                 throw new IllegalArgumentException(
1106                     "Last unit does not have enough valid bits");
1107             }
1108             // anything left is invalid, if is not MIME.
1109             // if MIME, ignore all non-base64 character
1110             while (sp < sl) {
1111                 if (isMIME && base64[src[sp++]] < 0)
1112                     continue;
1113                 throw new IllegalArgumentException(
1114                     "Input byte array has incorrect ending byte at " + sp);
1115             }
1116             return dp;
1117         }
1118     }
1119 
1120     /*
1121      * An output stream for encoding bytes into the Base64.
1122      */
1123     private static class EncOutputStream extends FilterOutputStream {
1124 
1125         private int leftover = 0;
1126         private int b0, b1, b2;
1127         private boolean closed = false;
1128 
1129         private final char[] base64;    // byte->base64 mapping
1130         private final byte[] newline;   // line separator, if needed
1131         private final int linemax;
1132         private int linepos = 0;
1133 
1134         EncOutputStream(OutputStream os,
1135                         char[] base64, byte[] newline, int linemax) {
1136             super(os);
1137             this.base64 = base64;
1138             this.newline = newline;
1139             this.linemax = linemax;
1140         }
1141 
1142         @Override
1143         public void write(int b) throws IOException {
1144             byte[] buf = new byte[1];
1145             buf[0] = (byte)(b & 0xff);
1146             write(buf, 0, 1);
1147         }
1148 
1149         private void checkNewline() throws IOException {
1150             if (linepos == linemax) {
1151                 out.write(newline);
1152                 linepos = 0;
1153             }
1154         }
1155 
1156         @Override
1157         public void write(byte[] b, int off, int len) throws IOException {
1158             if (closed)
1159                 throw new IOException("Stream is closed");
1160             if (off < 0 || len < 0 || off + len > b.length)
1161                 throw new ArrayIndexOutOfBoundsException();
1162             if (len == 0)
1163                 return;
1164             if (leftover != 0) {
1165                 if (leftover == 1) {
1166                     b1 = b[off++] & 0xff;
1167                     len--;
1168                     if (len == 0) {
1169                         leftover++;
1170                         return;
1171                     }
1172                 }
1173                 b2 = b[off++] & 0xff;
1174                 len--;
1175                 checkNewline();
1176                 out.write(base64[b0 >> 2]);
1177                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1178                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
1179                 out.write(base64[b2 & 0x3f]);
1180                 linepos += 4;
1181             }
1182             int nBits24 = len / 3;
1183             leftover = len - (nBits24 * 3);
1184             while (nBits24-- > 0) {
1185                 checkNewline();
1186                 int bits = (b[off++] & 0xff) << 16 |
1187                            (b[off++] & 0xff) <<  8 |
1188                            (b[off++] & 0xff);
1189                 out.write(base64[(bits >>> 18) & 0x3f]);
1190                 out.write(base64[(bits >>> 12) & 0x3f]);
1191                 out.write(base64[(bits >>> 6)  & 0x3f]);
1192                 out.write(base64[bits & 0x3f]);
1193                 linepos += 4;
1194            }
1195             if (leftover == 1) {
1196                 b0 = b[off++] & 0xff;
1197             } else if (leftover == 2) {
1198                 b0 = b[off++] & 0xff;
1199                 b1 = b[off++] & 0xff;
1200             }
1201         }
1202 
1203         @Override
1204         public void close() throws IOException {
1205             if (!closed) {
1206                 closed = true;
1207                 if (leftover == 1) {
1208                     checkNewline();
1209                     out.write(base64[b0 >> 2]);
1210                     out.write(base64[(b0 << 4) & 0x3f]);
1211                     out.write('=');
1212                     out.write('=');
1213                 } else if (leftover == 2) {
1214                     checkNewline();
1215                     out.write(base64[b0 >> 2]);
1216                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1217                     out.write(base64[(b1 << 2) & 0x3f]);
1218                     out.write('=');
1219                 }
1220                 leftover = 0;
1221                 out.close();
1222             }
1223         }
1224     }
1225 
1226     /*
1227      * An input stream for decoding Base64 bytes
1228      */
1229     private static class DecInputStream extends InputStream {
1230 
1231         private final InputStream is;
1232         private final boolean isMIME;
1233         private final int[] base64;      // base64 -> byte mapping
1234         private int bits = 0;            // 24-bit buffer for decoding
1235         private int nextin = 18;         // next available "off" in "bits" for input;
1236                                          // -> 18, 12, 6, 0
1237         private int nextout = -8;        // next available "off" in "bits" for output;
1238                                          // -> 8, 0, -8 (no byte for output)
1239         private boolean eof = false;
1240         private boolean closed = false;
1241 
1242         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
1243             this.is = is;
1244             this.base64 = base64;
1245             this.isMIME = isMIME;
1246         }
1247 
1248         private byte[] sbBuf = new byte[1];
1249 
1250         @Override
1251         public int read() throws IOException {
1252             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
1253         }
1254 
1255         @Override
1256         public int read(byte[] b, int off, int len) throws IOException {
1257             if (closed)
1258                 throw new IOException("Stream is closed");
1259             if (eof && nextout < 0)    // eof and no leftover
1260                 return -1;
1261             if (off < 0 || len < 0 || len > b.length - off)
1262                 throw new IndexOutOfBoundsException();
1263             int oldOff = off;
1264             if (nextout >= 0) {       // leftover output byte(s) in bits buf
1265                 do {
1266                     if (len == 0)
1267                         return off - oldOff;
1268                     b[off++] = (byte)(bits >> nextout);
1269                     len--;
1270                     nextout -= 8;
1271                 } while (nextout >= 0);
1272                 bits = 0;
1273             }
1274             while (len > 0) {
1275                 int v = is.read();
1276                 if (v == -1) {
1277                     eof = true;
1278                     if (nextin != 18) {
1279                         if (nextin == 12)
1280                             throw new IOException("Base64 stream has one un-decoded dangling byte.");
1281                         // treat ending xx/xxx without padding character legal.
1282                         // same logic as v == 'v' below
1283                         b[off++] = (byte)(bits >> (16));
1284                         len--;
1285                         if (nextin == 0) {           // only one padding byte
1286                             if (len == 0) {          // no enough output space
1287                                 bits >>= 8;          // shift to lowest byte
1288                                 nextout = 0;
1289                             } else {
1290                                 b[off++] = (byte) (bits >>  8);
1291                             }
1292                         }
1293                     }
1294                     if (off == oldOff)
1295                         return -1;
1296                     else
1297                         return off - oldOff;
1298                 }
1299                 if (v == '=') {                  // padding byte(s)
1300                     if (nextin != 6 && nextin != 0) {
1301                         throw new IOException("Illegal base64 ending sequence:" + nextin);
1302                     }
1303                     b[off++] = (byte)(bits >> (16));
1304                     len--;
1305                     if (nextin == 0) {           // only one padding byte
1306                         if (len == 0) {          // no enough output space
1307                             bits >>= 8;          // shift to lowest byte
1308                             nextout = 0;
1309                         } else {
1310                             b[off++] = (byte) (bits >>  8);
1311                         }
1312                     }
1313                     eof = true;
1314                     break;
1315                 }
1316                 if ((v = base64[v]) == -1) {
1317                     if (isMIME)                 // skip if for rfc2045
1318                         continue;
1319                     else
1320                         throw new IOException("Illegal base64 character " +
1321                             Integer.toString(v, 16));
1322                 }
1323                 bits |= (v << nextin);
1324                 if (nextin == 0) {
1325                     nextin = 18;    // clear for next
1326                     nextout = 16;
1327                     while (nextout >= 0) {
1328                         b[off++] = (byte)(bits >> nextout);
1329                         len--;
1330                         nextout -= 8;
1331                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
1332                             return off - oldOff;
1333                         }
1334                     }
1335                     bits = 0;
1336                 } else {
1337                     nextin -= 6;
1338                 }
1339             }
1340             return off - oldOff;
1341         }
1342 
1343         @Override
1344         public int available() throws IOException {
1345             if (closed)
1346                 throw new IOException("Stream is closed");
1347             return is.available();   // TBD:
1348         }
1349 
1350         @Override
1351         public void close() throws IOException {
1352             if (!closed) {
1353                 closed = true;
1354                 is.close();
1355             }
1356         }
1357     }
1358 }