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