Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (c) 2012, 2016, 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 || len > b.length - off)
    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 }
    999