1 /*
   2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util;
  27 
  28 import java.io.FilterOutputStream;
  29 import java.io.InputStream;
  30 import java.io.IOException;
  31 import java.io.OutputStream;
  32 import java.nio.ByteBuffer;
  33 import java.nio.charset.StandardCharsets;
  34 
  35 /**
  36  * This class consists exclusively of static methods for obtaining
  37  * encoders and decoders for the Base64 encoding scheme. The
  38  * implementation of this class supports the following types of Base64
  39  * as specified in
  40  * <a href="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</a> and
  41  * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
  42  *
  43  * <ul>
  44  * <li><a name="basic"><b>Basic</b></a>
  45  * <p> Uses "The Base64 Alphabet" as specified in Table 1 of
  46  *     RFC 4648 and RFC 2045 for encoding and decoding operation.
  47  *     The encoder does not add any line feed (line separator)
  48  *     character. The decoder rejects data that contains characters
  49  *     outside the base64 alphabet.</p></li>
  50  *
  51  * <li><a name="url"><b>URL and Filename safe</b></a>
  52  * <p> Uses the "URL and Filename safe Base64 Alphabet" as specified
  53  *     in Table 2 of RFC 4648 for encoding and decoding. The
  54  *     encoder does not add any line feed (line separator) character.
  55  *     The decoder rejects data that contains characters outside the
  56  *     base64 alphabet.</p></li>
  57  *
  58  * <li><a name="mime"><b>MIME</b></a>
  59  * <p> Uses the "The Base64 Alphabet" as specified in Table 1 of
  60  *     RFC 2045 for encoding and decoding operation. The encoded output
  61  *     must be represented in lines of no more than 76 characters each
  62  *     and uses a carriage return {@code '\r'} followed immediately by
  63  *     a linefeed {@code '\n'} as the line separator. No line separator
  64  *     is added to the end of the encoded output. All line separators
  65  *     or other characters not found in the base64 alphabet table are
  66  *     ignored in decoding operation.</p></li>
  67  * </ul>
  68  *
  69  * <p> Unless otherwise noted, passing a {@code null} argument to a
  70  * method of this class will cause a {@link java.lang.NullPointerException
  71  * NullPointerException} to be thrown.
  72  *
  73  * @author  Xueming Shen
  74  * @since   1.8
  75  */
  76 
  77 public class Base64 {
  78 
  79     private Base64() {}
  80 
  81     /**
  82      * Returns a {@link Encoder} that encodes using the
  83      * <a href="#basic">Basic</a> type base64 encoding scheme.
  84      *
  85      * @return  A Base64 encoder.
  86      */
  87     public static Encoder getEncoder() {
  88          return Encoder.RFC4648;
  89     }
  90 
  91     /**
  92      * Returns a {@link Encoder} that encodes using the
  93      * <a href="#url">URL and Filename safe</a> type base64
  94      * encoding scheme.
  95      *
  96      * @return  A Base64 encoder.
  97      */
  98     public static Encoder getUrlEncoder() {
  99          return Encoder.RFC4648_URLSAFE;
 100     }
 101 
 102     /**
 103      * Returns a {@link Encoder} that encodes using the
 104      * <a href="#mime">MIME</a> type base64 encoding scheme.
 105      *
 106      * @return  A Base64 encoder.
 107      */
 108     public static Encoder getMimeEncoder() {
 109         return Encoder.RFC2045;
 110     }
 111 
 112     /**
 113      * Returns a {@link Encoder} that encodes using the
 114      * <a href="#mime">MIME</a> type base64 encoding scheme
 115      * with specified line length and line separators.
 116      *
 117      * @param   lineLength
 118      *          the length of each output line (rounded down to nearest multiple
 119      *          of 4). If {@code lineLength <= 0} the output will not be separated
 120      *          in lines
 121      * @param   lineSeparator
 122      *          the line separator for each output line
 123      *
 124      * @return  A Base64 encoder.
 125      *
 126      * @throws  IllegalArgumentException if {@code lineSeparator} includes any
 127      *          character of "The Base64 Alphabet" as specified in Table 1 of
 128      *          RFC 2045.
 129      */
 130     public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
 131          Objects.requireNonNull(lineSeparator);
 132          int[] base64 = Decoder.fromBase64;
 133          for (byte b : lineSeparator) {
 134              if (base64[b & 0xff] != -1)
 135                  throw new IllegalArgumentException(
 136                      "Illegal base64 line separator character 0x" + Integer.toString(b, 16));
 137          }
 138          if (lineLength <= 0) {
 139              return Encoder.RFC4648;
 140          }
 141          return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true);
 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         private final boolean doPadding;
 196 
 197         private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
 198             this.isURL = isURL;
 199             this.newline = newline;
 200             this.linemax = linemax;
 201             this.doPadding = doPadding;
 202         }
 203 
 204         /**
 205          * This array is a lookup table that translates 6-bit positive integer
 206          * index values into their "Base64 Alphabet" equivalents as specified
 207          * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
 208          */
 209         private static final char[] toBase64 = {
 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             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 213             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 214             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
 215         };
 216 
 217         /**
 218          * It's the lookup table for "URL and Filename safe Base64" as specified
 219          * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
 220          * '_'. This table is used when BASE64_URL is specified.
 221          */
 222         private static final char[] toBase64URL = {
 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             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 226             'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
 227             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
 228         };
 229 
 230         private static final int MIMELINEMAX = 76;
 231         private static final byte[] CRLF = new byte[] {'\r', '\n'};
 232 
 233         static final Encoder RFC4648 = new Encoder(false, null, -1, true);
 234         static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
 235         static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
 236 
 237         private final int outLength(int srclen) {
 238             int len = 0;
 239             if (doPadding) {
 240                 len = 4 * ((srclen + 2) / 3);
 241             } else {
 242                 int n = srclen % 3;
 243                 len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1);
 244             }
 245             if (linemax > 0)                                  // line separators
 246                 len += (len - 1) / linemax * newline.length;
 247             return len;
 248         }
 249 
 250         /**
 251          * Encodes all bytes from the specified byte array into a newly-allocated
 252          * byte array using the {@link Base64} encoding scheme. The returned byte
 253          * array is of the length of the resulting bytes.
 254          *
 255          * @param   src
 256          *          the byte array to encode
 257          * @return  A newly-allocated byte array containing the resulting
 258          *          encoded bytes.
 259          */
 260         public byte[] encode(byte[] src) {
 261             int len = outLength(src.length);          // dst array size
 262             byte[] dst = new byte[len];
 263             int ret = encode0(src, 0, src.length, dst);
 264             if (ret != dst.length)
 265                  return Arrays.copyOf(dst, ret);
 266             return dst;
 267         }
 268 
 269         /**
 270          * Encodes all bytes from the specified byte array using the
 271          * {@link Base64} encoding scheme, writing the resulting bytes to the
 272          * given output byte array, starting at offset 0.
 273          *
 274          * <p> It is the responsibility of the invoker of this method to make
 275          * sure the output byte array {@code dst} has enough space for encoding
 276          * all bytes from the input byte array. No bytes will be written to the
 277          * output byte array if the output byte array is not big enough.
 278          *
 279          * @param   src
 280          *          the byte array to encode
 281          * @param   dst
 282          *          the output byte array
 283          * @return  The number of bytes written to the output byte array
 284          *
 285          * @throws  IllegalArgumentException if {@code dst} does not have enough
 286          *          space for encoding all input bytes.
 287          */
 288         public int encode(byte[] src, byte[] dst) {
 289             int len = outLength(src.length);         // dst array size
 290             if (dst.length < len)
 291                 throw new IllegalArgumentException(
 292                     "Output byte array is too small for encoding all input bytes");
 293             return encode0(src, 0, src.length, dst);
 294         }
 295 
 296         /**
 297          * Encodes the specified byte array into a String using the {@link Base64}
 298          * encoding scheme.
 299          *
 300          * <p> This method first encodes all input bytes into a base64 encoded
 301          * byte array and then constructs a new String by using the encoded byte
 302          * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1
 303          * ISO-8859-1} charset.
 304          *
 305          * <p> In other words, an invocation of this method has exactly the same
 306          * effect as invoking
 307          * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}.
 308          *
 309          * @param   src
 310          *          the byte array to encode
 311          * @return  A String containing the resulting Base64 encoded characters
 312          */
 313         @SuppressWarnings("deprecation")
 314         public String encodeToString(byte[] src) {
 315             byte[] encoded = encode(src);
 316             return new String(encoded, 0, 0, encoded.length);
 317         }
 318 
 319         /**
 320          * Encodes all remaining bytes from the specified byte buffer into
 321          * a newly-allocated ByteBuffer using the {@link Base64} encoding
 322          * scheme.
 323          *
 324          * Upon return, the source buffer's position will be updated to
 325          * its limit; its limit will not have been changed. The returned
 326          * output buffer's position will be zero and its limit will be the
 327          * number of resulting encoded bytes.
 328          *
 329          * @param   buffer
 330          *          the source ByteBuffer to encode
 331          * @return  A newly-allocated byte buffer containing the encoded bytes.
 332          */
 333         public ByteBuffer encode(ByteBuffer buffer) {
 334             int len = outLength(buffer.remaining());
 335             byte[] dst = new byte[len];
 336             int ret = 0;
 337             if (buffer.hasArray()) {
 338                 ret = encode0(buffer.array(),
 339                               buffer.arrayOffset() + buffer.position(),
 340                               buffer.arrayOffset() + buffer.limit(),
 341                               dst);
 342                 buffer.position(buffer.limit());
 343             } else {
 344                 byte[] src = new byte[buffer.remaining()];
 345                 buffer.get(src);
 346                 ret = encode0(src, 0, src.length, dst);
 347             }
 348             if (ret != dst.length)
 349                  dst = Arrays.copyOf(dst, ret);
 350             return ByteBuffer.wrap(dst);
 351         }
 352 
 353         /**
 354          * Wraps an output stream for encoding byte data using the {@link Base64}
 355          * encoding scheme.
 356          *
 357          * <p> It is recommended to promptly close the returned output stream after
 358          * use, during which it will flush all possible leftover bytes to the underlying
 359          * output stream. Closing the returned output stream will close the underlying
 360          * output stream.
 361          *
 362          * @param   os
 363          *          the output stream.
 364          * @return  the output stream for encoding the byte data into the
 365          *          specified Base64 encoded format
 366          */
 367         public OutputStream wrap(OutputStream os) {
 368             Objects.requireNonNull(os);
 369             return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
 370                                        newline, linemax, doPadding);
 371         }
 372 
 373         /**
 374          * Returns an encoder instance that encodes equivalently to this one,
 375          * but without adding any padding character at the end of the encoded
 376          * byte data.
 377          *
 378          * <p> The encoding scheme of this encoder instance is unaffected by
 379          * this invocation. The returned encoder instance should be used for
 380          * non-padding encoding operation.
 381          *
 382          * @return an equivalent encoder that encodes without adding any
 383          *         padding character at the end
 384          */
 385         public Encoder withoutPadding() {
 386             if (!doPadding)
 387                 return this;
 388             return new Encoder(isURL, newline, linemax, false);
 389         }
 390 
 391         private int encode0(byte[] src, int off, int end, byte[] dst) {
 392             char[] base64 = isURL ? toBase64URL : toBase64;
 393             int sp = off;
 394             int slen = (end - off) / 3 * 3;
 395             int sl = off + slen;
 396             if (linemax > 0 && slen  > linemax / 4 * 3)
 397                 slen = linemax / 4 * 3;
 398             int dp = 0;
 399             while (sp < sl) {
 400                 int sl0 = Math.min(sp + slen, sl);
 401                 for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
 402                     int bits = (src[sp0++] & 0xff) << 16 |
 403                                (src[sp0++] & 0xff) <<  8 |
 404                                (src[sp0++] & 0xff);
 405                     dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
 406                     dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
 407                     dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
 408                     dst[dp0++] = (byte)base64[bits & 0x3f];
 409                 }
 410                 int dlen = (sl0 - sp) / 3 * 4;
 411                 dp += dlen;
 412                 sp = sl0;
 413                 if (dlen == linemax && sp < end) {
 414                     for (byte b : newline){
 415                         dst[dp++] = b;
 416                     }
 417                 }
 418             }
 419             if (sp < end) {               // 1 or 2 leftover bytes
 420                 int b0 = src[sp++] & 0xff;
 421                 dst[dp++] = (byte)base64[b0 >> 2];
 422                 if (sp == end) {
 423                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
 424                     if (doPadding) {
 425                         dst[dp++] = '=';
 426                         dst[dp++] = '=';
 427                     }
 428                 } else {
 429                     int b1 = src[sp++] & 0xff;
 430                     dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
 431                     dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
 432                     if (doPadding) {
 433                         dst[dp++] = '=';
 434                     }
 435                 }
 436             }
 437             return dp;
 438         }
 439     }
 440 
 441     /**
 442      * This class implements a decoder for decoding byte data using the
 443      * Base64 encoding scheme as specified in RFC 4648 and RFC 2045.
 444      *
 445      * <p> The Base64 padding character {@code '='} is accepted and
 446      * interpreted as the end of the encoded byte data, but is not
 447      * required. So if the final unit of the encoded byte data only has
 448      * two or three Base64 characters (without the corresponding padding
 449      * character(s) padded), they are decoded as if followed by padding
 450      * character(s). If there is a padding character present in the
 451      * final unit, the correct number of padding character(s) must be
 452      * present, otherwise {@code IllegalArgumentException} (
 453      * {@code IOException} when reading from a Base64 stream) is thrown
 454      * during decoding.
 455      *
 456      * <p> Instances of {@link Decoder} class are safe for use by
 457      * multiple concurrent threads.
 458      *
 459      * <p> Unless otherwise noted, passing a {@code null} argument to
 460      * a method of this class will cause a
 461      * {@link java.lang.NullPointerException NullPointerException} to
 462      * be thrown.
 463      *
 464      * @see     Encoder
 465      * @since   1.8
 466      */
 467     public static class Decoder {
 468 
 469         private final boolean isURL;
 470         private final boolean isMIME;
 471 
 472         private Decoder(boolean isURL, boolean isMIME) {
 473             this.isURL = isURL;
 474             this.isMIME = isMIME;
 475         }
 476 
 477         /**
 478          * Lookup table for decoding unicode characters drawn from the
 479          * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into
 480          * their 6-bit positive integer equivalents.  Characters that
 481          * are not in the Base64 alphabet but fall within the bounds of
 482          * the array are encoded to -1.
 483          *
 484          */
 485         private static final int[] fromBase64 = new int[256];
 486         static {
 487             Arrays.fill(fromBase64, -1);
 488             for (int i = 0; i < Encoder.toBase64.length; i++)
 489                 fromBase64[Encoder.toBase64[i]] = i;
 490             fromBase64['='] = -2;
 491         }
 492 
 493         /**
 494          * Lookup table for decoding "URL and Filename safe Base64 Alphabet"
 495          * as specified in Table2 of the RFC 4648.
 496          */
 497         private static final int[] fromBase64URL = new int[256];
 498 
 499         static {
 500             Arrays.fill(fromBase64URL, -1);
 501             for (int i = 0; i < Encoder.toBase64URL.length; i++)
 502                 fromBase64URL[Encoder.toBase64URL[i]] = i;
 503             fromBase64URL['='] = -2;
 504         }
 505 
 506         static final Decoder RFC4648         = new Decoder(false, false);
 507         static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
 508         static final Decoder RFC2045         = new Decoder(false, true);
 509 
 510         /**
 511          * Decodes all bytes from the input byte array using the {@link Base64}
 512          * encoding scheme, writing the results into a newly-allocated output
 513          * byte array. The returned byte array is of the length of the resulting
 514          * bytes.
 515          *
 516          * @param   src
 517          *          the byte array to decode
 518          *
 519          * @return  A newly-allocated byte array containing the decoded bytes.
 520          *
 521          * @throws  IllegalArgumentException
 522          *          if {@code src} is not in valid Base64 scheme
 523          */
 524         public byte[] decode(byte[] src) {
 525             byte[] dst = new byte[outLength(src, 0, src.length)];
 526             int ret = decode0(src, 0, src.length, dst);
 527             if (ret != dst.length) {
 528                 dst = Arrays.copyOf(dst, ret);
 529             }
 530             return dst;
 531         }
 532 
 533         /**
 534          * Decodes a Base64 encoded String into a newly-allocated byte array
 535          * using the {@link Base64} encoding scheme.
 536          *
 537          * <p> An invocation of this method has exactly the same effect as invoking
 538          * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))}
 539          *
 540          * @param   src
 541          *          the string to decode
 542          *
 543          * @return  A newly-allocated byte array containing the decoded bytes.
 544          *
 545          * @throws  IllegalArgumentException
 546          *          if {@code src} is not in valid Base64 scheme
 547          */
 548         public byte[] decode(String src) {
 549             return decode(src.getBytes(StandardCharsets.ISO_8859_1));
 550         }
 551 
 552         /**
 553          * Decodes all bytes from the input byte array using the {@link Base64}
 554          * encoding scheme, writing the results into the given output byte array,
 555          * starting at offset 0.
 556          *
 557          * <p> It is the responsibility of the invoker of this method to make
 558          * sure the output byte array {@code dst} has enough space for decoding
 559          * all bytes from the input byte array. No bytes will be be written to
 560          * the output byte array if the output byte array is not big enough.
 561          *
 562          * <p> If the input byte array is not in valid Base64 encoding scheme
 563          * then some bytes may have been written to the output byte array before
 564          * IllegalargumentException is thrown.
 565          *
 566          * @param   src
 567          *          the byte array to decode
 568          * @param   dst
 569          *          the output byte array
 570          *
 571          * @return  The number of bytes written to the output byte array
 572          *
 573          * @throws  IllegalArgumentException
 574          *          if {@code src} is not in valid Base64 scheme, or {@code dst}
 575          *          does not have enough space for decoding all input bytes.
 576          */
 577         public int decode(byte[] src, byte[] dst) {
 578             int len = outLength(src, 0, src.length);
 579             if (dst.length < len)
 580                 throw new IllegalArgumentException(
 581                     "Output byte array is too small for decoding all input bytes");
 582             return decode0(src, 0, src.length, dst);
 583         }
 584 
 585         /**
 586          * Decodes all bytes from the input byte buffer using the {@link Base64}
 587          * encoding scheme, writing the results into a newly-allocated ByteBuffer.
 588          *
 589          * <p> Upon return, the source buffer's position will be updated to
 590          * its limit; its limit will not have been changed. The returned
 591          * output buffer's position will be zero and its limit will be the
 592          * number of resulting decoded bytes
 593          *
 594          * <p> {@code IllegalArgumentException} is thrown if the input buffer
 595          * is not in valid Base64 encoding scheme. The position of the input
 596          * buffer will not be advanced in this case.
 597          *
 598          * @param   buffer
 599          *          the ByteBuffer to decode
 600          *
 601          * @return  A newly-allocated byte buffer containing the decoded bytes
 602          *
 603          * @throws  IllegalArgumentException
 604          *          if {@code src} is not in valid Base64 scheme.
 605          */
 606         public ByteBuffer decode(ByteBuffer buffer) {
 607             int pos0 = buffer.position();
 608             try {
 609                 byte[] src;
 610                 int sp, sl;
 611                 if (buffer.hasArray()) {
 612                     src = buffer.array();
 613                     sp = buffer.arrayOffset() + buffer.position();
 614                     sl = buffer.arrayOffset() + buffer.limit();
 615                     buffer.position(buffer.limit());
 616                 } else {
 617                     src = new byte[buffer.remaining()];
 618                     buffer.get(src);
 619                     sp = 0;
 620                     sl = src.length;
 621                 }
 622                 byte[] dst = new byte[outLength(src, sp, sl)];
 623                 return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
 624             } catch (IllegalArgumentException iae) {
 625                 buffer.position(pos0);
 626                 throw iae;
 627             }
 628         }
 629 
 630         /**
 631          * Returns an input stream for decoding {@link Base64} encoded byte stream.
 632          *
 633          * <p> The {@code read}  methods of the returned {@code InputStream} will
 634          * throw {@code IOException} when reading bytes that cannot be decoded.
 635          *
 636          * <p> Closing the returned input stream will close the underlying
 637          * input stream.
 638          *
 639          * @param   is
 640          *          the input stream
 641          *
 642          * @return  the input stream for decoding the specified Base64 encoded
 643          *          byte stream
 644          */
 645         public InputStream wrap(InputStream is) {
 646             Objects.requireNonNull(is);
 647             return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
 648         }
 649 
 650         private int outLength(byte[] src, int sp, int sl) {
 651             int[] base64 = isURL ? fromBase64URL : fromBase64;
 652             int paddings = 0;
 653             int len = sl - sp;
 654             if (len == 0)
 655                 return 0;
 656             if (len < 2) {
 657                 if (isMIME && base64[0] == -1)
 658                     return 0;
 659                 throw new IllegalArgumentException(
 660                     "Input byte[] should at least have 2 bytes for base64 bytes");
 661             }
 662             if (isMIME) {
 663                 // scan all bytes to fill out all non-alphabet. a performance
 664                 // trade-off of pre-scan or Arrays.copyOf
 665                 int n = 0;
 666                 while (sp < sl) {
 667                     int b = src[sp++] & 0xff;
 668                     if (b == '=') {
 669                         len -= (sl - sp + 1);
 670                         break;
 671                     }
 672                     if ((b = base64[b]) == -1)
 673                         n++;
 674                 }
 675                 len -= n;
 676             } else {
 677                 if (src[sl - 1] == '=') {
 678                     paddings++;
 679                     if (src[sl - 2] == '=')
 680                         paddings++;
 681                 }
 682             }
 683             if (paddings == 0 && (len & 0x3) !=  0)
 684                 paddings = 4 - (len & 0x3);
 685             return 3 * ((len + 3) / 4) - paddings;
 686         }
 687 
 688         private int decode0(byte[] src, int sp, int sl, byte[] dst) {
 689             int[] base64 = isURL ? fromBase64URL : fromBase64;
 690             int dp = 0;
 691             int bits = 0;
 692             int shiftto = 18;       // pos of first byte of 4-byte atom
 693             while (sp < sl) {
 694                 int b = src[sp++] & 0xff;
 695                 if ((b = base64[b]) < 0) {
 696                     if (b == -2) {         // padding byte '='
 697                         // =     shiftto==18 unnecessary padding
 698                         // x=    shiftto==12 a dangling single x
 699                         // x     to be handled together with non-padding case
 700                         // xx=   shiftto==6&&sp==sl missing last =
 701                         // xx=y  shiftto==6 last is not =
 702                         if (shiftto == 6 && (sp == sl || src[sp++] != '=') ||
 703                             shiftto == 18) {
 704                             throw new IllegalArgumentException(
 705                                 "Input byte array has wrong 4-byte ending unit");
 706                         }
 707                         break;
 708                     }
 709                     if (isMIME)    // skip if for rfc2045
 710                         continue;
 711                     else
 712                         throw new IllegalArgumentException(
 713                             "Illegal base64 character " +
 714                             Integer.toString(src[sp - 1], 16));
 715                 }
 716                 bits |= (b << shiftto);
 717                 shiftto -= 6;
 718                 if (shiftto < 0) {
 719                     dst[dp++] = (byte)(bits >> 16);
 720                     dst[dp++] = (byte)(bits >>  8);
 721                     dst[dp++] = (byte)(bits);
 722                     shiftto = 18;
 723                     bits = 0;
 724                 }
 725             }
 726             // reached end of byte array or hit padding '=' characters.
 727             if (shiftto == 6) {
 728                 dst[dp++] = (byte)(bits >> 16);
 729             } else if (shiftto == 0) {
 730                 dst[dp++] = (byte)(bits >> 16);
 731                 dst[dp++] = (byte)(bits >>  8);
 732             } else if (shiftto == 12) {
 733                 // dangling single "x", incorrectly encoded.
 734                 throw new IllegalArgumentException(
 735                     "Last unit does not have enough valid bits");
 736             }
 737             // anything left is invalid, if is not MIME.
 738             // if MIME, ignore all non-base64 character
 739             while (sp < sl) {
 740                 if (isMIME && base64[src[sp++]] < 0)
 741                     continue;
 742                 throw new IllegalArgumentException(
 743                     "Input byte array has incorrect ending byte at " + sp);
 744             }
 745             return dp;
 746         }
 747     }
 748 
 749     /*
 750      * An output stream for encoding bytes into the Base64.
 751      */
 752     private static class EncOutputStream extends FilterOutputStream {
 753 
 754         private int leftover = 0;
 755         private int b0, b1, b2;
 756         private boolean closed = false;
 757 
 758         private final char[] base64;    // byte->base64 mapping
 759         private final byte[] newline;   // line separator, if needed
 760         private final int linemax;
 761         private final boolean doPadding;// whether or not to pad
 762         private int linepos = 0;
 763 
 764         EncOutputStream(OutputStream os, char[] base64,
 765                         byte[] newline, int linemax, boolean doPadding) {
 766             super(os);
 767             this.base64 = base64;
 768             this.newline = newline;
 769             this.linemax = linemax;
 770             this.doPadding = doPadding;
 771         }
 772 
 773         @Override
 774         public void write(int b) throws IOException {
 775             byte[] buf = new byte[1];
 776             buf[0] = (byte)(b & 0xff);
 777             write(buf, 0, 1);
 778         }
 779 
 780         private void checkNewline() throws IOException {
 781             if (linepos == linemax) {
 782                 out.write(newline);
 783                 linepos = 0;
 784             }
 785         }
 786 
 787         @Override
 788         public void write(byte[] b, int off, int len) throws IOException {
 789             if (closed)
 790                 throw new IOException("Stream is closed");
 791             if (off < 0 || len < 0 || off + len > b.length)
 792                 throw new ArrayIndexOutOfBoundsException();
 793             if (len == 0)
 794                 return;
 795             if (leftover != 0) {
 796                 if (leftover == 1) {
 797                     b1 = b[off++] & 0xff;
 798                     len--;
 799                     if (len == 0) {
 800                         leftover++;
 801                         return;
 802                     }
 803                 }
 804                 b2 = b[off++] & 0xff;
 805                 len--;
 806                 checkNewline();
 807                 out.write(base64[b0 >> 2]);
 808                 out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 809                 out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]);
 810                 out.write(base64[b2 & 0x3f]);
 811                 linepos += 4;
 812             }
 813             int nBits24 = len / 3;
 814             leftover = len - (nBits24 * 3);
 815             while (nBits24-- > 0) {
 816                 checkNewline();
 817                 int bits = (b[off++] & 0xff) << 16 |
 818                            (b[off++] & 0xff) <<  8 |
 819                            (b[off++] & 0xff);
 820                 out.write(base64[(bits >>> 18) & 0x3f]);
 821                 out.write(base64[(bits >>> 12) & 0x3f]);
 822                 out.write(base64[(bits >>> 6)  & 0x3f]);
 823                 out.write(base64[bits & 0x3f]);
 824                 linepos += 4;
 825            }
 826             if (leftover == 1) {
 827                 b0 = b[off++] & 0xff;
 828             } else if (leftover == 2) {
 829                 b0 = b[off++] & 0xff;
 830                 b1 = b[off++] & 0xff;
 831             }
 832         }
 833 
 834         @Override
 835         public void close() throws IOException {
 836             if (!closed) {
 837                 closed = true;
 838                 if (leftover == 1) {
 839                     checkNewline();
 840                     out.write(base64[b0 >> 2]);
 841                     out.write(base64[(b0 << 4) & 0x3f]);
 842                     if (doPadding) {
 843                         out.write('=');
 844                         out.write('=');
 845                     }
 846                 } else if (leftover == 2) {
 847                     checkNewline();
 848                     out.write(base64[b0 >> 2]);
 849                     out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
 850                     out.write(base64[(b1 << 2) & 0x3f]);
 851                     if (doPadding) {
 852                        out.write('=');
 853                     }
 854                 }
 855                 leftover = 0;
 856                 out.close();
 857             }
 858         }
 859     }
 860 
 861     /*
 862      * An input stream for decoding Base64 bytes
 863      */
 864     private static class DecInputStream extends InputStream {
 865 
 866         private final InputStream is;
 867         private final boolean isMIME;
 868         private final int[] base64;      // base64 -> byte mapping
 869         private int bits = 0;            // 24-bit buffer for decoding
 870         private int nextin = 18;         // next available "off" in "bits" for input;
 871                                          // -> 18, 12, 6, 0
 872         private int nextout = -8;        // next available "off" in "bits" for output;
 873                                          // -> 8, 0, -8 (no byte for output)
 874         private boolean eof = false;
 875         private boolean closed = false;
 876 
 877         DecInputStream(InputStream is, int[] base64, boolean isMIME) {
 878             this.is = is;
 879             this.base64 = base64;
 880             this.isMIME = isMIME;
 881         }
 882 
 883         private byte[] sbBuf = new byte[1];
 884 
 885         @Override
 886         public int read() throws IOException {
 887             return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
 888         }
 889 
 890         @Override
 891         public int read(byte[] b, int off, int len) throws IOException {
 892             if (closed)
 893                 throw new IOException("Stream is closed");
 894             if (eof && nextout < 0)    // eof and no leftover
 895                 return -1;
 896             if (off < 0 || len < 0 || len > b.length - off)
 897                 throw new IndexOutOfBoundsException();
 898             int oldOff = off;
 899             if (nextout >= 0) {       // leftover output byte(s) in bits buf
 900                 do {
 901                     if (len == 0)
 902                         return off - oldOff;
 903                     b[off++] = (byte)(bits >> nextout);
 904                     len--;
 905                     nextout -= 8;
 906                 } while (nextout >= 0);
 907                 bits = 0;
 908             }
 909             while (len > 0) {
 910                 int v = is.read();
 911                 if (v == -1) {
 912                     eof = true;
 913                     if (nextin != 18) {
 914                         if (nextin == 12)
 915                             throw new IOException("Base64 stream has one un-decoded dangling byte.");
 916                         // treat ending xx/xxx without padding character legal.
 917                         // same logic as v == '=' below
 918                         b[off++] = (byte)(bits >> (16));
 919                         len--;
 920                         if (nextin == 0) {           // only one padding byte
 921                             if (len == 0) {          // no enough output space
 922                                 bits >>= 8;          // shift to lowest byte
 923                                 nextout = 0;
 924                             } else {
 925                                 b[off++] = (byte) (bits >>  8);
 926                             }
 927                         }
 928                     }
 929                     if (off == oldOff)
 930                         return -1;
 931                     else
 932                         return off - oldOff;
 933                 }
 934                 if (v == '=') {                  // padding byte(s)
 935                     // =     shiftto==18 unnecessary padding
 936                     // x=    shiftto==12 dangling x, invalid unit
 937                     // xx=   shiftto==6 && missing last '='
 938                     // xx=y  or last is not '='
 939                     if (nextin == 18 || nextin == 12 ||
 940                         nextin == 6 && is.read() != '=') {
 941                         throw new IOException("Illegal base64 ending sequence:" + nextin);
 942                     }
 943                     b[off++] = (byte)(bits >> (16));
 944                     len--;
 945                     if (nextin == 0) {           // only one padding byte
 946                         if (len == 0) {          // no enough output space
 947                             bits >>= 8;          // shift to lowest byte
 948                             nextout = 0;
 949                         } else {
 950                             b[off++] = (byte) (bits >>  8);
 951                         }
 952                     }
 953                     eof = true;
 954                     break;
 955                 }
 956                 if ((v = base64[v]) == -1) {
 957                     if (isMIME)                 // skip if for rfc2045
 958                         continue;
 959                     else
 960                         throw new IOException("Illegal base64 character " +
 961                             Integer.toString(v, 16));
 962                 }
 963                 bits |= (v << nextin);
 964                 if (nextin == 0) {
 965                     nextin = 18;    // clear for next
 966                     nextout = 16;
 967                     while (nextout >= 0) {
 968                         b[off++] = (byte)(bits >> nextout);
 969                         len--;
 970                         nextout -= 8;
 971                         if (len == 0 && nextout >= 0) {  // don't clean "bits"
 972                             return off - oldOff;
 973                         }
 974                     }
 975                     bits = 0;
 976                 } else {
 977                     nextin -= 6;
 978                 }
 979             }
 980             return off - oldOff;
 981         }
 982 
 983         @Override
 984         public int available() throws IOException {
 985             if (closed)
 986                 throw new IOException("Stream is closed");
 987             return is.available();   // TBD:
 988         }
 989 
 990         @Override
 991         public void close() throws IOException {
 992             if (!closed) {
 993                 closed = true;
 994                 is.close();
 995             }
 996         }
 997     }
 998 }