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 ISO-8859-1}
 293          * 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 Buffer#remaining remaining} bytes. This is typically
 362          * done by draining any encoded bytes from the output buffer. The
 363          * 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             return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
 417                                        newline, linemax);
 418         }
 419 
 420         private int encodeArray(ByteBuffer src, ByteBuffer dst, int bytesOut) {
 421             char[] base64 = isURL? toBase64URL : toBase64;
 422             byte[] sa = src.array();
 423             int    sp = src.arrayOffset() + src.position();
 424             int    sl = src.arrayOffset() + src.limit();
 425             byte[] da = dst.array();
 426             int    dp = dst.arrayOffset() + dst.position();
 427             int    dl = dst.arrayOffset() + dst.limit();
 428             int    dp00 = dp;
 429             int    dpos = 0;        // dp of each line
 430             if (linemax > 0 && bytesOut > 0)
 431                 dpos = bytesOut % (linemax + newline.length);
 432             try {
 433                 if (dpos == linemax && sp < src.limit()) {
 434                     if (dp + newline.length > dl)
 435                         return  dp - dp00 + bytesOut;
 436                     for (byte b : newline){
 437                         dst.put(dp++, b);
 438                     }
 439                     dpos = 0;
 440                 }
 441                 sl = sp + (sl - sp) / 3 * 3;
 442                 while (sp < sl) {
 443                     int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3
 444                                              : sl - sp;
 445                     int sl0 = Math.min(sp + slen, sl);
 446                     for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 447                         if (dp0 + 4 > dl) {
 448                             sp = sp0; dp = dp0;
 449                             return  dp0 - dp00 + bytesOut;
 450                         }
 451                         int bits = (sa[sp0++] & 0xff) << 16 |
 452                                    (sa[sp0++] & 0xff) <<  8 |
 453                                    (sa[sp0++] & 0xff);
 454                         da[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 455                         da[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 456                         da[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 457                         da[dp0++] = (byte)base64[bits & 0x3f];
 458                     }
 459                     int n = (sl0 - sp) / 3 * 4;
 460                     dpos += n;
 461                     dp += n;
 462                     sp = sl0;
 463                     if (dpos == linemax && sp < src.limit()) {
 464                         if (dp + newline.length > dl)
 465                             return  dp - dp00 + bytesOut;
 466                         for (byte b : newline){
 467                             da[dp++] = b;
 468                         }
 469                         dpos = 0;
 470                     }
 471                 }
 472                 sl = src.arrayOffset() + src.limit();
 473                 if (sp < sl && dl >= dp + 4) {       // 1 or 2 leftover bytes
 474                     int b0 = sa[sp++] & 0xff;
 475                     da[dp++] = (byte)base64[b0 >> 2];
 476                     if (sp == sl) {
 477                         da[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 478                         da[dp++] = '=';
 479                         da[dp++] = '=';
 480                     } else {
 481                         int b1 = sa[sp++] & 0xff;
 482                         da[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 483                         da[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 484                         da[dp++] = '=';
 485                     }
 486                 }
 487                 return dp - dp00 + bytesOut;
 488             } finally {
 489                 src.position(sp - src.arrayOffset());
 490                 dst.position(dp - dst.arrayOffset());
 491             }
 492         }
 493 
 494         private int encodeBuffer(ByteBuffer src, ByteBuffer dst, int bytesOut) {
 495             char[] base64 = isURL? toBase64URL : toBase64;
 496             int sp = src.position();
 497             int sl = src.limit();
 498             int dp = dst.position();
 499             int dl = dst.limit();
 500             int dp00 = dp;
 501 
 502             int dpos = 0;        // dp of each line
 503             if (linemax > 0 && bytesOut > 0)
 504                 dpos = bytesOut % (linemax + newline.length);
 505             try {
 506                 if (dpos == linemax && sp < src.limit()) {
 507                     if (dp + newline.length > dl)
 508                         return  dp - dp00 + bytesOut;
 509                     for (byte b : newline){
 510                         dst.put(dp++, b);
 511                     }
 512                     dpos = 0;
 513                 }
 514                 sl = sp + (sl - sp) / 3 * 3;
 515                 while (sp < sl) {
 516                     int slen = (linemax > 0) ? (linemax - dpos) / 4 * 3
 517                                              : sl - sp;
 518                     int sl0 = Math.min(sp + slen, sl);
 519                     for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 520                         if (dp0 + 4 > dl) {
 521                             sp = sp0; dp = dp0;
 522                             return  dp0 - dp00 + bytesOut;
 523                         }
 524                         int bits = (src.get(sp0++) & 0xff) << 16 |
 525                                    (src.get(sp0++) & 0xff) <<  8 |
 526                                    (src.get(sp0++) & 0xff);
 527                         dst.put(dp0++, (byte)base64[(bits >>> 18) & 0x3f]);
 528                         dst.put(dp0++, (byte)base64[(bits >>> 12) & 0x3f]);
 529                         dst.put(dp0++, (byte)base64[(bits >>> 6)  & 0x3f]);
 530                         dst.put(dp0++, (byte)base64[bits & 0x3f]);
 531                     }
 532                     int n = (sl0 - sp) / 3 * 4;
 533                     dpos += n;
 534                     dp += n;
 535                     sp = sl0;
 536                     if (dpos == linemax && sp < src.limit()) {
 537                         if (dp + newline.length > dl)
 538                             return  dp - dp00 + bytesOut;
 539                         for (byte b : newline){
 540                             dst.put(dp++, b);
 541                         }
 542                         dpos = 0;
 543                     }
 544                 }
 545                 if (sp < src.limit() && dl >= dp + 4) {       // 1 or 2 leftover bytes
 546                     int b0 = src.get(sp++) & 0xff;
 547                     dst.put(dp++, (byte)base64[b0 >> 2]);
 548                     if (sp == src.limit()) {
 549                         dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f]);
 550                         dst.put(dp++, (byte)'=');
 551                         dst.put(dp++, (byte)'=');
 552                     } else {
 553                         int b1 = src.get(sp++) & 0xff;
 554                         dst.put(dp++, (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 555                         dst.put(dp++, (byte)base64[(b1 << 2) & 0x3f]);
 556                         dst.put(dp++, (byte)'=');
 557                     }
 558                 }
 559                 return dp - dp00 + bytesOut;
 560             } finally {
 561                 src.position(sp);
 562                 dst.position(dp);
 563             }
 564         }
 565 
 566         private int encode0(byte[] src, int off, int end, byte[] dst) {
 567             char[] base64 = isURL ? toBase64URL : toBase64;
 568             int sp = off;
 569             int slen = (end - off) / 3 * 3;
 570             int sl = off + slen;
 571             if (linemax > 0 && slen  > linemax / 4 * 3)
 572                 slen = linemax / 4 * 3;
 573             int dp = 0;
 574             while (sp < sl) {
 575                 int sl0 = Math.min(sp + slen, sl);
 576                 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 577                     int bits = (src[sp0++] & 0xff) << 16 |
 578                                (src[sp0++] & 0xff) <<  8 |
 579                                (src[sp0++] & 0xff);
 580                     dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 581                     dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 582                     dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 583                     dst[dp0++] = (byte)base64[bits & 0x3f];
 584                 }
 585                 int dlen = (sl0 - sp) / 3 * 4;
 586                 dp += dlen;
 587                 sp = sl0;
 588                 if (dlen == linemax && sp < end) {
 589                     for (byte b : newline){
 590                         dst[dp++] = b;
 591                     }
 592                 }
 593             }
 594             if (sp < end) {               // 1 or 2 leftover bytes
 595                 int b0 = src[sp++] & 0xff;
 596                 dst[dp++] = (byte)base64[b0 >> 2];
 597                 if (sp == end) {
 598                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 599                     dst[dp++] = '=';
 600                     dst[dp++] = '=';
 601                 } else {
 602                     int b1 = src[sp++] & 0xff;
 603                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 604                     dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 605                     dst[dp++] = '=';
 606                 }
 607             }
 608             return dp;
 609         }
 610     }
 611 
 612     /**
 613      * This class implements a decoder for decoding byte data using the
 614      * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 615      *
 616      * <p> Instances of {@link Decoder} class are safe for use by
 617      * multiple concurrent threads.
 618      *
 619      * <p> Unless otherwise noted, passing a {@code null} argument to
 620      * a method of this class will cause a
 621      * {@link java.lang.NullPointerException NullPointerException} to
 622      * be thrown.
 623      *
 624      * @see     Encoder
 625      * @since   1.8
 626      */
 627     public static class Decoder {
 628 
 629         private final boolean isURL;
 630         private final boolean isMIME;
 631 
 632         private Decoder(boolean isURL, boolean isMIME) {
 633             this.isURL = isURL;
 634             this.isMIME = isMIME;
 635         }
 636 
 637         /**
 638          * Lookup table for decoding unicode characters drawn from the
 639          * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
 640          * their 6-bit positive integer equivalents.  Characters that
 641          * are not in the Base64 alphabet but fall within the bounds of
 642          * the array are encoded to -1.
 643          *
 644          */
 645         private static final int[] fromBase64 = new int[256];
 646         static {
 647             Arrays.fill(fromBase64, -1);
 648             for (int i = 0; i < Encoder.toBase64.length; i++)
 649                 fromBase64[Encoder.toBase64[i]] = i;
 650             fromBase64['='] = -2;
 651         }
 652 
 653         /**
 654          * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
 655          * as specified in Table2 of the RFC 4648.
 656          */
 657         private static final int[] fromBase64URL = new int[256];
 658 
 659         static {
 660             Arrays.fill(fromBase64URL, -1);
 661             for (int i = 0; i < Encoder.toBase64URL.length; i++)
 662                 fromBase64URL[Encoder.toBase64URL[i]] = i;
 663             fromBase64URL['='] = -2;
 664         }
 665 
 666         static final Decoder RFC4648         = new Decoder(false, false);
 667         static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
 668         static final Decoder RFC2045         = new Decoder(false, true);
 669 
 670         /**
 671          * Decodes all bytes from the input byte array using the {@link Base64}
 672          * encoding scheme, writing the results into a newly-allocated output
 673          * byte array. The returned byte array is of the length of the resulting
 674          * bytes.
 675          *
 676          * @param   src
 677          *          the byte array to decode
 678          *
 679          * @return  A newly-allocated byte array containing the decoded bytes.
 680          *
 681          * @throws  IllegalArgumentException
 682          *          if {@code src} is not in valid Base64 scheme
 683          */
 684         public byte[] decode(byte[] src) {
 685             byte[] dst = new byte[outLength(src, 0, src.length)];
 686             int ret = decode0(src, 0, src.length, dst);
 687             if (ret != dst.length) {
 688                 dst = Arrays.copyOf(dst, ret);
 689             }
 690             return dst;
 691         }
 692 
 693         /**
 694          * Decodes a Base64 encoded String into a newly-allocated byte array
 695          * using the {@link Base64} encoding scheme.
 696          *
 697          * <p> An invocation of this method has exactly the same effect as invoking
 698          * {@code return decode(src.getBytes(StandardCharsets.ISO_8859_1))}
 699          *
 700          * @param   src
 701          *          the string to decode
 702          *
 703          * @return  A newly-allocated byte array containing the decoded bytes.
 704          *
 705          * @throws  IllegalArgumentException
 706          *          if {@code src} is not in valid Base64 scheme
 707          */
 708         public byte[] decode(String src) {
 709             return decode(src.getBytes(StandardCharsets.ISO_8859_1));
 710         }
 711 
 712         /**
 713          * Decodes all bytes from the input byte array using the {@link Base64}
 714          * encoding scheme, writing the results into the given output byte array,
 715          * starting at offset 0.
 716          *
 717          * <p> It is the responsibility of the invoker of this method to make
 718          * sure the output byte array {@code dst} has enough space for decoding
 719          * all bytes from the input byte array. No bytes will be be written to
 720          * the output byte array if the output byte array is not big enough.
 721          *
 722          * <p> If the input byte array is not in valid Base64 encoding scheme
 723          * then some bytes may have been written to the output byte array before
 724          * IllegalargumentException is thrown.
 725          *
 726          * @param   src
 727          *          the byte array to decode
 728          * @param   dst
 729          *          the output byte array
 730          *
 731          * @return  The number of bytes written to the output byte array
 732          *
 733          * @throws  IllegalArgumentException
 734          *          if {@code src} is not in valid Base64 scheme, or {@code dst}
 735          *          does not have enough space for decoding all input bytes.
 736          */
 737         public int decode(byte[] src, byte[] dst) {
 738             int len = outLength(src, 0, src.length);
 739             if (dst.length < len)
 740                 throw new IllegalArgumentException(
 741                     "Output byte array is too small for decoding all input bytes");
 742             return decode0(src, 0, src.length, dst);
 743         }
 744 
 745         /**
 746          * Decodes all bytes from the input byte buffer using the {@link Base64}
 747          * encoding scheme, writing the results into a newly-allocated ByteBuffer.
 748          *
 749          * <p> Upon return, the source buffer's position will be updated to
 750          * its limit; its limit will not have been changed. The returned
 751          * output buffer's position will be zero and its limit will be the
 752          * number of resulting decoded bytes
 753          *
 754          * @param   buffer
 755          *          the ByteBuffer to decode
 756          *
 757          * @return  A newly-allocated byte buffer containing the decoded bytes
 758          *
 759          * @throws  IllegalArgumentException
 760          *          if {@code src} is not in valid Base64 scheme.
 761          */
 762         public ByteBuffer decode(ByteBuffer buffer) {
 763             int pos0 = buffer.position();
 764             try {
 765                 byte[] src;
 766                 int sp, sl;
 767                 if (buffer.hasArray()) {
 768                     src = buffer.array();
 769                     sp = buffer.arrayOffset() + buffer.position();
 770                     sl = buffer.arrayOffset() + buffer.limit();
 771                     buffer.position(buffer.limit());
 772                 } else {
 773                     src = new byte[buffer.remaining()];
 774                     buffer.get(src);
 775                     sp = 0;
 776                     sl = src.length;
 777                 }
 778                 byte[] dst = new byte[outLength(src, sp, sl)];
 779                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
 780             } catch (IllegalArgumentException iae) {
 781                 buffer.position(pos0);
 782                 throw iae;
 783             }
 784         }
 785 
 786         /**
 787          * Decodes as many bytes as possible from the input byte buffer
 788          * using the {@link Base64} encoding scheme, writing the resulting
 789          * bytes to the given output byte buffer.
 790          *
 791          * <p>The buffers are read from, and written to, starting at their
 792          * current positions. Upon return, the input and output buffers'
 793          * positions will be advanced to reflect the bytes read and written,
 794          * but their limits will not be modified.
 795          *
 796          * <p> If the input buffer is not in valid Base64 encoding scheme
 797          * then some bytes may have been written to the output buffer
 798          * before IllegalArgumentException is thrown. The positions of
 799          * both input and output buffer will not be advanced in this case.
 800          *
 801          * <p>The decoding operation will end and return if all remaining
 802          * bytes in the input buffer have been decoded and written to the
 803          * output buffer.
 804          *
 805          * <p> The decoding operation will stop and return if the output
 806          * buffer has insufficient space to decode any more input bytes.
 807          * The decoding operation can be continued, if there is more bytes
 808          * in input buffer to be decoded, by invoking this method again with
 809          * an output buffer that has more {@linkplain Buffer#remaining remaining}
 810          * bytes.This is typically done by draining any decoded bytes from the
 811          * output buffer.
 812          *
 813          * <p><b>Recommended Usage Example</b>
 814          * <pre>
 815          *    ByteBuffer src = ...;
 816          *    ByteBuffer dst = ...;
 817          *    Base64.Decoder dec = Base64.getDecoder();
 818          *
 819          *    while (src.hasRemaining()) {
 820          *
 821          *        // prepare the output byte buffer
 822          *        dst.clear();
 823          *        dec.decode(src, dst);
 824          *
 825          *        // read bytes from the output buffer
 826          *        dst.flip();
 827          *        ...
 828          *    }
 829          * </pre>
 830          *
 831          * @param   src
 832          *          the input byte buffer to decode
 833          * @param   dst
 834          *          the output byte buffer
 835          *
 836          * @return  The number of bytes written to the output byte buffer during
 837          *          this decoding invocation
 838          *
 839          * @throws  IllegalArgumentException
 840          *          if {@code src} is not in valid Base64 scheme.
 841          */
 842         public int decode(ByteBuffer src, ByteBuffer dst) {
 843             int sp0 = src.position();
 844             int dp0 = dst.position();
 845             try {
 846                 if (src.hasArray() && dst.hasArray())
 847                     return decodeArray(src, dst);
 848                 return decodeBuffer(src, dst);
 849             } catch (IllegalArgumentException iae) {
 850                 src.position(sp0);
 851                 dst.position(dp0);
 852                 throw iae;
 853             }
 854         }
 855 
 856         /**
 857          * Returns an input stream for decoding {@link Base64} encoded byte stream.
 858          *
 859          * <p> Closing the returned input stream will close the underlying
 860          * input stream.
 861          *
 862          * @param   is
 863          *          the input stream
 864          *
 865          * @return  the input stream for decoding the specified Base64 encoded
 866          *          byte stream
 867          */
 868         public InputStream wrap(InputStream is) {
 869             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 870         }
 871 
 872         private int decodeArray(ByteBuffer src, ByteBuffer dst) {
 873             int[] base64 = isURL ? fromBase64URL : fromBase64;
 874             int   bits = 0;
 875             int   shiftto = 18;       // pos of first byte of 4-byte atom
 876             byte[] sa = src.array();
 877             int    sp = src.arrayOffset() + src.position();
 878             int    sl = src.arrayOffset() + src.limit();
 879             byte[] da = dst.array();
 880             int    dp = dst.arrayOffset() + dst.position();
 881             int    dl = dst.arrayOffset() + dst.limit();
 882             int    dp0 = dp;
 883             int    mark = sp;
 884             boolean padding = false;
 885             try {
 886                 while (sp < sl) {
 887                     int b = sa[sp++] & 0xff;
 888                     if ((b = base64[b]) < 0) {
 889                         if (b == -2) {   // padding byte
 890                             padding = true;
 891                             break;
 892                         }
 893                         if (isMIME)     // skip if for rfc2045
 894                             continue;
 895                         else
 896                             throw new IllegalArgumentException(
 897                                 "Illegal base64 character " +
 898                                 Integer.toString(sa[sp - 1], 16));
 899                     }
 900                     bits |= (b << shiftto);
 901                     shiftto -= 6;
 902                     if (shiftto < 0) {
 903                         if (dl < dp + 3)
 904                             return dp;
 905                         da[dp++] = (byte)(bits >> 16);
 906                         da[dp++] = (byte)(bits >>  8);
 907                         da[dp++] = (byte)(bits);
 908                         shiftto = 18;
 909                         bits = 0;
 910                         mark = sp;
 911                     }
 912                 }
 913                 if (shiftto == 6) {
 914                     if (dl - dp < 1)
 915                         return dp;
 916                     if (padding && (sp + 1 != sl || sa[sp++] != '='))
 917                         throw new IllegalArgumentException(
 918                             "Input buffer has wrong 4-byte ending unit");
 919                     da[dp++] = (byte)(bits >> 16);
 920                     mark = sp;
 921                 } else if (shiftto == 0) {
 922                     if (dl - dp < 2)
 923                         return dp;
 924                     if (padding && sp != sl)
 925                         throw new IllegalArgumentException(
 926                             "Input buffer has wrong 4-byte ending unit");
 927                     da[dp++] = (byte)(bits >> 16);
 928                     da[dp++] = (byte)(bits >>  8);
 929                     mark = sp;
 930                 } else if (padding || shiftto != 18) {
 931                     throw new IllegalArgumentException(
 932                         "Last unit does not have enough valid bits");
 933                 }
 934                 return dp - dp0;
 935             } finally {
 936                 src.position(mark);
 937                 dst.position(dp);
 938             }
 939         }
 940 
 941         private int decodeBuffer(ByteBuffer src, ByteBuffer dst) {
 942             int[] base64 = isURL ? fromBase64URL : fromBase64;
 943             int   bits = 0;
 944             int   shiftto = 18;       // pos of first byte of 4-byte atom
 945             int    sp = src.position();
 946             int    sl = src.limit();
 947             int    dp = dst.position();
 948             int    dl = dst.limit();
 949             int    dp0 = dp;
 950             int    mark = sp;
 951             boolean padding = false;
 952 
 953             try {
 954                 while (sp < sl) {
 955                     int b = src.get(sp++) & 0xff;
 956                     if ((b = base64[b]) < 0) {
 957                         if (b == -2) {  // padding byte
 958                             padding = true;
 959                             break;
 960                         }
 961                         if (isMIME)     // skip if for rfc2045
 962                             continue;
 963                         else
 964                             throw new IllegalArgumentException(
 965                                 "Illegal base64 character " +
 966                                 Integer.toString(src.get(sp - 1), 16));
 967                     }
 968                     bits |= (b << shiftto);
 969                     shiftto -= 6;
 970                     if (shiftto < 0) {
 971                         if (dl < dp + 3)
 972                             return dp;
 973                         dst.put(dp++, (byte)(bits >> 16));
 974                         dst.put(dp++, (byte)(bits >>  8));
 975                         dst.put(dp++, (byte)(bits));
 976                         shiftto = 18;
 977                         bits = 0;
 978                         mark = sp;
 979                     }
 980                 }
 981                 if (shiftto == 6) {
 982                     if (dl - dp < 1)
 983                         return dp;
 984                     if (padding && (sp + 1 != sl || src.get(sp++) != '='))
 985                         throw new IllegalArgumentException(
 986                             "Input buffer has wrong 4-byte ending unit");
 987                      dst.put(dp++, (byte)(bits >> 16));
 988                      mark = sp;
 989                 } else if (shiftto == 0) {
 990                     if (dl - dp < 2)
 991                         return dp;
 992                     if (padding && sp != sl)
 993                         throw new IllegalArgumentException(
 994                             "Input buffer has wrong 4-byte ending unit");
 995                     dst.put(dp++, (byte)(bits >> 16));
 996                     dst.put(dp++, (byte)(bits >>  8));
 997                     mark = sp;
 998                 } else if (padding || shiftto != 18) {
 999                     throw new IllegalArgumentException(
1000                         "Last unit does not have enough valid bits");
1001                 }
1002                 return dp - dp0;
1003             } finally {
1004                 src.position(mark);
1005                 dst.position(dp);
1006             }
1007         }
1008 
1009         private int outLength(byte[] src, int sp, int sl) {
1010             int[] base64 = isURL ? fromBase64URL : fromBase64;
1011             int paddings = 0;
1012             int len = sl - sp;
1013             if (len == 0)
1014                 return 0;
1015             if (len < 2)
1016                 throw new IllegalArgumentException(
1017                     "Input byte[] should at least have 2 bytes for base64 bytes");
1018             if (src[sl - 1] == '=') {
1019                 paddings++;
1020                 if (src[sl - 2] == '=')
1021                     paddings++;
1022             }
1023             if (isMIME) {
1024                 // scan all bytes to fill out all non-alphabet. a performance
1025                 // trade-off of pre-scan or Arrays.copyOf
1026                 int n = 0;
1027                 while (sp < sl) {
1028                     int b = src[sp++] & 0xff;
1029                     if (b == '=')
1030                         break;
1031                     if ((b = base64[b]) == -1)
1032                         n++;
1033                 }
1034                 len -= n;
1035             }
1036             if (paddings == 0 && (len & 0x3) !=  0)
1037                 paddings = 4 - (len & 0x3);
1038             return 3 * ((len + 3) / 4) - paddings;
1039         }
1040 
1041         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
1042             int[] base64 = isURL ? fromBase64URL : fromBase64;
1043             int dp = 0;
1044             int bits = 0;
1045             int shiftto = 18;       // pos of first byte of 4-byte atom
1046             boolean padding = false;
1047             while (sp < sl) {
1048                 int b = src[sp++] & 0xff;
1049                 if ((b = base64[b]) < 0) {
1050                     if (b == -2) {     // padding byte
1051                         padding = true;
1052                         break;
1053                     }
1054                     if (isMIME)    // skip if for rfc2045
1055                         continue;
1056                     else
1057                         throw new IllegalArgumentException(
1058                             "Illegal base64 character " +
1059                             Integer.toString(src[sp - 1], 16));
1060                 }
1061                 bits |= (b << shiftto);
1062                 shiftto -= 6;
1063                 if (shiftto < 0) {
1064                     dst[dp++] = (byte)(bits >> 16);
1065                     dst[dp++] = (byte)(bits >>  8);
1066                     dst[dp++] = (byte)(bits);
1067                     shiftto = 18;
1068                     bits = 0;
1069                 }
1070             }
1071             // reach end of byte arry or hit padding '=' characters.
1072             // if '=' presents, they must be the last one or two.
1073             if (shiftto == 6) {           // xx==
1074                 if (padding && (sp + 1 != sl || src[sp] != '='))
1075                     throw new IllegalArgumentException(
1076                         "Input byte array has wrong 4-byte ending unit");
1077                 dst[dp++] = (byte)(bits >> 16);
1078             } else if (shiftto == 0) {    // xxx=
1079                 if (padding && sp != sl)
1080                     throw new IllegalArgumentException(
1081                         "Input byte array has wrong 4-byte ending unit");
1082                 dst[dp++] = (byte)(bits >> 16);
1083                 dst[dp++] = (byte)(bits >>  8);
1084             } else if (padding || shiftto != 18) {
1085                     throw new IllegalArgumentException(
1086                         "last unit does not have enough bytes");
1087             }
1088             return dp;
1089         }
1090     }
1091 
1092     /*
1093      * An output stream for encoding bytes into the Base64.
1094      */
1095     private static class EncOutputStream extends FilterOutputStream {
1096 
1097         private int leftover = 0;
1098         private int b0, b1, b2;
1099         private boolean closed = false;
1100 
1101         private final char[] base64;    // byte->base64 mapping
1102         private final byte[] newline;   // line separator, if needed
1103         private final int linemax;
1104         private int linepos = 0;
1105 
1106         EncOutputStream(OutputStream os,
1107                         char[] base64, byte[] newline, int linemax) {
1108             super(os);
1109             this.base64 = base64;
1110             this.newline = newline;
1111             this.linemax = linemax;
1112         }
1113 
1114         @Override
1115         public void write(int b) throws IOException {
1116             byte[] buf = new byte[1];
1117             buf[0] = (byte)(b & 0xff);
1118             write(buf, 0, 1);
1119         }
1120 
1121         private void checkNewline() throws IOException {
1122             if (linepos == linemax) {
1123                 out.write(newline);
1124                 linepos = 0;
1125             }
1126         }
1127 
1128         @Override
1129         public void write(byte[] b, int off, int len) throws IOException {
1130             if (closed)
1131                 throw new IOException("Stream is closed");
1132             if (off < 0 || len < 0 || off + len > b.length)
1133                 throw new ArrayIndexOutOfBoundsException();
1134             if (len == 0)
1135                 return;
1136             if (leftover != 0) {
1137                 if (leftover == 1) {
1138                     b1 = b[off++] & 0xff;
1139                     len--;
1140                     if (len == 0) {
1141                         leftover++;
1142                         return;
1143                     }
1144                 }
1145                 b2 = b[off++] & 0xff;
1146                 len--;
1147                 checkNewline();
1148                 out.write(base64[b0 >> 2]);
1149                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1150                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
1151                 out.write(base64[b2 & 0x3f]);
1152                 linepos += 4;
1153             }
1154             int nBits24 = len / 3;
1155             leftover = len - (nBits24 * 3);
1156             while (nBits24-- > 0) {
1157                 checkNewline();
1158                 int bits = (b[off++] & 0xff) << 16 |
1159                            (b[off++] & 0xff) <<  8 |
1160                            (b[off++] & 0xff);
1161                 out.write(base64[(bits >>> 18) & 0x3f]);
1162                 out.write(base64[(bits >>> 12) & 0x3f]);
1163                 out.write(base64[(bits >>> 6)  & 0x3f]);
1164                 out.write(base64[bits & 0x3f]);
1165                 linepos += 4;
1166            }
1167             if (leftover == 1) {
1168                 b0 = b[off++] & 0xff;
1169             } else if (leftover == 2) {
1170                 b0 = b[off++] & 0xff;
1171                 b1 = b[off++] & 0xff;
1172             }
1173         }
1174 
1175         @Override
1176         public void close() throws IOException {
1177             if (!closed) {
1178                 closed = true;
1179                 if (leftover == 1) {
1180                     checkNewline();
1181                     out.write(base64[b0 >> 2]);
1182                     out.write(base64[(b0 << 4) & 0x3f]);
1183                     out.write('=');
1184                     out.write('=');
1185                 } else if (leftover == 2) {
1186                     checkNewline();
1187                     out.write(base64[b0 >> 2]);
1188                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
1189                     out.write(base64[(b1 << 2) & 0x3f]);
1190                     out.write('=');
1191                 }
1192                 leftover = 0;
1193                 out.close();
1194             }
1195         }
1196     }
1197 
1198     /*
1199      * An input stream for decoding Base64 bytes
1200      */
1201     private static class DecInputStream extends InputStream {
1202 
1203         private final InputStream is;
1204         private final boolean isMIME;
1205         private final int[] base64;      // base64 -> byte mapping
1206         private int bits = 0;            // 24-bit buffer for decoding
1207         private int nextin = 18;         // next available "off" in "bits" for input;
1208                                          // -> 18, 12, 6, 0
1209         private int nextout = -8;        // next available "off" in "bits" for output;
1210                                          // -> 8, 0, -8 (no byte for output)
1211         private boolean eof = false;
1212         private boolean closed = false;
1213 
1214         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
1215             this.is = is;
1216             this.base64 = base64;
1217             this.isMIME = isMIME;
1218         }
1219 
1220         private byte[] sbBuf = new byte[1];
1221 
1222         @Override
1223         public int read() throws IOException {
1224             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
1225         }
1226 
1227         @Override
1228         public int read(byte[] b, int off, int len) throws IOException {
1229             if (closed)
1230                 throw new IOException("Stream is closed");
1231             if (eof && nextout < 0)    // eof and no leftover
1232                 return -1;
1233             if (off < 0 || len < 0 || len > b.length - off)
1234                 throw new IndexOutOfBoundsException();
1235             int oldOff = off;
1236             if (nextout >= 0) {       // leftover output byte(s) in bits buf
1237                 do {
1238                     if (len == 0)
1239                         return off - oldOff;
1240                     b[off++] = (byte)(bits >> nextout);
1241                     len--;
1242                     nextout -= 8;
1243                 } while (nextout >= 0);
1244                 bits = 0;
1245             }
1246             while (len > 0) {
1247                 int v = is.read();
1248                 if (v == -1) {
1249                     eof = true;
1250                     if (nextin != 18)
1251                         throw new IOException("Base64 stream has un-decoded dangling byte(s).");
1252                     if (off == oldOff)
1253                         return -1;
1254                     else
1255                         return off - oldOff;
1256                 }
1257                 if (v == '=') {                  // padding byte(s)
1258                     if (nextin != 6 && nextin != 0) {
1259                         throw new IOException("Illegal base64 ending sequence:" + nextin);
1260                     }
1261                     b[off++] = (byte)(bits >> (16));
1262                     len--;
1263                     if (nextin == 0) {           // only one padding byte
1264                         if (len == 0) {          // no enough output space
1265                             bits >>= 8;          // shift to lowest byte
1266                             nextout = 0;
1267                         } else {
1268                             b[off++] = (byte) (bits >>  8);
1269                         }
1270                     }
1271                     eof = true;
1272                     break;
1273                 }
1274                 if ((v = base64[v]) == -1) {
1275                     if (isMIME)                 // skip if for rfc2045
1276                         continue;
1277                     else
1278                         throw new IOException("Illegal base64 character " +
1279                             Integer.toString(v, 16));
1280                 }
1281                 bits |= (v << nextin);
1282                 if (nextin == 0) {
1283                     nextin = 18;    // clear for next
1284                     nextout = 16;
1285                     while (nextout >= 0) {
1286                         b[off++] = (byte)(bits >> nextout);
1287                         len--;
1288                         nextout -= 8;
1289                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
1290                             return off - oldOff;
1291                         }
1292                     }
1293                     bits = 0;
1294                 } else {
1295                     nextin -= 6;
1296                 }
1297             }
1298             return off - oldOff;
1299         }
1300 
1301         @Override
1302         public int available() throws IOException {
1303             if (closed)
1304                 throw new IOException("Stream is closed");
1305             return is.available();   // TBD:
1306         }
1307 
1308         @Override
1309         public void close() throws IOException {
1310             if (!closed) {
1311                 closed = true;
1312                 is.close();
1313             }
1314         }
1315     }
1316 }