1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util;
  27 
  28 import java.io.FilterOutputStream;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.io.OutputStream;
  32 import java.nio.ByteBuffer;
  33 import java.nio.charset.StandardCharsets;
  34 
  35 /**
  36  * This class consists exclusively of static methods for obtaining
  37  * encoders and decoders for the Base64 encoding scheme. The
  38  * implementation of this class supports the following types of Base64
  39  * as specified in
  40  * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
  41  * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
  42  *
  43  * <p>
  44  * <ul>
  45  * <a name="basic">
  46  * <li><b>Basic</b>
  47  * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
  48  *     RFC 4648 and RFC 2045 for encoding and decoding operation.
  49  *     The encoder does not add any line feed (line separator)
  50  *     character. The decoder rejects data that contains characters
  51  *     outside the base64 alphabet.</p></li>
  52  *
  53  * <a name="url">
  54  * <li><b>URL and Filename safe</b>
  55  * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
  56  *     in Table 2 of RFC 4648 for encoding and decoding. The
  57  *     encoder does not add any line feed (line separator) character.
  58  *     The decoder rejects data that contains characters outside the
  59  *     base64 alphabet.</p></li>
  60  *
  61  * <a name="mime">
  62  * <li><b>MIME</b>
  63  * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
  64  *     RFC 2045 for encoding and decoding operation. The encoded output
  65  *     must be represented in lines of no more than 76 characters each
  66  *     and uses a carriage return {@code '\r'} followed immediately by
  67  *     a linefeed {@code '\n'} as the line separator. All line separators
  68  *     or other characters not found in the base64 alphabet table are
  69  *     ignored in decoding operation.</p></li>
  70  * </ul>
  71  *
  72  * <p> Unless otherwise noted, passing a {@code null} argument to a
  73  * method of this class will cause a {@link java.lang.NullPointerException
  74  * NullPointerException} to be thrown.
  75  *
  76  * @author  Xueming Shen
  77  * @since   1.8
  78  */
  79 
  80 public class Base64 {
  81 
  82     private Base64() {}
  83 
  84     /**
  85      * Returns a {@link Encoder} that encodes using the
  86      * <a href="#basic">Basic</a> type base64 encoding scheme.
  87      *
  88      * @return  A Base64 encoder.
  89      */
  90     public static Encoder getEncoder() {
  91          return Encoder.RFC4648;
  92     }
  93 
  94     /**
  95      * Returns a {@link Encoder} that encodes using the
  96      * <a href="#url">URL and Filename safe</a> type base64
  97      * encoding scheme.
  98      *
  99      * @return  A Base64 encoder.
 100      */
 101     public static Encoder getUrlEncoder() {
 102          return Encoder.RFC4648_URLSAFE;
 103     }
 104 
 105     /**
 106      * Returns a {@link Encoder} that encodes using the
 107      * <a href="#mime">MIME</a> type base64 encoding scheme.
 108      *
 109      * @return  A Base64 encoder.
 110      */
 111     public static Encoder getMimeEncoder() {
 112         return Encoder.RFC2045;
 113     }
 114 
 115     /**
 116      * Returns a {@link Encoder} that encodes using the
 117      * <a href="#mime">MIME</a> type base64 encoding scheme
 118      * with specified line length and line separators.
 119      *
 120      * @param   lineLength
 121      *          the length of each output line (rounded down to nearest multiple
 122      *          of 4). If {@code lineLength <= 0} the output will not be separated
 123      *          in lines
 124      * @param   lineSeparator
 125      *          the line separator for each output line
 126      *
 127      * @return  A Base64 encoder.
 128      *
 129      * @throws  IllegalArgumentException if {@code lineSeparator} includes any
 130      *          character of "The Base64 Alphabet" as specified in Table 1 of
 131      *          RFC 2045.
 132      */
 133     public static Encoder getEncoder(int lineLength, byte[] lineSeparator) {
 134          Objects.requireNonNull(lineSeparator);
 135          int[] base64 = Decoder.fromBase64;
 136          for (byte b : lineSeparator) {
 137              if (base64[b & 0xff] != -1)
 138                  throw new IllegalArgumentException(
 139                      "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
 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> Instances of {@link Decoder} class are safe for use by
 618      * multiple concurrent threads.
 619      *
 620      * <p> Unless otherwise noted, passing a {@code null} argument to
 621      * a method of this class will cause a
 622      * {@link java.lang.NullPointerException NullPointerException} to
 623      * be thrown.
 624      *
 625      * @see     Encoder
 626      * @since   1.8
 627      */
 628     public static class Decoder {
 629 
 630         private final boolean isURL;
 631         private final boolean isMIME;
 632 
 633         private Decoder(boolean isURL, boolean isMIME) {
 634             this.isURL = isURL;
 635             this.isMIME = isMIME;
 636         }
 637 
 638         /**
 639          * Lookup table for decoding unicode characters drawn from the
 640          * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
 641          * their 6-bit positive integer equivalents.  Characters that
 642          * are not in the Base64 alphabet but fall within the bounds of
 643          * the array are encoded to -1.
 644          *
 645          */
 646         private static final int[] fromBase64 = new int[256];
 647         static {
 648             Arrays.fill(fromBase64, -1);
 649             for (int i = 0; i < Encoder.toBase64.length; i++)
 650                 fromBase64[Encoder.toBase64[i]] = i;
 651             fromBase64['='] = -2;
 652         }
 653 
 654         /**
 655          * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
 656          * as specified in Table2 of the RFC 4648.
 657          */
 658         private static final int[] fromBase64URL = new int[256];
 659 
 660         static {
 661             Arrays.fill(fromBase64URL, -1);
 662             for (int i = 0; i < Encoder.toBase64URL.length; i++)
 663                 fromBase64URL[Encoder.toBase64URL[i]] = i;
 664             fromBase64URL['='] = -2;
 665         }
 666 
 667         static final Decoder RFC4648         = new Decoder(false, false);
 668         static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
 669         static final Decoder RFC2045         = new Decoder(false, true);
 670 
 671         /**
 672          * Decodes all bytes from the input byte array using the {@link Base64}
 673          * encoding scheme, writing the results into a newly-allocated output
 674          * byte array. The returned byte array is of the length of the resulting
 675          * bytes.
 676          *
 677          * @param   src
 678          *          the byte array to decode
 679          *
 680          * @return  A newly-allocated byte array containing the decoded bytes.
 681          *
 682          * @throws  IllegalArgumentException
 683          *          if {@code src} is not in valid Base64 scheme
 684          */
 685         public byte[] decode(byte[] src) {
 686             byte[] dst = new byte[outLength(src, 0, src.length)];
 687             int ret = decode0(src, 0, src.length, dst);
 688             if (ret != dst.length) {
 689                 dst = Arrays.copyOf(dst, ret);
 690             }
 691             return dst;
 692         }
 693 
 694         /**
 695          * Decodes a Base64 encoded String into a newly-allocated byte array
 696          * using the {@link Base64} encoding scheme.
 697          *
 698          * <p> An invocation of this method has exactly the same effect as invoking
 699          * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
 700          *
 701          * @param   src
 702          *          the string to decode
 703          *
 704          * @return  A newly-allocated byte array containing the decoded bytes.
 705          *
 706          * @throws  IllegalArgumentException
 707          *          if {@code src} is not in valid Base64 scheme
 708          */
 709         public byte[] decode(String src) {
 710             return decode(src.getBytes(StandardCharsets.ISO_8859_1));
 711         }
 712 
 713         /**
 714          * Decodes all bytes from the input byte array using the {@link Base64}
 715          * encoding scheme, writing the results into the given output byte array,
 716          * starting at offset 0.
 717          *
 718          * <p> It is the responsibility of the invoker of this method to make
 719          * sure the output byte array {@code dst} has enough space for decoding
 720          * all bytes from the input byte array. No bytes will be be written to
 721          * the output byte array if the output byte array is not big enough.
 722          *
 723          * <p> If the input byte array is not in valid Base64 encoding scheme
 724          * then some bytes may have been written to the output byte array before
 725          * IllegalargumentException is thrown.
 726          *
 727          * @param   src
 728          *          the byte array to decode
 729          * @param   dst
 730          *          the output byte array
 731          *
 732          * @return  The number of bytes written to the output byte array
 733          *
 734          * @throws  IllegalArgumentException
 735          *          if {@code src} is not in valid Base64 scheme, or {@code dst}
 736          *          does not have enough space for decoding all input bytes.
 737          */
 738         public int decode(byte[] src, byte[] dst) {
 739             int len = outLength(src, 0, src.length);
 740             if (dst.length < len)
 741                 throw new IllegalArgumentException(
 742                     "Output byte array is too small for decoding all input bytes");
 743             return decode0(src, 0, src.length, dst);
 744         }
 745 
 746         /**
 747          * Decodes all bytes from the input byte buffer using the {@link Base64}
 748          * encoding scheme, writing the results into a newly-allocated ByteBuffer.
 749          *
 750          * <p> Upon return, the source buffer's position will be updated to
 751          * its limit; its limit will not have been changed. The returned
 752          * output buffer's position will be zero and its limit will be the
 753          * number of resulting decoded bytes
 754          *
 755          * @param   buffer
 756          *          the ByteBuffer to decode
 757          *
 758          * @return  A newly-allocated byte buffer containing the decoded bytes
 759          *
 760          * @throws  IllegalArgumentException
 761          *          if {@code src} is not in valid Base64 scheme.
 762          */
 763         public ByteBuffer decode(ByteBuffer buffer) {
 764             int pos0 = buffer.position();
 765             try {
 766                 byte[] src;
 767                 int sp, sl;
 768                 if (buffer.hasArray()) {
 769                     src = buffer.array();
 770                     sp = buffer.arrayOffset() + buffer.position();
 771                     sl = buffer.arrayOffset() + buffer.limit();
 772                     buffer.position(buffer.limit());
 773                 } else {
 774                     src = new byte[buffer.remaining()];
 775                     buffer.get(src);
 776                     sp = 0;
 777                     sl = src.length;
 778                 }
 779                 byte[] dst = new byte[outLength(src, sp, sl)];
 780                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
 781             } catch (IllegalArgumentException iae) {
 782                 buffer.position(pos0);
 783                 throw iae;
 784             }
 785         }
 786 
 787         /**
 788          * Decodes as many bytes as possible from the input byte buffer
 789          * using the {@link Base64} encoding scheme, writing the resulting
 790          * bytes to the given output byte buffer.
 791          *
 792          * <p>The buffers are read from, and written to, starting at their
 793          * current positions. Upon return, the input and output buffers'
 794          * positions will be advanced to reflect the bytes read and written,
 795          * but their limits will not be modified.
 796          *
 797          * <p> If the input buffer is not in valid Base64 encoding scheme
 798          * then some bytes may have been written to the output buffer
 799          * before IllegalArgumentException is thrown. The positions of
 800          * both input and output buffer will not be advanced in this case.
 801          *
 802          * <p>The decoding operation will end and return if all remaining
 803          * bytes in the input buffer have been decoded and written to the
 804          * output buffer.
 805          *
 806          * <p> The decoding operation will stop and return if the output
 807          * buffer has insufficient space to decode any more input bytes.
 808          * The decoding operation can be continued, if there is more bytes
 809          * in input buffer to be decoded, by invoking this method again with
 810          * an output buffer that has more {@linkplain java.nio.Buffer#remaining
 811          * remaining} bytes. This is typically done by draining any decoded
 812          * bytes from the output buffer.
 813          *
 814          * <p><b>Recommended Usage Example</b>
 815          * <pre>
 816          *    ByteBuffer src = ...;
 817          *    ByteBuffer dst = ...;
 818          *    Base64.Decoder dec = Base64.getDecoder();
 819          *
 820          *    while (src.hasRemaining()) {
 821          *
 822          *        // prepare the output byte buffer
 823          *        dst.clear();
 824          *        dec.decode(src, dst);
 825          *
 826          *        // read bytes from the output buffer
 827          *        dst.flip();
 828          *        ...
 829          *    }
 830          * </pre>
 831          *
 832          * @param   src
 833          *          the input byte buffer to decode
 834          * @param   dst
 835          *          the output byte buffer
 836          *
 837          * @return  The number of bytes written to the output byte buffer during
 838          *          this decoding invocation
 839          *
 840          * @throws  IllegalArgumentException
 841          *          if {@code src} is not in valid Base64 scheme.
 842          */
 843         public int decode(ByteBuffer src, ByteBuffer dst) {
 844             int sp0 = src.position();
 845             int dp0 = dst.position();
 846             try {
 847                 if (src.hasArray() && dst.hasArray())
 848                     return decodeArray(src, dst);
 849                 return decodeBuffer(src, dst);
 850             } catch (IllegalArgumentException iae) {
 851                 src.position(sp0);
 852                 dst.position(dp0);
 853                 throw iae;
 854             }
 855         }
 856 
 857         /**
 858          * Returns an input stream for decoding {@link Base64} encoded byte stream.
 859          *
 860          * <p> If the input byte stream is not in valid Base64 encoding scheme
 861          * then IllegalArgumentException will be thrown when reading any invalid
 862          * Base64 byte(s) from the returned input stream.
 863          *
 864          * <p> Closing the returned input stream will close the underlying
 865          * input stream.
 866          *
 867          * @param   is
 868          *          the input stream
 869          *
 870          * @return  the input stream for decoding the specified Base64 encoded
 871          *          byte stream
 872          */
 873         public InputStream wrap(InputStream is) {
 874             Objects.requireNonNull(is);
 875             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 876         }
 877 
 878         private int decodeArray(ByteBuffer src, ByteBuffer dst) {
 879             int[] base64 = isURL ? fromBase64URL : fromBase64;
 880             int   bits = 0;
 881             int   shiftto = 18;       // pos of first byte of 4-byte atom
 882             byte[] sa = src.array();
 883             int    sp = src.arrayOffset() + src.position();
 884             int    sl = src.arrayOffset() + src.limit();
 885             byte[] da = dst.array();
 886             int    dp = dst.arrayOffset() + dst.position();
 887             int    dl = dst.arrayOffset() + dst.limit();
 888             int    dp0 = dp;
 889             int    mark = sp;
 890             boolean padding = false;
 891             try {
 892                 while (sp < sl) {
 893                     int b = sa[sp++] & 0xff;
 894                     if ((b = base64[b]) < 0) {
 895                         if (b == -2) {   // padding byte
 896                             padding = true;
 897                             break;
 898                         }
 899                         if (isMIME)     // skip if for rfc2045
 900                             continue;
 901                         else
 902                             throw new IllegalArgumentException(
 903                                 "Illegal base64 character " +
 904                                 Integer.toString(sa[sp - 1], 16));
 905                     }
 906                     bits |= (b << shiftto);
 907                     shiftto -= 6;
 908                     if (shiftto < 0) {
 909                         if (dl < dp + 3)
 910                             return dp - dp0;
 911                         da[dp++] = (byte)(bits >> 16);
 912                         da[dp++] = (byte)(bits >>  8);
 913                         da[dp++] = (byte)(bits);
 914                         shiftto = 18;
 915                         bits = 0;
 916                         mark = sp;
 917                     }
 918                 }
 919                 if (shiftto == 6) {
 920                     if (dl - dp < 1)
 921                         return dp - dp0;
 922                     if (padding && (sp + 1 != sl || sa[sp++] != '='))
 923                         throw new IllegalArgumentException(
 924                             "Input buffer has wrong 4-byte ending unit");
 925                     da[dp++] = (byte)(bits >> 16);
 926                     mark = sp;
 927                 } else if (shiftto == 0) {
 928                     if (dl - dp < 2)
 929                         return dp - dp0;
 930                     if (padding && sp != sl)
 931                         throw new IllegalArgumentException(
 932                             "Input buffer has wrong 4-byte ending unit");
 933                     da[dp++] = (byte)(bits >> 16);
 934                     da[dp++] = (byte)(bits >>  8);
 935                     mark = sp;
 936                 } else if (padding || shiftto != 18) {
 937                     throw new IllegalArgumentException(
 938                         "Last unit does not have enough valid bits");
 939                 }
 940                 return dp - dp0;
 941             } finally {
 942                 src.position(mark);
 943                 dst.position(dp);
 944             }
 945         }
 946 
 947         private int decodeBuffer(ByteBuffer src, ByteBuffer dst) {
 948             int[] base64 = isURL ? fromBase64URL : fromBase64;
 949             int   bits = 0;
 950             int   shiftto = 18;       // pos of first byte of 4-byte atom
 951             int    sp = src.position();
 952             int    sl = src.limit();
 953             int    dp = dst.position();
 954             int    dl = dst.limit();
 955             int    dp0 = dp;
 956             int    mark = sp;
 957             boolean padding = false;
 958 
 959             try {
 960                 while (sp < sl) {
 961                     int b = src.get(sp++) & 0xff;
 962                     if ((b = base64[b]) < 0) {
 963                         if (b == -2) {  // padding byte
 964                             padding = true;
 965                             break;
 966                         }
 967                         if (isMIME)     // skip if for rfc2045
 968                             continue;
 969                         else
 970                             throw new IllegalArgumentException(
 971                                 "Illegal base64 character " +
 972                                 Integer.toString(src.get(sp - 1), 16));
 973                     }
 974                     bits |= (b << shiftto);
 975                     shiftto -= 6;
 976                     if (shiftto < 0) {
 977                         if (dl < dp + 3)
 978                             return dp - dp0;
 979                         dst.put(dp++, (byte)(bits >> 16));
 980                         dst.put(dp++, (byte)(bits >>  8));
 981                         dst.put(dp++, (byte)(bits));
 982                         shiftto = 18;
 983                         bits = 0;
 984                         mark = sp;
 985                     }
 986                 }
 987                 if (shiftto == 6) {
 988                     if (dl - dp < 1)
 989                         return dp - dp0;
 990                     if (padding && (sp + 1 != sl || src.get(sp++) != '='))
 991                         throw new IllegalArgumentException(
 992                             "Input buffer has wrong 4-byte ending unit");
 993                      dst.put(dp++, (byte)(bits >> 16));
 994                      mark = sp;
 995                 } else if (shiftto == 0) {
 996                     if (dl - dp < 2)
 997                         return dp - dp0;
 998                     if (padding && sp != sl)
 999                         throw new IllegalArgumentException(
1000                             "Input buffer has wrong 4-byte ending unit");
1001                     dst.put(dp++, (byte)(bits >> 16));
1002                     dst.put(dp++, (byte)(bits >>  8));
1003                     mark = sp;
1004                 } else if (padding || shiftto != 18) {
1005                     throw new IllegalArgumentException(
1006                         "Last unit does not have enough valid bits");
1007                 }
1008                 return dp - dp0;
1009             } finally {
1010                 src.position(mark);
1011                 dst.position(dp);
1012             }
1013         }
1014 
1015         private int outLength(byte[] src, int sp, int sl) {
1016             int[] base64 = isURL ? fromBase64URL : fromBase64;
1017             int paddings = 0;
1018             int len = sl - sp;
1019             if (len == 0)
1020                 return 0;
1021             if (len < 2) {
1022                 if (isMIME && base64[0] == -1)
1023                     return 0;
1024                 throw new IllegalArgumentException(
1025                     "Input byte[] should at least have 2 bytes for base64 bytes");
1026             }
1027             if (src[sl - 1] == '=') {
1028                 paddings++;
1029                 if (src[sl - 2] == '=')
1030                     paddings++;
1031             }
1032             if (isMIME) {
1033                 // scan all bytes to fill out all non-alphabet. a performance
1034                 // trade-off of pre-scan or Arrays.copyOf
1035                 int n = 0;
1036                 while (sp < sl) {
1037                     int b = src[sp++] & 0xff;
1038                     if (b == '=')
1039                         break;
1040                     if ((b = base64[b]) == -1)
1041                         n++;
1042                 }
1043                 len -= n;
1044             }
1045             if (paddings == 0 && (len & 0x3) !=  0)
1046                 paddings = 4 - (len & 0x3);
1047             return 3 * ((len + 3) / 4) - paddings;
1048         }
1049 
1050         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
1051             int[] base64 = isURL ? fromBase64URL : fromBase64;
1052             int dp = 0;
1053             int bits = 0;
1054             int shiftto = 18;       // pos of first byte of 4-byte atom
1055             boolean padding = false;
1056             while (sp < sl) {
1057                 int b = src[sp++] & 0xff;
1058                 if ((b = base64[b]) < 0) {
1059                     if (b == -2) {     // padding byte
1060                         padding = true;
1061                         break;
1062                     }
1063                     if (isMIME)    // skip if for rfc2045
1064                         continue;
1065                     else
1066                         throw new IllegalArgumentException(
1067                             "Illegal base64 character " +
1068                             Integer.toString(src[sp - 1], 16));
1069                 }
1070                 bits |= (b << shiftto);
1071                 shiftto -= 6;
1072                 if (shiftto < 0) {
1073                     dst[dp++] = (byte)(bits >> 16);
1074                     dst[dp++] = (byte)(bits >>  8);
1075                     dst[dp++] = (byte)(bits);
1076                     shiftto = 18;
1077                     bits = 0;
1078                 }
1079             }
1080             // reach end of byte arry or hit padding '=' characters.
1081             // if '=' presents, they must be the last one or two.
1082             if (shiftto == 6) {           // xx==
1083                 if (padding && (sp + 1 != sl || src[sp] != '='))
1084                     throw new IllegalArgumentException(
1085                         "Input byte array has wrong 4-byte ending unit");
1086                 dst[dp++] = (byte)(bits >> 16);
1087             } else if (shiftto == 0) {    // xxx=
1088                 if (padding && sp != sl)
1089                     throw new IllegalArgumentException(
1090                         "Input byte array has wrong 4-byte ending unit");
1091                 dst[dp++] = (byte)(bits >> 16);
1092                 dst[dp++] = (byte)(bits >>  8);
1093             } else if (padding || shiftto != 18) {
1094                     throw new IllegalArgumentException(
1095                         "last unit does not have enough bytes");
1096             }
1097             return dp;
1098         }
1099     }
1100 
1101     /*
1102      * An output stream for encoding bytes into the Base64.
1103      */
1104     private static class EncOutputStream extends FilterOutputStream {
1105 
1106         private int leftover = 0;
1107         private int b0, b1, b2;
1108         private boolean closed = false;
1109 
1110         private final char[] base64;    // byte->base64 mapping
1111         private final byte[] newline;   // line separator, if needed
1112         private final int linemax;
1113         private int linepos = 0;
1114 
1115         EncOutputStream(OutputStream os,
1116                         char[] base64, byte[] newline, int linemax) {
1117             super(os);
1118             this.base64 = base64;
1119             this.newline = newline;
1120             this.linemax = linemax;
1121         }
1122 
1123         @Override
1124         public void write(int b) throws IOException {
1125             byte[] buf = new byte[1];
1126             buf[0] = (byte)(b & 0xff);
1127             write(buf, 0, 1);
1128         }
1129 
1130         private void checkNewline() throws IOException {
1131             if (linepos == linemax) {
1132                 out.write(newline);
1133                 linepos = 0;
1134             }
1135         }
1136 
1137         @Override
1138         public void write(byte[] b, int off, int len) throws IOException {
1139             if (closed)
1140                 throw new IOException("Stream is closed");
1141             if (off < 0 || len < 0 || off + len > b.length)
1142                 throw new ArrayIndexOutOfBoundsException();
1143             if (len == 0)
1144                 return;
1145             if (leftover != 0) {
1146                 if (leftover == 1) {
1147                     b1 = b[off++] & 0xff;
1148                     len--;
1149                     if (len == 0) {
1150                         leftover++;
1151                         return;
1152                     }
1153                 }
1154                 b2 = b[off++] & 0xff;
1155                 len--;
1156                 checkNewline();
1157                 out.write(base64[b0 >> 2]);
1158                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1159                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
1160                 out.write(base64[b2 & 0x3f]);
1161                 linepos += 4;
1162             }
1163             int nBits24 = len / 3;
1164             leftover = len - (nBits24 * 3);
1165             while (nBits24-- > 0) {
1166                 checkNewline();
1167                 int bits = (b[off++] & 0xff) << 16 |
1168                            (b[off++] & 0xff) <<  8 |
1169                            (b[off++] & 0xff);
1170                 out.write(base64[(bits >>> 18) & 0x3f]);
1171                 out.write(base64[(bits >>> 12) & 0x3f]);
1172                 out.write(base64[(bits >>> 6)  & 0x3f]);
1173                 out.write(base64[bits & 0x3f]);
1174                 linepos += 4;
1175            }
1176             if (leftover == 1) {
1177                 b0 = b[off++] & 0xff;
1178             } else if (leftover == 2) {
1179                 b0 = b[off++] & 0xff;
1180                 b1 = b[off++] & 0xff;
1181             }
1182         }
1183 
1184         @Override
1185         public void close() throws IOException {
1186             if (!closed) {
1187                 closed = true;
1188                 if (leftover == 1) {
1189                     checkNewline();
1190                     out.write(base64[b0 >> 2]);
1191                     out.write(base64[(b0 << 4) & 0x3f]);
1192                     out.write('=');
1193                     out.write('=');
1194                 } else if (leftover == 2) {
1195                     checkNewline();
1196                     out.write(base64[b0 >> 2]);
1197                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1198                     out.write(base64[(b1 << 2) & 0x3f]);
1199                     out.write('=');
1200                 }
1201                 leftover = 0;
1202                 out.close();
1203             }
1204         }
1205     }
1206 
1207     /*
1208      * An input stream for decoding Base64 bytes
1209      */
1210     private static class DecInputStream extends InputStream {
1211 
1212         private final InputStream is;
1213         private final boolean isMIME;
1214         private final int[] base64;      // base64 -> byte mapping
1215         private int bits = 0;            // 24-bit buffer for decoding
1216         private int nextin = 18;         // next available "off" in "bits" for input;
1217                                          // -> 18, 12, 6, 0
1218         private int nextout = -8;        // next available "off" in "bits" for output;
1219                                          // -> 8, 0, -8 (no byte for output)
1220         private boolean eof = false;
1221         private boolean closed = false;
1222 
1223         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
1224             this.is = is;
1225             this.base64 = base64;
1226             this.isMIME = isMIME;
1227         }
1228 
1229         private byte[] sbBuf = new byte[1];
1230 
1231         @Override
1232         public int read() throws IOException {
1233             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
1234         }
1235 
1236         @Override
1237         public int read(byte[] b, int off, int len) throws IOException {
1238             if (closed)
1239                 throw new IOException("Stream is closed");
1240             if (eof && nextout < 0)    // eof and no leftover
1241                 return -1;
1242             if (off < 0 || len < 0 || len > b.length - off)
1243                 throw new IndexOutOfBoundsException();
1244             int oldOff = off;
1245             if (nextout >= 0) {       // leftover output byte(s) in bits buf
1246                 do {
1247                     if (len == 0)
1248                         return off - oldOff;
1249                     b[off++] = (byte)(bits >> nextout);
1250                     len--;
1251                     nextout -= 8;
1252                 } while (nextout >= 0);
1253                 bits = 0;
1254             }
1255             while (len > 0) {
1256                 int v = is.read();
1257                 if (v == -1) {
1258                     eof = true;
1259                     if (nextin != 18)
1260                         throw new IllegalArgumentException(
1261                             "Base64 stream has un-decoded dangling byte(s).");
1262                     if (off == oldOff)
1263                         return -1;
1264                     else
1265                         return off - oldOff;
1266                 }
1267                 if (v == '=') {                  // padding byte(s)
1268                     if (nextin != 6 && nextin != 0) {
1269                         throw new IllegalArgumentException(
1270                             "Illegal base64 ending sequence:" + nextin);
1271                     }
1272                     b[off++] = (byte)(bits >> (16));
1273                     len--;
1274                     if (nextin == 0) {           // only one padding byte
1275                         if (len == 0) {          // no enough output space
1276                             bits >>= 8;          // shift to lowest byte
1277                             nextout = 0;
1278                         } else {
1279                             b[off++] = (byte) (bits >>  8);
1280                         }
1281                     }
1282                     eof = true;
1283                     break;
1284                 }
1285                 if ((v = base64[v]) == -1) {
1286                     if (isMIME)                 // skip if for rfc2045
1287                         continue;
1288                     else
1289                         throw new IllegalArgumentException(
1290                             "Illegal base64 character " + Integer.toString(v, 16));
1291                 }
1292                 bits |= (v << nextin);
1293                 if (nextin == 0) {
1294                     nextin = 18;    // clear for next
1295                     nextout = 16;
1296                     while (nextout >= 0) {
1297                         b[off++] = (byte)(bits >> nextout);
1298                         len--;
1299                         nextout -= 8;
1300                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
1301                             return off - oldOff;
1302                         }
1303                     }
1304                     bits = 0;
1305                 } else {
1306                     nextin -= 6;
1307                 }
1308             }
1309             return off - oldOff;
1310         }
1311 
1312         @Override
1313         public int available() throws IOException {
1314             if (closed)
1315                 throw new IOException("Stream is closed");
1316             return is.available();   // TBD:
1317         }
1318 
1319         @Override
1320         public void close() throws IOException {
1321             if (!closed) {
1322                 closed = true;
1323                 is.close();
1324             }
1325         }
1326     }
1327 }