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