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> Closing the returned input stream will close the underlying
 861          * input stream.
 862          *
 863          * @param   is
 864          *          the input stream
 865          *
 866          * @return  the input stream for decoding the specified Base64 encoded
 867          *          byte stream
 868          */
 869         public InputStream wrap(InputStream is) {
 870             Objects.requireNonNull(is);
 871             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 872         }
 873 
 874         private int decodeArray(ByteBuffer src, ByteBuffer dst) {
 875             int[] base64 = isURL ? fromBase64URL : fromBase64;
 876             int   bits = 0;
 877             int   shiftto = 18;       // pos of first byte of 4-byte atom
 878             byte[] sa = src.array();
 879             int    sp = src.arrayOffset() + src.position();
 880             int    sl = src.arrayOffset() + src.limit();
 881             byte[] da = dst.array();
 882             int    dp = dst.arrayOffset() + dst.position();
 883             int    dl = dst.arrayOffset() + dst.limit();
 884             int    dp0 = dp;
 885             int    mark = sp;
 886             boolean padding = false;
 887             try {
 888                 while (sp < sl) {
 889                     int b = sa[sp++] & 0xff;
 890                     if ((b = base64[b]) < 0) {
 891                         if (b == -2) {   // padding byte
 892                             padding = true;
 893                             break;
 894                         }
 895                         if (isMIME)     // skip if for rfc2045
 896                             continue;
 897                         else
 898                             throw new IllegalArgumentException(
 899                                 "Illegal base64 character " +
 900                                 Integer.toString(sa[sp - 1], 16));
 901                     }
 902                     bits |= (b << shiftto);
 903                     shiftto -= 6;
 904                     if (shiftto < 0) {
 905                         if (dl < dp + 3)
 906                             return dp - dp0;
 907                         da[dp++] = (byte)(bits >> 16);
 908                         da[dp++] = (byte)(bits >>  8);
 909                         da[dp++] = (byte)(bits);
 910                         shiftto = 18;
 911                         bits = 0;
 912                         mark = sp;
 913                     }
 914                 }
 915                 if (shiftto == 6) {
 916                     if (dl - dp < 1)
 917                         return dp - dp0;
 918                     if (padding && (sp + 1 != sl || sa[sp++] != '='))
 919                         throw new IllegalArgumentException(
 920                             "Input buffer has wrong 4-byte ending unit");
 921                     da[dp++] = (byte)(bits >> 16);
 922                     mark = sp;
 923                 } else if (shiftto == 0) {
 924                     if (dl - dp < 2)
 925                         return dp - dp0;
 926                     if (padding && sp != sl)
 927                         throw new IllegalArgumentException(
 928                             "Input buffer has wrong 4-byte ending unit");
 929                     da[dp++] = (byte)(bits >> 16);
 930                     da[dp++] = (byte)(bits >>  8);
 931                     mark = sp;
 932                 } else if (padding || shiftto != 18) {
 933                     throw new IllegalArgumentException(
 934                         "Last unit does not have enough valid bits");
 935                 }
 936                 return dp - dp0;
 937             } finally {
 938                 src.position(mark);
 939                 dst.position(dp);
 940             }
 941         }
 942 
 943         private int decodeBuffer(ByteBuffer src, ByteBuffer dst) {
 944             int[] base64 = isURL ? fromBase64URL : fromBase64;
 945             int   bits = 0;
 946             int   shiftto = 18;       // pos of first byte of 4-byte atom
 947             int    sp = src.position();
 948             int    sl = src.limit();
 949             int    dp = dst.position();
 950             int    dl = dst.limit();
 951             int    dp0 = dp;
 952             int    mark = sp;
 953             boolean padding = false;
 954 
 955             try {
 956                 while (sp < sl) {
 957                     int b = src.get(sp++) & 0xff;
 958                     if ((b = base64[b]) < 0) {
 959                         if (b == -2) {  // padding byte
 960                             padding = true;
 961                             break;
 962                         }
 963                         if (isMIME)     // skip if for rfc2045
 964                             continue;
 965                         else
 966                             throw new IllegalArgumentException(
 967                                 "Illegal base64 character " +
 968                                 Integer.toString(src.get(sp - 1), 16));
 969                     }
 970                     bits |= (b << shiftto);
 971                     shiftto -= 6;
 972                     if (shiftto < 0) {
 973                         if (dl < dp + 3)
 974                             return dp - dp0;
 975                         dst.put(dp++, (byte)(bits >> 16));
 976                         dst.put(dp++, (byte)(bits >>  8));
 977                         dst.put(dp++, (byte)(bits));
 978                         shiftto = 18;
 979                         bits = 0;
 980                         mark = sp;
 981                     }
 982                 }
 983                 if (shiftto == 6) {
 984                     if (dl - dp < 1)
 985                         return dp - dp0;
 986                     if (padding && (sp + 1 != sl || src.get(sp++) != '='))
 987                         throw new IllegalArgumentException(
 988                             "Input buffer has wrong 4-byte ending unit");
 989                      dst.put(dp++, (byte)(bits >> 16));
 990                      mark = sp;
 991                 } else if (shiftto == 0) {
 992                     if (dl - dp < 2)
 993                         return dp - dp0;
 994                     if (padding && sp != sl)
 995                         throw new IllegalArgumentException(
 996                             "Input buffer has wrong 4-byte ending unit");
 997                     dst.put(dp++, (byte)(bits >> 16));
 998                     dst.put(dp++, (byte)(bits >>  8));
 999                     mark = sp;
1000                 } else if (padding || shiftto != 18) {
1001                     throw new IllegalArgumentException(
1002                         "Last unit does not have enough valid bits");
1003                 }
1004                 return dp - dp0;
1005             } finally {
1006                 src.position(mark);
1007                 dst.position(dp);
1008             }
1009         }
1010 
1011         private int outLength(byte[] src, int sp, int sl) {
1012             int[] base64 = isURL ? fromBase64URL : fromBase64;
1013             int paddings = 0;
1014             int len = sl - sp;
1015             if (len == 0)
1016                 return 0;
1017             if (len < 2) {
1018                 if (isMIME && base64[0] == -1)
1019                     return 0;
1020                 throw new IllegalArgumentException(
1021                     "Input byte[] should at least have 2 bytes for base64 bytes");
1022             }
1023             if (src[sl - 1] == '=') {
1024                 paddings++;
1025                 if (src[sl - 2] == '=')
1026                     paddings++;
1027             }
1028             if (isMIME) {
1029                 // scan all bytes to fill out all non-alphabet. a performance
1030                 // trade-off of pre-scan or Arrays.copyOf
1031                 int n = 0;
1032                 while (sp < sl) {
1033                     int b = src[sp++] & 0xff;
1034                     if (b == '=')
1035                         break;
1036                     if ((b = base64[b]) == -1)
1037                         n++;
1038                 }
1039                 len -= n;
1040             }
1041             if (paddings == 0 && (len & 0x3) !=  0)
1042                 paddings = 4 - (len & 0x3);
1043             return 3 * ((len + 3) / 4) - paddings;
1044         }
1045 
1046         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
1047             int[] base64 = isURL ? fromBase64URL : fromBase64;
1048             int dp = 0;
1049             int bits = 0;
1050             int shiftto = 18;       // pos of first byte of 4-byte atom
1051             boolean padding = false;
1052             while (sp < sl) {
1053                 int b = src[sp++] & 0xff;
1054                 if ((b = base64[b]) < 0) {
1055                     if (b == -2) {     // padding byte
1056                         padding = true;
1057                         break;
1058                     }
1059                     if (isMIME)    // skip if for rfc2045
1060                         continue;
1061                     else
1062                         throw new IllegalArgumentException(
1063                             "Illegal base64 character " +
1064                             Integer.toString(src[sp - 1], 16));
1065                 }
1066                 bits |= (b << shiftto);
1067                 shiftto -= 6;
1068                 if (shiftto < 0) {
1069                     dst[dp++] = (byte)(bits >> 16);
1070                     dst[dp++] = (byte)(bits >>  8);
1071                     dst[dp++] = (byte)(bits);
1072                     shiftto = 18;
1073                     bits = 0;
1074                 }
1075             }
1076             // reach end of byte arry or hit padding '=' characters.
1077             // if '=' presents, they must be the last one or two.
1078             if (shiftto == 6) {           // xx==
1079                 if (padding && (sp + 1 != sl || src[sp] != '='))
1080                     throw new IllegalArgumentException(
1081                         "Input byte array has wrong 4-byte ending unit");
1082                 dst[dp++] = (byte)(bits >> 16);
1083             } else if (shiftto == 0) {    // xxx=
1084                 if (padding && sp != sl)
1085                     throw new IllegalArgumentException(
1086                         "Input byte array has wrong 4-byte ending unit");
1087                 dst[dp++] = (byte)(bits >> 16);
1088                 dst[dp++] = (byte)(bits >>  8);
1089             } else if (padding || shiftto != 18) {
1090                     throw new IllegalArgumentException(
1091                         "last unit does not have enough bytes");
1092             }
1093             return dp;
1094         }
1095     }
1096 
1097     /*
1098      * An output stream for encoding bytes into the Base64.
1099      */
1100     private static class EncOutputStream extends FilterOutputStream {
1101 
1102         private int leftover = 0;
1103         private int b0, b1, b2;
1104         private boolean closed = false;
1105 
1106         private final char[] base64;    // byte->base64 mapping
1107         private final byte[] newline;   // line separator, if needed
1108         private final int linemax;
1109         private int linepos = 0;
1110 
1111         EncOutputStream(OutputStream os,
1112                         char[] base64, byte[] newline, int linemax) {
1113             super(os);
1114             this.base64 = base64;
1115             this.newline = newline;
1116             this.linemax = linemax;
1117         }
1118 
1119         @Override
1120         public void write(int b) throws IOException {
1121             byte[] buf = new byte[1];
1122             buf[0] = (byte)(b & 0xff);
1123             write(buf, 0, 1);
1124         }
1125 
1126         private void checkNewline() throws IOException {
1127             if (linepos == linemax) {
1128                 out.write(newline);
1129                 linepos = 0;
1130             }
1131         }
1132 
1133         @Override
1134         public void write(byte[] b, int off, int len) throws IOException {
1135             if (closed)
1136                 throw new IOException("Stream is closed");
1137             if (off < 0 || len < 0 || off + len > b.length)
1138                 throw new ArrayIndexOutOfBoundsException();
1139             if (len == 0)
1140                 return;
1141             if (leftover != 0) {
1142                 if (leftover == 1) {
1143                     b1 = b[off++] & 0xff;
1144                     len--;
1145                     if (len == 0) {
1146                         leftover++;
1147                         return;
1148                     }
1149                 }
1150                 b2 = b[off++] & 0xff;
1151                 len--;
1152                 checkNewline();
1153                 out.write(base64[b0 >> 2]);
1154                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1155                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
1156                 out.write(base64[b2 & 0x3f]);
1157                 linepos += 4;
1158             }
1159             int nBits24 = len / 3;
1160             leftover = len - (nBits24 * 3);
1161             while (nBits24-- > 0) {
1162                 checkNewline();
1163                 int bits = (b[off++] & 0xff) << 16 |
1164                            (b[off++] & 0xff) <<  8 |
1165                            (b[off++] & 0xff);
1166                 out.write(base64[(bits >>> 18) & 0x3f]);
1167                 out.write(base64[(bits >>> 12) & 0x3f]);
1168                 out.write(base64[(bits >>> 6)  & 0x3f]);
1169                 out.write(base64[bits & 0x3f]);
1170                 linepos += 4;
1171            }
1172             if (leftover == 1) {
1173                 b0 = b[off++] & 0xff;
1174             } else if (leftover == 2) {
1175                 b0 = b[off++] & 0xff;
1176                 b1 = b[off++] & 0xff;
1177             }
1178         }
1179 
1180         @Override
1181         public void close() throws IOException {
1182             if (!closed) {
1183                 closed = true;
1184                 if (leftover == 1) {
1185                     checkNewline();
1186                     out.write(base64[b0 >> 2]);
1187                     out.write(base64[(b0 << 4) & 0x3f]);
1188                     out.write('=');
1189                     out.write('=');
1190                 } else if (leftover == 2) {
1191                     checkNewline();
1192                     out.write(base64[b0 >> 2]);
1193                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1194                     out.write(base64[(b1 << 2) & 0x3f]);
1195                     out.write('=');
1196                 }
1197                 leftover = 0;
1198                 out.close();
1199             }
1200         }
1201     }
1202 
1203     /*
1204      * An input stream for decoding Base64 bytes
1205      */
1206     private static class DecInputStream extends InputStream {
1207 
1208         private final InputStream is;
1209         private final boolean isMIME;
1210         private final int[] base64;      // base64 -> byte mapping
1211         private int bits = 0;            // 24-bit buffer for decoding
1212         private int nextin = 18;         // next available "off" in "bits" for input;
1213                                          // -> 18, 12, 6, 0
1214         private int nextout = -8;        // next available "off" in "bits" for output;
1215                                          // -> 8, 0, -8 (no byte for output)
1216         private boolean eof = false;
1217         private boolean closed = false;
1218 
1219         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
1220             this.is = is;
1221             this.base64 = base64;
1222             this.isMIME = isMIME;
1223         }
1224 
1225         private byte[] sbBuf = new byte[1];
1226 
1227         @Override
1228         public int read() throws IOException {
1229             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
1230         }
1231 
1232         @Override
1233         public int read(byte[] b, int off, int len) throws IOException {
1234             if (closed)
1235                 throw new IOException("Stream is closed");
1236             if (eof && nextout < 0)    // eof and no leftover
1237                 return -1;
1238             if (off < 0 || len < 0 || len > b.length - off)
1239                 throw new IndexOutOfBoundsException();
1240             int oldOff = off;
1241             if (nextout >= 0) {       // leftover output byte(s) in bits buf
1242                 do {
1243                     if (len == 0)
1244                         return off - oldOff;
1245                     b[off++] = (byte)(bits >> nextout);
1246                     len--;
1247                     nextout -= 8;
1248                 } while (nextout >= 0);
1249                 bits = 0;
1250             }
1251             while (len > 0) {
1252                 int v = is.read();
1253                 if (v == -1) {
1254                     eof = true;
1255                     if (nextin != 18)
1256                         throw new IOException("Base64 stream has un-decoded dangling byte(s).");
1257                     if (off == oldOff)
1258                         return -1;
1259                     else
1260                         return off - oldOff;
1261                 }
1262                 if (v == '=') {                  // padding byte(s)
1263                     if (nextin != 6 && nextin != 0) {
1264                         throw new IOException("Illegal base64 ending sequence:" + nextin);
1265                     }
1266                     b[off++] = (byte)(bits >> (16));
1267                     len--;
1268                     if (nextin == 0) {           // only one padding byte
1269                         if (len == 0) {          // no enough output space
1270                             bits >>= 8;          // shift to lowest byte
1271                             nextout = 0;
1272                         } else {
1273                             b[off++] = (byte) (bits >>  8);
1274                         }
1275                     }
1276                     eof = true;
1277                     break;
1278                 }
1279                 if ((v = base64[v]) == -1) {
1280                     if (isMIME)                 // skip if for rfc2045
1281                         continue;
1282                     else
1283                         throw new IOException("Illegal base64 character " +
1284                             Integer.toString(v, 16));
1285                 }
1286                 bits |= (v << nextin);
1287                 if (nextin == 0) {
1288                     nextin = 18;    // clear for next
1289                     nextout = 16;
1290                     while (nextout >= 0) {
1291                         b[off++] = (byte)(bits >> nextout);
1292                         len--;
1293                         nextout -= 8;
1294                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
1295                             return off - oldOff;
1296                         }
1297                     }
1298                     bits = 0;
1299                 } else {
1300                     nextin -= 6;
1301                 }
1302             }
1303             return off - oldOff;
1304         }
1305 
1306         @Override
1307         public int available() throws IOException {
1308             if (closed)
1309                 throw new IOException("Stream is closed");
1310             return is.available();   // TBD:
1311         }
1312 
1313         @Override
1314         public void close() throws IOException {
1315             if (!closed) {
1316                 closed = true;
1317                 is.close();
1318             }
1319         }
1320     }
1321 }