001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.schematic;
017
018import java.io.BufferedInputStream;
019import java.io.IOException;
020
021/**
022 * <p>
023 * Encodes and decodes to and from Base64 notation.
024 * </p>
025 * <p>
026 * Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.
027 * </p>
028 * <p>
029 * Example:
030 * </p>
031 * <code>String encoded = Base64.encode( myByteArray );</code> <br /> <code>byte[] myByteArray = Base64.decode( encoded );</code>
032 * <p>
033 * The <tt>options</tt> parameter, which appears in a few places, is used to pass several pieces of information to the encoder. In
034 * the "higher level" methods such as encodeBytes( bytes, options ) the options parameter can be used to indicate such things as
035 * first gzipping the bytes before encoding them, not inserting linefeeds, and encoding using the URL-safe and Ordered dialects.
036 * </p>
037 * <p>
038 * Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>, Section 2.1, implementations should not add
039 * line feeds unless explicitly told to do so. I've got Base64 set to this behavior now, although earlier versions broke lines by
040 * default.
041 * </p>
042 * <p>
043 * The constants defined in Base64 can be OR-ed together to combine options, so you might make a call like this:
044 * </p>
045 * <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code>
046 * <p>
047 * to compress the data before encoding it and then making the output have newline characters.
048 * </p>
049 * <p>
050 * Also...
051 * </p>
052 * <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code>
053 * <p>
054 * Change Log:
055 * </p>
056 * <ul>
057 * <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the value 01111111, which is an invalid base 64 character but
058 * should not throw an ArrayIndexOutOfBoundsException either. Led to discovery of mishandling (or potential for better handling)
059 * of other bad input characters. You should now get an IOException if you try decoding something that has bad characters in it.</li>
060 * <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded string ended in the last column; the buffer was
061 * not properly shrunk and contained an extra (null) byte that made it into the string.</li>
062 * <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size was wrong for files of size 31, 34, and 37 bytes.
063 * </li>
064 * <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing the Base64.OutputStream closed the Base64 encoding
065 * (by padding with equals signs) too soon. Also added an option to suppress the automatic decoding of gzipped streams. Also added
066 * experimental support for specifying a class loader when using the
067 * {@link #decodeToObject(String, int, ClassLoader)} method.</li>
068 * <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java footprint with its CharEncoders and so
069 * forth. Fixed some javadocs that were inconsistent. Removed imports and specified things like java.io.IOException explicitly
070 * inline.</li>
071 * <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the final encoded data will be so that the
072 * code doesn't have to create two output arrays: an oversized initial one and then a final, exact-sized one. Big win when using
073 * the {@link #encodeBytesToBytes(byte[])} family of methods (and not using the gzip options which uses a different mechanism with
074 * streams and stuff).</li>
075 * <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some similar helper methods to be more efficient with
076 * memory by not returning a String but just a byte array.</li>
077 * <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments and bug fixes queued up and
078 * finally executed. Thanks to everyone who sent me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else.
079 * Much bad coding was cleaned up including throwing exceptions where necessary instead of returning null values or something
080 * similar. Here are some changes that may affect you:
081 * <ul>
082 * <li><em>Does not break lines, by default.</em> This is to keep in compliance with <a
083 * href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li>
084 * <li><em>Throws exceptions instead of returning null values.</em> Because some operations (especially those that may permit the
085 * GZIP option) use IO streams, there is a possiblity of an java.io.IOException being thrown. After some discussion and thought,
086 * I've changed the behavior of the methods to throw java.io.IOExceptions rather than return null if ever there's an error. I
087 * think this is more appropriate, though it will require some changes to your code. Sorry, it should have been done this way to
088 * begin with.</li>
089 * <li><em>Removed all references to System.out, System.err, and the like.</em> Shame on me. All I can say is sorry they were ever
090 * there.</li>
091 * <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed such as when passed arrays are null or
092 * offsets are invalid.</li>
093 * <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings. This was especially annoying before for people who
094 * were thorough in their own projects and then had gobs of javadoc warnings on this file.</li>
095 * </ul>
096 * <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug when using very small files (~&lt; 40 bytes).</li>
097 * <li>v2.2 - Added some helper methods for encoding/decoding directly from one file to the next. Also added a main() method to
098 * support command line encoding/decoding from one file to the next. Also added these Base64 dialects:
099 * <ol>
100 * <li>The default is RFC3548 format.</li>
101 * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates URL and file name friendly format as described in
102 * Section 4 of RFC3548. http://www.faqs.org/rfcs/rfc3548.html</li>
103 * <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates URL and file name friendly format that preserves
104 * lexical ordering as described in http://www.faqs.org/qa/rfcc-1940.html</li>
105 * </ol>
106 * Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a> for contributing the new
107 * Base64 dialects.</li>
108 * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading and writing
109 * to and from files.</li>
110 * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like EBCDIC).</li>
111 * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.</li>
112 * <li>v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner. The code
113 * now detects when data that's being decoded is gzip-compressed and will decompress it automatically. Generally things are
114 * cleaner. You'll probably have to change some method calls that you were making to support the new options format (<tt>int</tt>s
115 * that you "OR" together).</li>
116 * <li>v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
117 * Added the ability to "suspend" encoding in the Output Stream so you can turn on and off the encoding if you need to embed
118 * base64 data in an otherwise "normal" stream (like an XML file).</li>
119 * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. This helps when using GZIP streams. Added the
120 * ability to GZip-compress objects before encoding them.</li>
121 * <li>v1.4 - Added helper methods to read/write files.</li>
122 * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
123 * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if not
124 * completely full, was not returned.</li>
125 * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
126 * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
127 * </ul>
128 * <p>
129 * I am placing this code in the Public Domain. Do with it as you will. This software comes with no guarantees or warranties but
130 * with plenty of well-wishing instead! Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
131 * periodically to check for updates or to contribute improvements.
132 * </p>
133 * 
134 * @author Robert Harder
135 * @author rob@iharder.net
136 * @version 2.3.7
137 */
138public class Base64 {
139
140    /* ********  P U B L I C   F I E L D S  ******** */
141
142    /** No options specified. Value is zero. */
143    public final static int NO_OPTIONS = 0;
144
145    /** Specify encoding in first bit. Value is one. */
146    public final static int ENCODE = 1;
147
148    /** Specify decoding in first bit. Value is zero. */
149    public final static int DECODE = 0;
150
151    /** Specify that data should be gzip-compressed in second bit. Value is two. */
152    public final static int GZIP = 2;
153
154    /** Specify that gzipped data should <em>not</em> be automatically gunzipped. */
155    public final static int DONT_GUNZIP = 4;
156
157    /** Do break lines when encoding. Value is 8. */
158    public final static int DO_BREAK_LINES = 8;
159
160    /**
161     * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of RFC3548: <a
162     * href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. It is important to note that data
163     * encoded this way is <em>not</em> officially valid Base64, or at the very least should not be called Base64 without also
164     * specifying that is was encoded using the URL- and Filename-safe dialect.
165     */
166    public final static int URL_SAFE = 16;
167
168    /**
169     * Encode using the special "ordered" dialect of Base64 described here: <a
170     * href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
171     */
172    public final static int ORDERED = 32;
173
174    /* ********  P R I V A T E   F I E L D S  ******** */
175
176    /** Maximum line length (76) of Base64 output. */
177    private final static int MAX_LINE_LENGTH = 76;
178
179    /** The equals sign (=) as a byte. */
180    private final static byte EQUALS_SIGN = (byte)'=';
181
182    /** The new line character (\n) as a byte. */
183    private final static byte NEW_LINE = (byte)'\n';
184
185    /** Preferred encoding. */
186    private final static String PREFERRED_ENCODING = "US-ASCII";
187
188    private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
189    private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
190
191    /* ********  S T A N D A R D   B A S E 6 4   A L P H A B E T  ******** */
192
193    /** The 64 valid Base64 values. */
194    /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
195    private final static byte[] _STANDARD_ALPHABET = {(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
196        (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q',
197        (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b',
198        (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m',
199        (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
200        (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
201        (byte)'9', (byte)'+', (byte)'/'};
202
203    /**
204     * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other meaning.
205     **/
206    private final static byte[] _STANDARD_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
207        -5, -5, // Whitespace: Tab and Linefeed
208        -9, -9, // Decimal 11 - 12
209        -5, // Whitespace: Carriage Return
210        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
211        -9, -9, -9, -9, -9, // Decimal 27 - 31
212        -5, // Whitespace: Space
213        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
214        62, // Plus sign at decimal 43
215        -9, -9, -9, // Decimal 44 - 46
216        63, // Slash at decimal 47
217        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
218        -9, -9, -9, // Decimal 58 - 60
219        -1, // Equals sign at decimal 61
220        -9, -9, -9, // Decimal 62 - 64
221        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
222        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
223        -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
224        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
225        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
226        -9, -9, -9, -9, -9 // Decimal 123 - 127
227        , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
228        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
229        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
230        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
231        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
232        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
233        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
234        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
235        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
236        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
237    };
238
239    /* ********  U R L   S A F E   B A S E 6 4   A L P H A B E T  ******** */
240
241    /**
242     * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: <a
243     * href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. Notice that the last two bytes
244     * become "hyphen" and "underscore" instead of "plus" and "slash."
245     */
246    private final static byte[] _URL_SAFE_ALPHABET = {(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
247        (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q',
248        (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b',
249        (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m',
250        (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
251        (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
252        (byte)'9', (byte)'-', (byte)'_'};
253
254    /**
255     * Used in decoding URL- and Filename-safe dialects of Base64.
256     */
257    private final static byte[] _URL_SAFE_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
258        -5, -5, // Whitespace: Tab and Linefeed
259        -9, -9, // Decimal 11 - 12
260        -5, // Whitespace: Carriage Return
261        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
262        -9, -9, -9, -9, -9, // Decimal 27 - 31
263        -5, // Whitespace: Space
264        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
265        -9, // Plus sign at decimal 43
266        -9, // Decimal 44
267        62, // Minus sign at decimal 45
268        -9, // Decimal 46
269        -9, // Slash at decimal 47
270        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
271        -9, -9, -9, // Decimal 58 - 60
272        -1, // Equals sign at decimal 61
273        -9, -9, -9, // Decimal 62 - 64
274        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
275        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
276        -9, -9, -9, -9, // Decimal 91 - 94
277        63, // Underscore at decimal 95
278        -9, // Decimal 96
279        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
280        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
281        -9, -9, -9, -9, -9 // Decimal 123 - 127
282        , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
283        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
284        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
285        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
286        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
287        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
288        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
289        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
290        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
291        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
292    };
293
294    /* ********  O R D E R E D   B A S E 6 4   A L P H A B E T  ******** */
295
296    /**
297     * I don't get the point of this technique, but someone requested it, and it is described here: <a
298     * href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
299     */
300    private final static byte[] _ORDERED_ALPHABET = {(byte)'-', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
301        (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
302        (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R',
303        (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'_', (byte)'a', (byte)'b',
304        (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m',
305        (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
306        (byte)'y', (byte)'z'};
307
308    /**
309     * Used in decoding the "ordered" dialect of Base64.
310     */
311    private final static byte[] _ORDERED_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
312        -5, -5, // Whitespace: Tab and Linefeed
313        -9, -9, // Decimal 11 - 12
314        -5, // Whitespace: Carriage Return
315        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
316        -9, -9, -9, -9, -9, // Decimal 27 - 31
317        -5, // Whitespace: Space
318        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
319        -9, // Plus sign at decimal 43
320        -9, // Decimal 44
321        0, // Minus sign at decimal 45
322        -9, // Decimal 46
323        -9, // Slash at decimal 47
324        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine
325        -9, -9, -9, // Decimal 58 - 60
326        -1, // Equals sign at decimal 61
327        -9, -9, -9, // Decimal 62 - 64
328        11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M'
329        24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z'
330        -9, -9, -9, -9, // Decimal 91 - 94
331        37, // Underscore at decimal 95
332        -9, // Decimal 96
333        38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm'
334        51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z'
335        -9, -9, -9, -9, -9 // Decimal 123 - 127
336        , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139
337        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152
338        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165
339        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178
340        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191
341        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204
342        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217
343        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230
344        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243
345        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255
346    };
347
348    /* ********  D E T E R M I N E   W H I C H   A L H A B E T  ******** */
349
350    /**
351     * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's possible, though silly, to
352     * specify ORDERED <b>and</b> URLSAFE in which case one of them will be picked, though there is no guarantee as to which one
353     * will be picked.
354     * 
355     * @param options the options
356     * @return the byte array; never null
357     */
358    private final static byte[] getAlphabet( int options ) {
359        if ((options & URL_SAFE) == URL_SAFE) {
360            return _URL_SAFE_ALPHABET;
361        } else if ((options & ORDERED) == ORDERED) {
362            return _ORDERED_ALPHABET;
363        } else {
364            return _STANDARD_ALPHABET;
365        }
366    } // end getAlphabet
367
368    /**
369     * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's possible, though silly, to
370     * specify ORDERED and URL_SAFE in which case one of them will be picked, though there is no guarantee as to which one will be
371     * picked.
372     * 
373     * @param options the options
374     * @return the byte array; never null
375     */
376    private final static byte[] getDecodabet( int options ) {
377        if ((options & URL_SAFE) == URL_SAFE) {
378            return _URL_SAFE_DECODABET;
379        } else if ((options & ORDERED) == ORDERED) {
380            return _ORDERED_DECODABET;
381        } else {
382            return _STANDARD_DECODABET;
383        }
384    } // end getAlphabet
385
386    /** Defeats instantiation. */
387    private Base64() {
388    }
389
390    /* ********  E N C O D I N G   M E T H O D S  ******** */
391
392    /**
393     * Encodes up to the first three bytes of array <var>threeBytes</var> and returns a four-byte array in Base64 notation. The
394     * actual number of significant bytes in your array is given by <var>numSigBytes</var>. The array <var>threeBytes</var> needs
395     * only be as big as <var>numSigBytes</var>. Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
396     * 
397     * @param b4 A reusable byte array to reduce array instantiation
398     * @param threeBytes the array to convert
399     * @param numSigBytes the number of significant bytes in your array
400     * @param options the options
401     * @return four byte array in Base64 notation.
402     * @since 1.5.1
403     */
404    private static byte[] encode3to4( byte[] b4,
405                                      byte[] threeBytes,
406                                      int numSigBytes,
407                                      int options ) {
408        encode3to4(threeBytes, 0, numSigBytes, b4, 0, options);
409        return b4;
410    } // end encode3to4
411
412    /**
413     * <p>
414     * Encodes up to three bytes of the array <var>source</var> and writes the resulting four Base64 bytes to
415     * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by specifying
416     * <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays are large enough to
417     * accomodate <var>srcOffset</var> + 3 for the <var>source</var> array or <var>destOffset</var> + 4 for the
418     * <var>destination</var> array. The actual number of significant bytes in your array is given by <var>numSigBytes</var>.
419     * </p>
420     * <p>
421     * This is the lowest level of the encoding methods with all possible parameters.
422     * </p>
423     * 
424     * @param source the array to convert
425     * @param srcOffset the index where conversion begins
426     * @param numSigBytes the number of significant bytes in your array
427     * @param destination the array to hold the conversion
428     * @param destOffset the index where output will be put
429     * @param options the options
430     * @return the <var>destination</var> array
431     * @since 1.3
432     */
433    private static byte[] encode3to4( byte[] source,
434                                      int srcOffset,
435                                      int numSigBytes,
436                                      byte[] destination,
437                                      int destOffset,
438                                      int options ) {
439
440        byte[] ALPHABET = getAlphabet(options);
441
442        // 1 2 3
443        // 01234567890123456789012345678901 Bit position
444        // --------000000001111111122222222 Array position from threeBytes
445        // --------| || || || | Six bit groups to index ALPHABET
446        // >>18 >>12 >> 6 >> 0 Right shift necessary
447        // 0x3f 0x3f 0x3f Additional AND
448
449        // Create buffer with zero-padding if there are only one or two
450        // significant bytes passed in the array.
451        // We have to shift left 24 in order to flush out the 1's that appear
452        // when Java treats a value as negative that is cast from a byte to an int.
453        int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
454                     | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
455                     | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
456
457        switch (numSigBytes) {
458            case 3:
459                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
460                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
461                destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
462                destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
463                return destination;
464
465            case 2:
466                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
467                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
468                destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
469                destination[destOffset + 3] = EQUALS_SIGN;
470                return destination;
471
472            case 1:
473                destination[destOffset] = ALPHABET[(inBuff >>> 18)];
474                destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
475                destination[destOffset + 2] = EQUALS_SIGN;
476                destination[destOffset + 3] = EQUALS_SIGN;
477                return destination;
478
479            default:
480                return destination;
481        } // end switch
482    } // end encode3to4
483
484    /**
485     * Encodes content of the supplied InputStream into Base64 notation. Does not GZip-compress data.
486     * 
487     * @param source The data to convert
488     * @return the encoded bytes
489     */
490    public static String encode( java.io.InputStream source ) {
491        return encode(source, NO_OPTIONS);
492    }
493
494    /**
495     * Encodes the content of the supplied InputStream into Base64 notation.
496     * <p>
497     * Valid options:
498     * 
499     * <pre>
500     *   GZIP: gzip-compresses object before encoding it.
501     *   DONT_BREAK_LINES: don't break lines at 76 characters
502     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
503     * </pre>
504     * <p>
505     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
506     * <p>
507     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
508     * 
509     * @param source The data to convert
510     * @param options Specified options- the alphabet type is pulled from this (standard, url-safe, ordered)
511     * @return the encoded bytes
512     * @see Base64#GZIP
513     */
514    public static String encode( java.io.InputStream source,
515                                 int options ) {
516        if (source == null) {
517            throw new IllegalArgumentException("Source cannot be null");
518        }
519        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
520        Base64.OutputStream b64os = new Base64.OutputStream(baos, ENCODE | options);
521        BufferedInputStream input = new BufferedInputStream(source);
522        java.io.OutputStream output = b64os;
523
524        boolean error = false;
525        try {
526            if ((options & GZIP) == GZIP) {
527                output = new java.util.zip.GZIPOutputStream(output);
528            }
529            int numRead = 0;
530            byte[] buffer = new byte[1024];
531            while ((numRead = input.read(buffer)) > -1) {
532                output.write(buffer, 0, numRead);
533            }
534            output.close();
535        } catch (IOException e) {
536            error = true;
537            throw new RuntimeException(e); // error using reading from byte array!
538        } finally {
539            try {
540                input.close();
541            } catch (IOException e) {
542                if (!error) new RuntimeException(e); // error closing input stream
543            }
544        }
545
546        // Return value according to relevant encoding.
547        try {
548            return new String(baos.toByteArray(), PREFERRED_ENCODING);
549        } catch (java.io.UnsupportedEncodingException uue) {
550            return new String(baos.toByteArray());
551        }
552    }
553
554    /**
555     * Performs Base64 encoding on the <code>raw</code> ByteBuffer, writing it to the <code>encoded</code> ByteBuffer. This is an
556     * experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}.
557     * 
558     * @param raw input buffer
559     * @param encoded output buffer
560     * @since 2.3
561     */
562    public static void encode( java.nio.ByteBuffer raw,
563                               java.nio.ByteBuffer encoded ) {
564        byte[] raw3 = new byte[3];
565        byte[] enc4 = new byte[4];
566
567        while (raw.hasRemaining()) {
568            int rem = Math.min(3, raw.remaining());
569            raw.get(raw3, 0, rem);
570            Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
571            encoded.put(enc4);
572        } // end input remaining
573    }
574
575    /**
576     * Performs Base64 encoding on the <code>raw</code> ByteBuffer, writing it to the <code>encoded</code> CharBuffer. This is an
577     * experimental feature. Currently it does not pass along any options (such as {@link #DO_BREAK_LINES} or {@link #GZIP}.
578     * 
579     * @param raw input buffer
580     * @param encoded output buffer
581     * @since 2.3
582     */
583    public static void encode( java.nio.ByteBuffer raw,
584                               java.nio.CharBuffer encoded ) {
585        byte[] raw3 = new byte[3];
586        byte[] enc4 = new byte[4];
587
588        while (raw.hasRemaining()) {
589            int rem = Math.min(3, raw.remaining());
590            raw.get(raw3, 0, rem);
591            Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS);
592            for (int i = 0; i < 4; i++) {
593                encoded.put((char)(enc4[i] & 0xFF));
594            }
595        } // end input remaining
596    }
597
598    /**
599     * Serializes an object and returns the Base64-encoded version of that serialized object.
600     * <p>
601     * As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException.
602     * <b>This is new to v2.3!</b> In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way
603     * to handle it.
604     * </p>
605     * The object is not GZip-compressed before being encoded.
606     * 
607     * @param serializableObject The object to encode
608     * @return The Base64-encoded object
609     * @throws java.io.IOException if there is an error
610     * @throws NullPointerException if serializedObject is null
611     * @since 1.4
612     */
613    public static String encodeObject( java.io.Serializable serializableObject ) throws IOException {
614        return encodeObject(serializableObject, NO_OPTIONS);
615    } // end encodeObject
616
617    /**
618     * Serializes an object and returns the Base64-encoded version of that serialized object.
619     * <p>
620     * As of v 2.3, if the object cannot be serialized or there is another error, the method will throw an java.io.IOException.
621     * <b>This is new to v2.3!</b> In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way
622     * to handle it.
623     * </p>
624     * The object is not GZip-compressed before being encoded.
625     * <p>
626     * Example options:
627     * 
628     * <pre>
629     *   GZIP: gzip-compresses object before encoding it.
630     *   DO_BREAK_LINES: break lines at 76 characters
631     * </pre>
632     * <p>
633     * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
634     * <p>
635     * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
636     * 
637     * @param serializableObject The object to encode
638     * @param options Specified options
639     * @return The Base64-encoded object
640     * @see Base64#GZIP
641     * @see Base64#DO_BREAK_LINES
642     * @throws java.io.IOException if there is an error
643     * @since 2.0
644     */
645    public static String encodeObject( java.io.Serializable serializableObject,
646                                       int options ) throws IOException {
647
648        if (serializableObject == null) {
649            throw new NullPointerException("Cannot serialize a null object.");
650        } // end if: null
651
652        // Streams
653        java.io.ByteArrayOutputStream baos = null;
654        java.io.OutputStream b64os = null;
655        java.util.zip.GZIPOutputStream gzos = null;
656        java.io.ObjectOutputStream oos = null;
657
658        try {
659            // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
660            baos = new java.io.ByteArrayOutputStream();
661            b64os = new Base64.OutputStream(baos, ENCODE | options);
662            if ((options & GZIP) != 0) {
663                // Gzip
664                gzos = new java.util.zip.GZIPOutputStream(b64os);
665                oos = new java.io.ObjectOutputStream(gzos);
666            } else {
667                // Not gzipped
668                oos = new java.io.ObjectOutputStream(b64os);
669            }
670            oos.writeObject(serializableObject);
671        } // end try
672        catch (IOException e) {
673            // Catch it and then throw it immediately so that
674            // the finally{} block is called for cleanup.
675            throw e;
676        } // end catch
677        finally {
678            try {
679                if (oos != null) oos.close();
680            } catch (Exception e) {
681            }
682            try {
683                if (gzos != null) gzos.close();
684            } catch (Exception e) {
685            }
686            try {
687                if (b64os != null) b64os.close();
688            } catch (Exception e) {
689            }
690            try {
691                if (baos != null) baos.close();
692            } catch (Exception e) {
693            }
694        } // end finally
695
696        // Return value according to relevant encoding.
697        assert baos != null;
698        try {
699            return new String(baos.toByteArray(), PREFERRED_ENCODING);
700        } // end try
701        catch (java.io.UnsupportedEncodingException uue) {
702            // Fall back to some Java default
703            return new String(baos.toByteArray());
704        } // end catch
705
706    } // end encode
707
708    /**
709     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
710     * 
711     * @param source The data to convert
712     * @return The data in Base64-encoded form
713     * @throws NullPointerException if source array is null
714     * @since 1.4
715     */
716    public static String encodeBytes( byte[] source ) {
717        // Since we're not going to have the GZIP encoding turned on,
718        // we're not going to have an java.io.IOException thrown, so
719        // we should not force the user to have to catch it.
720        String encoded = null;
721        try {
722            encoded = encodeBytes(source, 0, source.length, NO_OPTIONS);
723        } catch (IOException ex) {
724            assert false : ex.getMessage();
725        } // end catch
726        assert encoded != null;
727        return encoded;
728    } // end encodeBytes
729
730    /**
731     * Encodes a byte array into Base64 notation.
732     * <p>
733     * Example options:
734     * 
735     * <pre>
736     *   GZIP: gzip-compresses object before encoding it.
737     *   DO_BREAK_LINES: break lines at 76 characters
738     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
739     * </pre>
740     * <p>
741     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
742     * <p>
743     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
744     * <p>
745     * As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. <b>This is new to
746     * v2.3!</b> In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
747     * </p>
748     * 
749     * @param source The data to convert
750     * @param options Specified options
751     * @return The Base64-encoded data as a String
752     * @see Base64#GZIP
753     * @see Base64#DO_BREAK_LINES
754     * @throws java.io.IOException if there is an error
755     * @throws NullPointerException if source array is null
756     * @since 2.0
757     */
758    public static String encodeBytes( byte[] source,
759                                      int options ) throws IOException {
760        return encodeBytes(source, 0, source.length, options);
761    } // end encodeBytes
762
763    /**
764     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
765     * <p>
766     * As of v 2.3, if there is an error, the method will throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
767     * versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
768     * </p>
769     * 
770     * @param source The data to convert
771     * @param off Offset in array where conversion should begin
772     * @param len Length of data to convert
773     * @return The Base64-encoded data as a String
774     * @throws NullPointerException if source array is null
775     * @throws IllegalArgumentException if source array, offset, or length are invalid
776     * @since 1.4
777     */
778    public static String encodeBytes( byte[] source,
779                                      int off,
780                                      int len ) {
781        // Since we're not going to have the GZIP encoding turned on,
782        // we're not going to have an java.io.IOException thrown, so
783        // we should not force the user to have to catch it.
784        String encoded = null;
785        try {
786            encoded = encodeBytes(source, off, len, NO_OPTIONS);
787        } catch (IOException ex) {
788            assert false : ex.getMessage();
789        } // end catch
790        assert encoded != null;
791        return encoded;
792    } // end encodeBytes
793
794    /**
795     * Encodes a byte array into Base64 notation.
796     * <p>
797     * Example options:
798     * 
799     * <pre>
800     *   GZIP: gzip-compresses object before encoding it.
801     *   DO_BREAK_LINES: break lines at 76 characters
802     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
803     * </pre>
804     * <p>
805     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
806     * <p>
807     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code>
808     * <p>
809     * As of v 2.3, if there is an error with the GZIP stream, the method will throw an java.io.IOException. <b>This is new to
810     * v2.3!</b> In earlier versions, it just returned a null value, but in retrospect that's a pretty poor way to handle it.
811     * </p>
812     * 
813     * @param source The data to convert
814     * @param off Offset in array where conversion should begin
815     * @param len Length of data to convert
816     * @param options Specified options
817     * @return The Base64-encoded data as a String
818     * @see Base64#GZIP
819     * @see Base64#DO_BREAK_LINES
820     * @throws java.io.IOException if there is an error
821     * @throws NullPointerException if source array is null
822     * @throws IllegalArgumentException if source array, offset, or length are invalid
823     * @since 2.0
824     */
825    public static String encodeBytes( byte[] source,
826                                      int off,
827                                      int len,
828                                      int options ) throws IOException {
829        byte[] encoded = encodeBytesToBytes(source, off, len, options);
830
831        // Return value according to relevant encoding.
832        try {
833            return new String(encoded, PREFERRED_ENCODING);
834        } // end try
835        catch (java.io.UnsupportedEncodingException uue) {
836            return new String(encoded);
837        } // end catch
838
839    } // end encodeBytes
840
841    /**
842     * Similar to {@link #encodeBytes(byte[])} but returns a byte array instead of instantiating a String. This is more efficient
843     * if you're working with I/O streams and have large data sets to encode.
844     * 
845     * @param source The data to convert
846     * @return The Base64-encoded data as a byte[] (of ASCII characters)
847     * @throws NullPointerException if source array is null
848     * @since 2.3.1
849     */
850    public static byte[] encodeBytesToBytes( byte[] source ) {
851        byte[] encoded = null;
852        try {
853            encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS);
854        } catch (IOException ex) {
855            assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
856        }
857        return encoded;
858    }
859
860    /**
861     * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns a byte array instead of instantiating a String. This is
862     * more efficient if you're working with I/O streams and have large data sets to encode.
863     * 
864     * @param source The data to convert
865     * @param off Offset in array where conversion should begin
866     * @param len Length of data to convert
867     * @param options Specified options
868     * @return The Base64-encoded data as a String
869     * @see Base64#GZIP
870     * @see Base64#DO_BREAK_LINES
871     * @throws java.io.IOException if there is an error
872     * @throws NullPointerException if source array is null
873     * @throws IllegalArgumentException if source array, offset, or length are invalid
874     * @since 2.3.1
875     */
876    public static byte[] encodeBytesToBytes( byte[] source,
877                                             int off,
878                                             int len,
879                                             int options ) throws IOException {
880
881        if (source == null) {
882            throw new NullPointerException("Cannot serialize a null array.");
883        } // end if: null
884
885        if (off < 0) {
886            throw new IllegalArgumentException("Cannot have negative offset: " + off);
887        } // end if: off < 0
888
889        if (len < 0) {
890            throw new IllegalArgumentException("Cannot have length offset: " + len);
891        } // end if: len < 0
892
893        if (off + len > source.length) {
894            throw new IllegalArgumentException(String.format(
895                    "Cannot have offset of %d and length of %d with array of length %d",
896                    off,
897                    len,
898                    source.length));
899        } // end if: off < 0
900
901        // Compress?
902        if ((options & GZIP) != 0) {
903            java.io.ByteArrayOutputStream baos = null;
904            java.util.zip.GZIPOutputStream gzos = null;
905            Base64.OutputStream b64os = null;
906
907            try {
908                // GZip -> Base64 -> ByteArray
909                baos = new java.io.ByteArrayOutputStream();
910                b64os = new Base64.OutputStream(baos, ENCODE | options);
911                gzos = new java.util.zip.GZIPOutputStream(b64os);
912
913                gzos.write(source, off, len);
914                gzos.close();
915            } // end try
916            catch (IOException e) {
917                // Catch it and then throw it immediately so that
918                // the finally{} block is called for cleanup.
919                throw e;
920            } // end catch
921            finally {
922                try {
923                    if (gzos != null) gzos.close();
924                } catch (Exception e) {
925                }
926                try {
927                    if (b64os != null) b64os.close();
928                } catch (Exception e) {
929                }
930                try {
931                    if (baos != null) baos.close();
932                } catch (Exception e) {
933                }
934            } // end finally
935
936            assert baos != null;
937            return baos.toByteArray();
938        } // end if: compress
939
940        // Else, don't compress. Better not to use streams at all then.
941        boolean breakLines = (options & DO_BREAK_LINES) != 0;
942
943        // int len43 = len * 4 / 3;
944        // byte[] outBuff = new byte[ ( len43 ) // Main 4:3
945        // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
946        // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
947        // Try to determine more precisely how big the array needs to be.
948        // If we get it right, we don't have to do an array copy, and
949        // we save a bunch of memory.
950        int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding
951        if (breakLines) {
952            encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters
953        }
954        byte[] outBuff = new byte[encLen];
955
956        int d = 0;
957        int e = 0;
958        int len2 = len - 2;
959        int lineLength = 0;
960        for (; d < len2; d += 3, e += 4) {
961            encode3to4(source, d + off, 3, outBuff, e, options);
962
963            lineLength += 4;
964            if (breakLines && lineLength >= MAX_LINE_LENGTH) {
965                outBuff[e + 4] = NEW_LINE;
966                e++;
967                lineLength = 0;
968            } // end if: end of line
969        } // en dfor: each piece of array
970
971        if (d < len) {
972            encode3to4(source, d + off, len - d, outBuff, e, options);
973            e += 4;
974        } // end if: some padding needed
975
976        // Only resize array if we didn't guess it right.
977        if (e <= outBuff.length - 1) {
978            // If breaking lines and the last byte falls right at
979            // the line length (76 bytes per line), there will be
980            // one extra byte, and the array will need to be resized.
981            // Not too bad of an estimate on array size, I'd say.
982            byte[] finalOut = new byte[e];
983            System.arraycopy(outBuff, 0, finalOut, 0, e);
984            // System.err.println("Having to resize array from " + outBuff.length + " to " + e );
985            return finalOut;
986        }
987        // System.err.println("No need to resize array.");
988        return outBuff;
989
990    } // end encodeBytesToBytes
991
992    /* ********  D E C O D I N G   M E T H O D S  ******** */
993
994    /**
995     * Decodes four bytes from array <var>source</var> and writes the resulting bytes (up to three of them) to
996     * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by specifying
997     * <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays are large enough to
998     * accomodate <var>srcOffset</var> + 4 for the <var>source</var> array or <var>destOffset</var> + 3 for the
999     * <var>destination</var> array. This method returns the actual number of bytes that were converted from the Base64 encoding.
1000     * <p>
1001     * This is the lowest level of the decoding methods with all possible parameters.
1002     * </p>
1003     * 
1004     * @param source the array to convert
1005     * @param srcOffset the index where conversion begins
1006     * @param destination the array to hold the conversion
1007     * @param destOffset the index where output will be put
1008     * @param options alphabet type is pulled from this (standard, url-safe, ordered)
1009     * @return the number of decoded bytes converted
1010     * @throws NullPointerException if source or destination arrays are null
1011     * @throws IllegalArgumentException if srcOffset or destOffset are invalid or there is not enough room in the array.
1012     * @since 1.3
1013     */
1014    private static int decode4to3( byte[] source,
1015                                   int srcOffset,
1016                                   byte[] destination,
1017                                   int destOffset,
1018                                   int options ) {
1019
1020        // Lots of error checking and exception throwing
1021        if (source == null) {
1022            throw new NullPointerException("Source array was null.");
1023        } // end if
1024        if (destination == null) {
1025            throw new NullPointerException("Destination array was null.");
1026        } // end if
1027        if (srcOffset < 0 || srcOffset + 3 >= source.length) {
1028            throw new IllegalArgumentException(
1029                                               String.format(
1030                                                       "Source array with length %d cannot have offset of %d and still process four bytes.",
1031                                                       source.length,
1032                                                       srcOffset));
1033        } // end if
1034        if (destOffset < 0 || destOffset + 2 >= destination.length) {
1035            throw new IllegalArgumentException(
1036                                               String.format(
1037                                                       "Destination array with length %d cannot have offset of %d and still store three bytes.",
1038                                                       destination.length,
1039                                                       destOffset));
1040        } // end if
1041
1042        byte[] DECODABET = getDecodabet(options);
1043
1044        // Example: Dk==
1045        if (source[srcOffset + 2] == EQUALS_SIGN) {
1046            // Two ways to do the same thing. Don't know which way I like best.
1047            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1048            // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
1049            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
1050
1051            destination[destOffset] = (byte)(outBuff >>> 16);
1052            return 1;
1053        }
1054
1055        // Example: DkL=
1056        else if (source[srcOffset + 3] == EQUALS_SIGN) {
1057            // Two ways to do the same thing. Don't know which way I like best.
1058            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1059            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
1060            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
1061            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
1062                          | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
1063
1064            destination[destOffset] = (byte)(outBuff >>> 16);
1065            destination[destOffset + 1] = (byte)(outBuff >>> 8);
1066            return 2;
1067        }
1068
1069        // Example: DkLE
1070        else {
1071            // Two ways to do the same thing. Don't know which way I like best.
1072            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
1073            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
1074            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
1075            // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
1076            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
1077                          | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF));
1078
1079            destination[destOffset] = (byte)(outBuff >> 16);
1080            destination[destOffset + 1] = (byte)(outBuff >> 8);
1081            destination[destOffset + 2] = (byte)(outBuff);
1082
1083            return 3;
1084        }
1085    } // end decodeToBytes
1086
1087    /**
1088     * Low-level access to decoding ASCII characters in the form of a byte array. <strong>Ignores GUNZIP option, if it's
1089     * set.</strong> This is not generally a recommended method, although it is used internally as part of the decoding process.
1090     * Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and
1091     * aren't gzipping), consider this method.
1092     * 
1093     * @param source The Base64 encoded data
1094     * @return decoded data
1095     * @throws java.io.IOException if there is an error decoding the supplied byte array
1096     * @since 2.3.1
1097     */
1098    public static byte[] decode( byte[] source ) throws IOException {
1099        byte[] decoded = null;
1100        // try {
1101        decoded = decode(source, 0, source.length, Base64.NO_OPTIONS);
1102        // } catch( java.io.IOException ex ) {
1103        // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
1104        // }
1105        return decoded;
1106    }
1107
1108    /**
1109     * Low-level access to decoding ASCII characters in the form of a byte array. <strong>Ignores GUNZIP option, if it's
1110     * set.</strong> This is not generally a recommended method, although it is used internally as part of the decoding process.
1111     * Special case: if len = 0, an empty array is returned. Still, if you need more speed and reduced memory footprint (and
1112     * aren't gzipping), consider this method.
1113     * 
1114     * @param source The Base64 encoded data
1115     * @param off The offset of where to begin decoding
1116     * @param len The length of characters to decode
1117     * @param options Can specify options such as alphabet type to use
1118     * @return decoded data
1119     * @throws java.io.IOException If bogus characters exist in source data
1120     * @since 1.3
1121     */
1122    public static byte[] decode( byte[] source,
1123                                 int off,
1124                                 int len,
1125                                 int options ) throws IOException {
1126
1127        // Lots of error checking and exception throwing
1128        if (source == null) {
1129            throw new NullPointerException("Cannot decode null source array.");
1130        } // end if
1131        if (off < 0 || off + len > source.length) {
1132            throw new IllegalArgumentException(
1133                                               String.format(
1134                                                       "Source array with length %d cannot have offset of %d and process %d bytes.",
1135                                                       source.length,
1136                                                       off,
1137                                                       len));
1138        } // end if
1139
1140        if (len == 0) {
1141            return new byte[0];
1142        } else if (len < 4) {
1143            throw new IllegalArgumentException(
1144                                               "Base64-encoded string must have at least four characters, but length specified was "
1145                                               + len);
1146        } // end if
1147
1148        byte[] DECODABET = getDecodabet(options);
1149
1150        int len34 = len * 3 / 4; // Estimate on array size
1151        byte[] outBuff = new byte[len34]; // Upper limit on size of output
1152        int outBuffPosn = 0; // Keep track of where we're writing
1153
1154        byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space
1155        int b4Posn = 0; // Keep track of four byte input buffer
1156        int i = 0; // Source array counter
1157        byte sbiDecode = 0; // Special value from DECODABET
1158
1159        for (i = off; i < off + len; i++) { // Loop through source
1160
1161            sbiDecode = DECODABET[source[i] & 0xFF];
1162
1163            // White space, Equals sign, or legit Base64 character
1164            // Note the values such as -5 and -9 in the
1165            // DECODABETs at the top of the file.
1166            if (sbiDecode >= WHITE_SPACE_ENC) {
1167                if (sbiDecode >= EQUALS_SIGN_ENC) {
1168                    b4[b4Posn++] = source[i]; // Save non-whitespace
1169                    if (b4Posn > 3) { // Time to decode?
1170                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options);
1171                        b4Posn = 0;
1172
1173                        // If that was the equals sign, break out of 'for' loop
1174                        if (source[i] == EQUALS_SIGN) {
1175                            break;
1176                        } // end if: equals sign
1177                    } // end if: quartet built
1178                } // end if: equals sign or better
1179            } // end if: white space, equals sign or better
1180            else {
1181                // There's a bad input character in the Base64 stream.
1182                throw new IOException(String.format("Bad Base64 input character decimal %d in array position %d",
1183                                                    source[i] & 0xFF,
1184                                                    i));
1185            } // end else:
1186        } // each input character
1187
1188        byte[] out = new byte[outBuffPosn];
1189        System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
1190        return out;
1191    } // end decode
1192
1193    /**
1194     * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
1195     * 
1196     * @param s the string to decode
1197     * @return the decoded data
1198     * @throws java.io.IOException If there is a problem
1199     * @since 1.4
1200     */
1201    public static byte[] decode( String s ) throws IOException {
1202        return decode(s, NO_OPTIONS);
1203    }
1204
1205    /**
1206     * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
1207     * 
1208     * @param s the string to decode
1209     * @param options encode options such as URL_SAFE
1210     * @return the decoded data
1211     * @throws java.io.IOException if there is an error
1212     * @throws NullPointerException if <tt>s</tt> is null
1213     * @since 1.4
1214     */
1215    public static byte[] decode( String s,
1216                                 int options ) throws IOException {
1217
1218        if (s == null) {
1219            throw new NullPointerException("Input string was null.");
1220        } // end if
1221
1222        byte[] bytes;
1223        try {
1224            bytes = s.getBytes(PREFERRED_ENCODING);
1225        } // end try
1226        catch (java.io.UnsupportedEncodingException uee) {
1227            bytes = s.getBytes();
1228        } // end catch
1229        // </change>
1230
1231        // Decode
1232        bytes = decode(bytes, 0, bytes.length, options);
1233
1234        // Check to see if it's gzip-compressed
1235        // GZIP Magic Two-Byte Number: 0x8b1f (35615)
1236        boolean dontGunzip = (options & DONT_GUNZIP) != 0;
1237        if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) {
1238
1239            int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
1240            if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
1241                java.io.ByteArrayInputStream bais = null;
1242                java.util.zip.GZIPInputStream gzis = null;
1243                java.io.ByteArrayOutputStream baos = null;
1244                byte[] buffer = new byte[2048];
1245                int length = 0;
1246
1247                try {
1248                    baos = new java.io.ByteArrayOutputStream();
1249                    bais = new java.io.ByteArrayInputStream(bytes);
1250                    gzis = new java.util.zip.GZIPInputStream(bais);
1251
1252                    while ((length = gzis.read(buffer)) >= 0) {
1253                        baos.write(buffer, 0, length);
1254                    } // end while: reading input
1255
1256                    // No error? Get new bytes.
1257                    bytes = baos.toByteArray();
1258
1259                } // end try
1260                catch (IOException e) {
1261                    e.printStackTrace();
1262                    // Just return originally-decoded bytes
1263                } // end catch
1264                finally {
1265                    try {
1266                        if (baos != null) baos.close();
1267                    } catch (Exception e) {
1268                    }
1269                    try {
1270                        if (gzis != null) gzis.close();
1271                    } catch (Exception e) {
1272                    }
1273                    try {
1274                        if (bais != null) bais.close();
1275                    } catch (Exception e) {
1276                    }
1277                } // end finally
1278
1279            } // end if: gzipped
1280        } // end if: bytes.length >= 2
1281
1282        return bytes;
1283    } // end decode
1284
1285    /**
1286     * Attempts to decode Base64 data and deserialize a Java Object within. Returns <tt>null</tt> if there was an error.
1287     * 
1288     * @param encodedObject The Base64 data to decode
1289     * @return The decoded and deserialized object
1290     * @throws NullPointerException if encodedObject is null
1291     * @throws java.io.IOException if there is a general error
1292     * @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
1293     * @since 1.5
1294     */
1295    public static Object decodeToObject( String encodedObject ) throws IOException, ClassNotFoundException {
1296        return decodeToObject(encodedObject, NO_OPTIONS, null);
1297    }
1298
1299    /**
1300     * Attempts to decode Base64 data and deserialize a Java Object within. Returns <tt>null</tt> if there was an error. If
1301     * <tt>loader</tt> is not null, it will be the class loader used when deserializing.
1302     * 
1303     * @param encodedObject The Base64 data to decode
1304     * @param options Various parameters related to decoding
1305     * @param loader Optional class loader to use in deserializing classes.
1306     * @return The decoded and deserialized object
1307     * @throws NullPointerException if encodedObject is null
1308     * @throws java.io.IOException if there is a general error
1309     * @throws ClassNotFoundException if the decoded object is of a class that cannot be found by the JVM
1310     * @since 2.3.4
1311     */
1312    public static Object decodeToObject( String encodedObject,
1313                                         int options,
1314                                         final ClassLoader loader ) throws IOException, ClassNotFoundException {
1315
1316        // Decode and gunzip if necessary
1317        byte[] objBytes = decode(encodedObject, options);
1318
1319        java.io.ByteArrayInputStream bais = null;
1320        java.io.ObjectInputStream ois = null;
1321        Object obj = null;
1322
1323        try {
1324            bais = new java.io.ByteArrayInputStream(objBytes);
1325
1326            // If no custom class loader is provided, use Java's builtin OIS.
1327            if (loader == null) {
1328                ois = new java.io.ObjectInputStream(bais);
1329            } // end if: no loader provided
1330
1331            // Else make a customized object input stream that uses
1332            // the provided class loader.
1333            else {
1334                ois = new java.io.ObjectInputStream(bais) {
1335                    @Override
1336                    public Class<?> resolveClass( java.io.ObjectStreamClass streamClass )
1337                        throws IOException, ClassNotFoundException {
1338                        Class<?> c = Class.forName(streamClass.getName(), false, loader);
1339                        if (c == null) {
1340                            return super.resolveClass(streamClass);
1341                        }
1342                        return c; // Class loader knows of this class.
1343                    } // end resolveClass
1344                }; // end ois
1345            } // end else: no custom class loader
1346
1347            obj = ois.readObject();
1348        } // end try
1349        catch (IOException e) {
1350            throw e; // Catch and throw in order to execute finally{}
1351        } // end catch
1352        catch (ClassNotFoundException e) {
1353            throw e; // Catch and throw in order to execute finally{}
1354        } // end catch
1355        finally {
1356            try {
1357                if (bais != null) bais.close();
1358            } catch (Exception e) {
1359            }
1360            try {
1361                if (ois != null) ois.close();
1362            } catch (Exception e) {
1363            }
1364        } // end finally
1365
1366        return obj;
1367    } // end decodeObject
1368
1369    /**
1370     * Convenience method for encoding data to a file.
1371     * <p>
1372     * As of v 2.3, if there is a error, the method will throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
1373     * versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
1374     * </p>
1375     * 
1376     * @param dataToEncode byte array of data to encode in base64 form
1377     * @param filename Filename for saving encoded data
1378     * @throws java.io.IOException if there is an error
1379     * @throws NullPointerException if dataToEncode is null
1380     * @since 2.1
1381     */
1382    public static void encodeToFile( byte[] dataToEncode,
1383                                     String filename ) throws IOException {
1384
1385        if (dataToEncode == null) {
1386            throw new NullPointerException("Data to encode was null.");
1387        } // end iff
1388
1389        Base64.OutputStream bos = null;
1390        try {
1391            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
1392            bos.write(dataToEncode);
1393        } // end try
1394        catch (IOException e) {
1395            throw e; // Catch and throw to execute finally{} block
1396        } // end catch: java.io.IOException
1397        finally {
1398            try {
1399                if (bos != null) bos.close();
1400            } catch (Exception e) {
1401            }
1402        } // end finally
1403
1404    } // end encodeToFile
1405
1406    /**
1407     * Convenience method for decoding data to a file.
1408     * <p>
1409     * As of v 2.3, if there is a error, the method will throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
1410     * versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
1411     * </p>
1412     * 
1413     * @param dataToDecode Base64-encoded data as a string
1414     * @param filename Filename for saving decoded data
1415     * @throws java.io.IOException if there is an error
1416     * @since 2.1
1417     */
1418    public static void decodeToFile( String dataToDecode,
1419                                     String filename ) throws IOException {
1420
1421        Base64.OutputStream bos = null;
1422        try {
1423            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
1424            bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
1425        } // end try
1426        catch (IOException e) {
1427            throw e; // Catch and throw to execute finally{} block
1428        } // end catch: java.io.IOException
1429        finally {
1430            try {
1431                if (bos != null) bos.close();
1432            } catch (Exception e) {
1433            }
1434        } // end finally
1435
1436    } // end decodeToFile
1437
1438    /**
1439     * Convenience method for reading a base64-encoded file and decoding it.
1440     * <p>
1441     * As of v 2.3, if there is a error, the method will throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
1442     * versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
1443     * </p>
1444     * 
1445     * @param filename Filename for reading encoded data
1446     * @return decoded byte array
1447     * @throws java.io.IOException if there is an error
1448     * @since 2.1
1449     */
1450    public static byte[] decodeFromFile( String filename ) throws IOException {
1451
1452        byte[] decodedData = null;
1453        Base64.InputStream bis = null;
1454        try {
1455            // Set up some useful variables
1456            java.io.File file = new java.io.File(filename);
1457            byte[] buffer = null;
1458            int length = 0;
1459            int numBytes = 0;
1460
1461            // Check for size of file
1462            if (file.length() > Integer.MAX_VALUE) {
1463                throw new IOException("File is too big for this convenience method (" + file.length() + " bytes).");
1464            } // end if: file too big for int index
1465            buffer = new byte[(int)file.length()];
1466
1467            // Open a stream
1468            bis = new Base64.InputStream(new BufferedInputStream(new java.io.FileInputStream(file)), Base64.DECODE);
1469
1470            // Read until done
1471            while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
1472                length += numBytes;
1473            } // end while
1474
1475            // Save in a variable to return
1476            decodedData = new byte[length];
1477            System.arraycopy(buffer, 0, decodedData, 0, length);
1478
1479        } // end try
1480        catch (IOException e) {
1481            throw e; // Catch and release to execute finally{}
1482        } // end catch: java.io.IOException
1483        finally {
1484            try {
1485                if (bis != null) bis.close();
1486            } catch (Exception e) {
1487            }
1488        } // end finally
1489
1490        return decodedData;
1491    } // end decodeFromFile
1492
1493    /**
1494     * Convenience method for reading a binary file and base64-encoding it.
1495     * <p>
1496     * As of v 2.3, if there is a error, the method will throw an java.io.IOException. <b>This is new to v2.3!</b> In earlier
1497     * versions, it just returned false, but in retrospect that's a pretty poor way to handle it.
1498     * </p>
1499     * 
1500     * @param filename Filename for reading binary data
1501     * @return base64-encoded string
1502     * @throws java.io.IOException if there is an error
1503     * @since 2.1
1504     */
1505    public static String encodeFromFile( String filename ) throws IOException {
1506
1507        String encodedData = null;
1508        Base64.InputStream bis = null;
1509        try {
1510            // Set up some useful variables
1511            java.io.File file = new java.io.File(filename);
1512            byte[] buffer = new byte[Math.max((int)(file.length() * 1.4 + 1), 40)]; // Need max() for math on small files
1513            // (v2.2.1); Need +1 for a few corner cases
1514            // (v2.3.5)
1515            int length = 0;
1516            int numBytes = 0;
1517
1518            // Open a stream
1519            bis = new Base64.InputStream(new BufferedInputStream(new java.io.FileInputStream(file)), Base64.ENCODE);
1520
1521            // Read until done
1522            while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
1523                length += numBytes;
1524            } // end while
1525
1526            // Save in a variable to return
1527            encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
1528
1529        } // end try
1530        catch (IOException e) {
1531            throw e; // Catch and release to execute finally{}
1532        } // end catch: java.io.IOException
1533        finally {
1534            try {
1535                if (bis != null) bis.close();
1536            } catch (Exception e) {
1537            }
1538        } // end finally
1539
1540        return encodedData;
1541    } // end encodeFromFile
1542
1543    /**
1544     * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
1545     * 
1546     * @param infile Input file
1547     * @param outfile Output file
1548     * @throws java.io.IOException if there is an error
1549     * @since 2.2
1550     */
1551    public static void encodeFileToFile( String infile,
1552                                         String outfile ) throws IOException {
1553
1554        String encoded = Base64.encodeFromFile(infile);
1555        java.io.OutputStream out = null;
1556        try {
1557            out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
1558            out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output.
1559        } // end try
1560        catch (IOException e) {
1561            throw e; // Catch and release to execute finally{}
1562        } // end catch
1563        finally {
1564            try {
1565                if (out != null) out.close();
1566            } catch (Exception ex) {
1567            }
1568        } // end finally
1569    } // end encodeFileToFile
1570
1571    /**
1572     * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
1573     * 
1574     * @param infile Input file
1575     * @param outfile Output file
1576     * @throws java.io.IOException if there is an error
1577     * @since 2.2
1578     */
1579    public static void decodeFileToFile( String infile,
1580                                         String outfile ) throws IOException {
1581
1582        byte[] decoded = Base64.decodeFromFile(infile);
1583        java.io.OutputStream out = null;
1584        try {
1585            out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
1586            out.write(decoded);
1587        } // end try
1588        catch (IOException e) {
1589            throw e; // Catch and release to execute finally{}
1590        } // end catch
1591        finally {
1592            try {
1593                if (out != null) out.close();
1594            } catch (Exception ex) {
1595            }
1596        } // end finally
1597    } // end decodeFileToFile
1598
1599    /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
1600
1601    /**
1602     * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor, and
1603     * encode/decode to/from Base64 notation on the fly.
1604     * 
1605     * @see Base64
1606     * @since 1.3
1607     */
1608    public static class InputStream extends java.io.FilterInputStream {
1609
1610        private boolean encode; // Encoding or decoding
1611        private int position; // Current position in the buffer
1612        private byte[] buffer; // Small buffer holding converted data
1613        private int bufferLength; // Length of buffer (3 or 4)
1614        private int numSigBytes; // Number of meaningful bytes in the buffer
1615        private int lineLength;
1616        private boolean breakLines; // Break lines at less than 80 characters
1617        private int options; // Record options used to create the stream.
1618        private byte[] decodabet; // Local copies to avoid extra method calls
1619
1620        /**
1621         * Constructs a {@link Base64.InputStream} in DECODE mode.
1622         * 
1623         * @param in the <tt>java.io.InputStream</tt> from which to read data.
1624         * @since 1.3
1625         */
1626        public InputStream( java.io.InputStream in ) {
1627            this(in, DECODE);
1628        } // end constructor
1629
1630        /**
1631         * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode.
1632         * <p>
1633         * Valid options:
1634         * 
1635         * <pre>
1636         *   ENCODE or DECODE: Encode or Decode as data is read.
1637         *   DO_BREAK_LINES: break lines at 76 characters
1638         *     (only meaningful when encoding)&lt;/i&gt;
1639         * </pre>
1640         * <p>
1641         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
1642         * 
1643         * @param in the <tt>java.io.InputStream</tt> from which to read data.
1644         * @param options Specified options
1645         * @see Base64#ENCODE
1646         * @see Base64#DECODE
1647         * @see Base64#DO_BREAK_LINES
1648         * @since 2.0
1649         */
1650        @SuppressWarnings( "synthetic-access" )
1651        public InputStream( java.io.InputStream in,
1652                            int options ) {
1653
1654            super(in);
1655            this.options = options; // Record for later
1656            this.breakLines = (options & DO_BREAK_LINES) > 0;
1657            this.encode = (options & ENCODE) > 0;
1658            this.bufferLength = encode ? 4 : 3;
1659            this.buffer = new byte[bufferLength];
1660            this.position = -1;
1661            this.lineLength = 0;
1662            this.decodabet = getDecodabet(options);
1663        } // end constructor
1664
1665        /**
1666         * Reads enough of the input stream to convert to/from Base64 and returns the next byte.
1667         * 
1668         * @return next byte
1669         * @since 1.3
1670         */
1671        @SuppressWarnings( "synthetic-access" )
1672        @Override
1673        public int read() throws IOException {
1674
1675            // Do we need to get data?
1676            if (position < 0) {
1677                if (encode) {
1678                    byte[] b3 = new byte[3];
1679                    int numBinaryBytes = 0;
1680                    for (int i = 0; i < 3; i++) {
1681                        int b = in.read();
1682
1683                        // If end of stream, b is -1.
1684                        if (b >= 0) {
1685                            b3[i] = (byte)b;
1686                            numBinaryBytes++;
1687                        } else {
1688                            break; // out of for loop
1689                        } // end else: end of stream
1690
1691                    } // end for: each needed input byte
1692
1693                    if (numBinaryBytes > 0) {
1694                        encode3to4(b3, 0, numBinaryBytes, buffer, 0, options);
1695                        position = 0;
1696                        numSigBytes = 4;
1697                    } // end if: got data
1698                    else {
1699                        return -1; // Must be end of stream
1700                    } // end else
1701                } // end if: encoding
1702
1703                // Else decoding
1704                else {
1705                    byte[] b4 = new byte[4];
1706                    int i = 0;
1707                    for (i = 0; i < 4; i++) {
1708                        // Read four "meaningful" bytes:
1709                        int b = 0;
1710                        do {
1711                            b = in.read();
1712                        } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC);
1713
1714                        if (b < 0) {
1715                            break; // Reads a -1 if end of stream
1716                        } // end if: end of stream
1717
1718                        b4[i] = (byte)b;
1719                    } // end for: each needed input byte
1720
1721                    if (i == 4) {
1722                        numSigBytes = decode4to3(b4, 0, buffer, 0, options);
1723                        position = 0;
1724                    } // end if: got four characters
1725                    else if (i == 0) {
1726                        return -1;
1727                    } // end else if: also padded correctly
1728                    else {
1729                        // Must have broken out from above.
1730                        throw new IOException("Improperly padded Base64 input.");
1731                    } // end
1732
1733                } // end else: decode
1734            } // end else: get data
1735
1736            // Got data?
1737            if (position >= 0) {
1738                // End of relevant data?
1739                if ( /*!encode &&*/position >= numSigBytes) {
1740                    return -1;
1741                } // end if: got data
1742
1743                if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
1744                    lineLength = 0;
1745                    return '\n';
1746                } // end if
1747                lineLength++; // This isn't important when decoding
1748                // but throwing an extra "if" seems
1749                // just as wasteful.
1750
1751                int b = buffer[position++];
1752
1753                if (position >= bufferLength) {
1754                    position = -1;
1755                } // end if: end
1756
1757                return b & 0xFF; // This is how you "cast" a byte that's
1758                // intended to be unsigned.
1759            } // end if: position >= 0
1760
1761            // Else error
1762            throw new IOException("Error in Base64 code reading stream.");
1763        } // end read
1764
1765        /**
1766         * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns number of
1767         * bytes read into array or -1 if end of stream is encountered.
1768         * 
1769         * @param dest array to hold values
1770         * @param off offset for array
1771         * @param len max number of bytes to read into array
1772         * @return bytes read into array or -1 if end of stream is encountered.
1773         * @since 1.3
1774         */
1775        @Override
1776        public int read( byte[] dest,
1777                         int off,
1778                         int len ) throws IOException {
1779            int i;
1780            int b;
1781            for (i = 0; i < len; i++) {
1782                b = read();
1783
1784                if (b >= 0) {
1785                    dest[off + i] = (byte)b;
1786                } else if (i == 0) {
1787                    return -1;
1788                } else {
1789                    break; // Out of 'for' loop
1790                } // Out of 'for' loop
1791            } // end for: each byte read
1792            return i;
1793        } // end read
1794
1795    } // end inner class InputStream
1796
1797    /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
1798
1799    /**
1800     * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor, and
1801     * encode/decode to/from Base64 notation on the fly.
1802     * 
1803     * @see Base64
1804     * @since 1.3
1805     */
1806    public static class OutputStream extends java.io.FilterOutputStream {
1807
1808        private boolean encode;
1809        private int position;
1810        private byte[] buffer;
1811        private int bufferLength;
1812        private int lineLength;
1813        private boolean breakLines;
1814        private byte[] b4; // Scratch used in a few places
1815        private boolean suspendEncoding;
1816        private int options; // Record for later
1817        private byte[] decodabet; // Local copies to avoid extra method calls
1818
1819        /**
1820         * Constructs a {@link Base64.OutputStream} in ENCODE mode.
1821         * 
1822         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1823         * @since 1.3
1824         */
1825        public OutputStream( java.io.OutputStream out ) {
1826            this(out, ENCODE);
1827        } // end constructor
1828
1829        /**
1830         * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode.
1831         * <p>
1832         * Valid options:
1833         * 
1834         * <pre>
1835         *   ENCODE or DECODE: Encode or Decode as data is read.
1836         *   DO_BREAK_LINES: don't break lines at 76 characters
1837         *     (only meaningful when encoding)&lt;/i&gt;
1838         * </pre>
1839         * <p>
1840         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1841         * 
1842         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1843         * @param options Specified options.
1844         * @see Base64#ENCODE
1845         * @see Base64#DECODE
1846         * @see Base64#DO_BREAK_LINES
1847         * @since 1.3
1848         */
1849        @SuppressWarnings( "synthetic-access" )
1850        public OutputStream( java.io.OutputStream out,
1851                             int options ) {
1852            super(out);
1853            this.breakLines = (options & DO_BREAK_LINES) != 0;
1854            this.encode = (options & ENCODE) != 0;
1855            this.bufferLength = encode ? 3 : 4;
1856            this.buffer = new byte[bufferLength];
1857            this.position = 0;
1858            this.lineLength = 0;
1859            this.suspendEncoding = false;
1860            this.b4 = new byte[4];
1861            this.options = options;
1862            this.decodabet = getDecodabet(options);
1863        } // end constructor
1864
1865        /**
1866         * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are buffered three
1867         * at a time before the output stream actually gets a write() call. When decoding, bytes are buffered four at a time.
1868         * 
1869         * @param theByte the byte to write
1870         * @since 1.3
1871         */
1872        @SuppressWarnings( "synthetic-access" )
1873        @Override
1874        public void write( int theByte ) throws IOException {
1875            // Encoding suspended?
1876            if (suspendEncoding) {
1877                this.out.write(theByte);
1878                return;
1879            } // end if: supsended
1880
1881            // Encode?
1882            if (encode) {
1883                buffer[position++] = (byte)theByte;
1884                if (position >= bufferLength) { // Enough to encode.
1885
1886                    this.out.write(encode3to4(b4, buffer, bufferLength, options));
1887
1888                    lineLength += 4;
1889                    if (breakLines && lineLength >= MAX_LINE_LENGTH) {
1890                        this.out.write(NEW_LINE);
1891                        lineLength = 0;
1892                    } // end if: end of line
1893
1894                    position = 0;
1895                } // end if: enough to output
1896            } // end if: encoding
1897
1898            // Else, Decoding
1899            else {
1900                // Meaningful Base64 character?
1901                if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) {
1902                    buffer[position++] = (byte)theByte;
1903                    if (position >= bufferLength) { // Enough to output.
1904
1905                        int len = Base64.decode4to3(buffer, 0, b4, 0, options);
1906                        out.write(b4, 0, len);
1907                        position = 0;
1908                    } // end if: enough to output
1909                } // end if: meaningful base64 character
1910                else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) {
1911                    throw new IOException("Invalid character in Base64 data.");
1912                } // end else: not white space either
1913            } // end else: decoding
1914        } // end write
1915
1916        /**
1917         * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written.
1918         * 
1919         * @param theBytes array from which to read bytes
1920         * @param off offset for array
1921         * @param len max number of bytes to read into array
1922         * @since 1.3
1923         */
1924        @Override
1925        public void write( byte[] theBytes,
1926                           int off,
1927                           int len ) throws IOException {
1928            // Encoding suspended?
1929            if (suspendEncoding) {
1930                this.out.write(theBytes, off, len);
1931                return;
1932            } // end if: supsended
1933
1934            for (int i = 0; i < len; i++) {
1935                write(theBytes[off + i]);
1936            } // end for: each byte written
1937
1938        } // end write
1939
1940        /**
1941         * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
1942         * 
1943         * @throws java.io.IOException if there's an error.
1944         */
1945        @SuppressWarnings( "synthetic-access" )
1946        public void flushBase64() throws IOException {
1947            if (position > 0) {
1948                if (encode) {
1949                    out.write(encode3to4(b4, buffer, position, options));
1950                    position = 0;
1951                } // end if: encoding
1952                else {
1953                    throw new IOException("Base64 input not properly padded.");
1954                } // end else: decoding
1955            } // end if: buffer partially full
1956
1957        } // end flush
1958
1959        /**
1960         * Flushes and closes (I think, in the superclass) the stream.
1961         * 
1962         * @since 1.3
1963         */
1964        @Override
1965        public void close() throws IOException {
1966            // 1. Ensure that pending characters are written
1967            flushBase64();
1968
1969            // 2. Actually close the stream
1970            // Base class both flushes and closes.
1971            super.close();
1972
1973            buffer = null;
1974            out = null;
1975        } // end close
1976
1977        /**
1978         * Suspends encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream.
1979         * 
1980         * @throws java.io.IOException if there's an error flushing
1981         * @since 1.5.1
1982         */
1983        public void suspendEncoding() throws IOException {
1984            flushBase64();
1985            this.suspendEncoding = true;
1986        } // end suspendEncoding
1987
1988        /**
1989         * Resumes encoding of the stream. May be helpful if you need to embed a piece of base64-encoded data in a stream.
1990         * 
1991         * @since 1.5.1
1992         */
1993        public void resumeEncoding() {
1994            this.suspendEncoding = false;
1995        } // end resumeEncoding
1996
1997    } // end inner class OutputStream
1998
1999} // end class Base64