Base64.java revision 1d70b849467dfcfa30f495e6d03bcbdef47b2588
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
26package java.util;
27
28import java.io.FilterOutputStream;
29import java.io.InputStream;
30import java.io.IOException;
31import java.io.OutputStream;
32import java.nio.ByteBuffer;
33import 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
77public 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