001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
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 *
016 * Contributors:
017 */
018package org.nuxeo.ecm.automation.client.jaxrs.util;
019
020import java.io.UnsupportedEncodingException;
021import java.nio.charset.Charset;
022
023import org.apache.commons.io.IOUtils;
024
025/**
026 * Encodes and decodes to and from Base64 notation.
027 * <p>
028 * Change Log:
029 * </p>
030 * <ul>
031 * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added some convenience methods for reading
032 * and writing to and from files.</li>
033 * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems with other encodings (like
034 * EBCDIC).</li>
035 * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the encoded data was a single byte.</li>
036 * <li>v2.0 - I got rid of methods that used booleans to set options. Now everything is more consolidated and cleaner.
037 * The code now detects when data that's being decoded is gzip-compressed and will decompress it automatically.
038 * Generally things are cleaner. You'll probably have to change some method calls that you were making to support the
039 * new options format (<tt>int</tt>s that you "OR" together).</li>
040 * <li>v1.5.1 - Fixed bug when decompressing and decoding to a byte[] using
041 * <tt>decode( String s, boolean gzipCompressed )</tt>. Added the ability to "suspend" encoding in the Output Stream so
042 * you can turn on and off the encoding if you need to embed base64 data in an otherwise "normal" stream (like an XML
043 * file).</li>
044 * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. This helps when using GZIP streams.
045 * Added the ability to GZip-compress objects before encoding them.</li>
046 * <li>v1.4 - Added helper methods to read/write files.</li>
047 * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
048 * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream where last buffer being read, if
049 * not completely full, was not returned.</li>
050 * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
051 * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
052 * </ul>
053 * <p>
054 * I am placing this code in the Public Domain. Do with it as you will. This software comes with no guarantees or
055 * warranties but with plenty of well-wishing instead! Please visit <a
056 * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically to check for updates or to contribute
057 * improvements.
058 * </p>
059 *
060 * @author Robert Harder
061 * @author rob@iharder.net
062 * @version 2.1
063 */
064public class Base64 {
065
066    /* ******** P U B L I C F I E L D S ******** */
067
068    /** No options specified. Value is zero. */
069    public final static int NO_OPTIONS = 0;
070
071    /** Specify encoding. */
072    public final static int ENCODE = 1;
073
074    /** Specify decoding. */
075    public final static int DECODE = 0;
076
077    /** Specify that data should be gzip-compressed. */
078    public final static int GZIP = 2;
079
080    /** Don't break lines when encoding (violates strict Base64 specification) */
081    public final static int DONT_BREAK_LINES = 8;
082
083    /* ******** P R I V A T E F I E L D S ******** */
084
085    /** Maximum line length (76) of Base64 output. */
086    private final static int MAX_LINE_LENGTH = 76;
087
088    /** The equals sign (=) as a byte. */
089    private final static byte EQUALS_SIGN = (byte) '=';
090
091    /** The new line character (\n) as a byte. */
092    private final static byte NEW_LINE = (byte) '\n';
093
094    /** Preferred encoding. */
095    private final static String PREFERRED_ENCODING = "UTF-8";
096
097    /** The 64 valid Base64 values. */
098    private final static byte[] ALPHABET;
099
100    private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
101    { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I',
102            (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R',
103            (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a',
104            (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
105            (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's',
106            (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1',
107            (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+',
108            (byte) '/' };
109
110    /** Determine which ALPHABET to use. */
111    static {
112        byte[] __bytes;
113        try {
114            __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(PREFERRED_ENCODING);
115        } // end try
116        catch (java.io.UnsupportedEncodingException use) {
117            __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
118        } // end catch
119        ALPHABET = __bytes;
120    } // end static
121
122    /**
123     * Translates a Base64 value to either its 6-bit reconstruction value or a negative number indicating some other
124     * meaning.
125     **/
126    private final static byte[] DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
127            -5, -5, // Whitespace: Tab and Linefeed
128            -9, -9, // Decimal 11 - 12
129            -5, // Whitespace: Carriage Return
130            -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
131            -9, -9, -9, -9, -9, // Decimal 27 - 31
132            -5, // Whitespace: Space
133            -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
134            62, // Plus sign at decimal 43
135            -9, -9, -9, // Decimal 44 - 46
136            63, // Slash at decimal 47
137            52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
138            -9, -9, -9, // Decimal 58 - 60
139            -1, // Equals sign at decimal 61
140            -9, -9, -9, // Decimal 62 - 64
141            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
142            14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
143            -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
144            26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
145            39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
146            -9, -9, -9, -9 // Decimal 123 - 126
147    /*
148     * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal
149     * 140 - 152 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, //
150     * Decimal 166 - 178 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
151     * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal
152     * 205 - 217 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, //
153     * Decimal 231 - 243 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
154     */
155    };
156
157    // I think I end up not using the BAD_ENCODING indicator.
158    // private final static byte BAD_ENCODING = -9; // Indicates error in encoding
159    private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
160
161    private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
162
163    /** Defeats instantiation. */
164    private Base64() {
165    }
166
167    /* ******** E N C O D I N G M E T H O D S ******** */
168
169    /**
170     * Encodes up to the first three bytes of array <var>threeBytes</var> and returns a four-byte array in Base64
171     * notation. The actual number of significant bytes in your array is given by <var>numSigBytes</var>. The array
172     * <var>threeBytes</var> needs only be as big as <var>numSigBytes</var>. Code can reuse a byte array by passing a
173     * four-byte array as <var>b4</var>.
174     *
175     * @param b4 A reusable byte array to reduce array instantiation
176     * @param threeBytes the array to convert
177     * @param numSigBytes the number of significant bytes in your array
178     * @return four byte array in Base64 notation.
179     * @since 1.5.1
180     */
181    private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes) {
182        encode3to4(threeBytes, 0, numSigBytes, b4, 0);
183        return b4;
184    } // end encode3to4
185
186    /**
187     * Encodes up to three bytes of the array <var>source</var> and writes the resulting four Base64 bytes to
188     * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by
189     * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays
190     * are large enough to accomodate <var>srcOffset</var> + 3 for the <var>source</var> array or <var>destOffset</var>
191     * + 4 for the <var>destination</var> array. The actual number of significant bytes in your array is given by
192     * <var>numSigBytes</var>.
193     *
194     * @param source the array to convert
195     * @param srcOffset the index where conversion begins
196     * @param numSigBytes the number of significant bytes in your array
197     * @param destination the array to hold the conversion
198     * @param destOffset the index where output will be put
199     * @return the <var>destination</var> array
200     * @since 1.3
201     */
202    private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset) {
203        // 1 2 3
204        // 01234567890123456789012345678901 Bit position
205        // --------000000001111111122222222 Array position from threeBytes
206        // --------| || || || | Six bit groups to index ALPHABET
207        // >>18 >>12 >> 6 >> 0 Right shift necessary
208        // 0x3f 0x3f 0x3f Additional AND
209
210        // Create buffer with zero-padding if there are only one or two
211        // significant bytes passed in the array.
212        // We have to shift left 24 in order to flush out the 1's that appear
213        // when Java treats a value as negative that is cast from a byte to an int.
214        int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
215                | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
216                | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
217
218        switch (numSigBytes) {
219        case 3:
220            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
221            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
222            destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
223            destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
224            return destination;
225
226        case 2:
227            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
228            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
229            destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
230            destination[destOffset + 3] = EQUALS_SIGN;
231            return destination;
232
233        case 1:
234            destination[destOffset] = ALPHABET[(inBuff >>> 18)];
235            destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
236            destination[destOffset + 2] = EQUALS_SIGN;
237            destination[destOffset + 3] = EQUALS_SIGN;
238            return destination;
239
240        default:
241            return destination;
242        } // end switch
243    } // end encode3to4
244
245    public static String encode(byte[] bytes) {
246        return encodeBytes(bytes);
247    }
248
249    public static String encode(String content) {
250        return encodeBytes(content.getBytes());
251    }
252
253    public static String encode(String content, String charset) throws UnsupportedEncodingException {
254        return encodeBytes(content.getBytes(charset));
255    }
256
257    public static String encode(String content, Charset charset) {
258        return encodeBytes(content.getBytes(charset));
259    }
260
261    /**
262     * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be
263     * serialized or there is another error, the method will return <tt>null</tt>. The object is not GZip-compressed
264     * before being encoded.
265     *
266     * @param serializableObject The object to encode
267     * @return The Base64-encoded object
268     * @since 1.4
269     */
270    public static String encodeObject(java.io.Serializable serializableObject) {
271        return encodeObject(serializableObject, NO_OPTIONS);
272    } // end encodeObject
273
274    /**
275     * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be
276     * serialized or there is another error, the method will return <tt>null</tt>.
277     * <p>
278     * Valid options:
279     *
280     * <pre>
281     *   GZIP: gzip-compresses object before encoding it.
282     *   DONT_BREAK_LINES: don't break lines at 76 characters
283     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
284     * </pre>
285     * <p>
286     * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
287     * <p>
288     * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
289     *
290     * @param serializableObject The object to encode
291     * @param options Specified options
292     * @return The Base64-encoded object
293     * @see Base64#GZIP
294     * @see Base64#DONT_BREAK_LINES
295     * @since 2.0
296     */
297    public static String encodeObject(java.io.Serializable serializableObject, int options) {
298        // Streams
299        java.io.ByteArrayOutputStream baos = null;
300        java.io.OutputStream b64os = null;
301        java.io.ObjectOutputStream oos = null;
302        java.util.zip.GZIPOutputStream gzos = null;
303
304        // Isolate options
305        int gzip = (options & GZIP);
306        int dontBreakLines = (options & DONT_BREAK_LINES);
307
308        try {
309            // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
310            baos = new java.io.ByteArrayOutputStream();
311            b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
312
313            // GZip?
314            if (gzip == GZIP) {
315                gzos = new java.util.zip.GZIPOutputStream(b64os);
316                oos = new java.io.ObjectOutputStream(gzos);
317            } // end if: gzip
318            else
319                oos = new java.io.ObjectOutputStream(b64os);
320
321            oos.writeObject(serializableObject);
322        } // end try
323        catch (java.io.IOException e) {
324            e.printStackTrace();
325            return null;
326        } // end catch
327        finally {
328            IOUtils.closeQuietly(oos);
329            IOUtils.closeQuietly(gzos);
330            IOUtils.closeQuietly(b64os);
331            IOUtils.closeQuietly(baos);
332        } // end finally
333
334        // Return value according to relevant encoding.
335        try {
336            return new String(baos.toByteArray(), PREFERRED_ENCODING);
337        } // end try
338        catch (java.io.UnsupportedEncodingException uue) {
339            return new String(baos.toByteArray());
340        } // end catch
341
342    } // end encode
343
344    /**
345     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
346     *
347     * @param source The data to convert
348     * @since 1.4
349     */
350    public static String encodeBytes(byte[] source) {
351        return encodeBytes(source, 0, source.length, NO_OPTIONS);
352    } // end encodeBytes
353
354    /**
355     * Encodes a byte array into Base64 notation.
356     * <p>
357     * Valid options:
358     *
359     * <pre>
360     *   GZIP: gzip-compresses object before encoding it.
361     *   DONT_BREAK_LINES: don't break lines at 76 characters
362     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
363     * </pre>
364     * <p>
365     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
366     * <p>
367     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
368     *
369     * @param source The data to convert
370     * @param options Specified options
371     * @see Base64#GZIP
372     * @see Base64#DONT_BREAK_LINES
373     * @since 2.0
374     */
375    public static String encodeBytes(byte[] source, int options) {
376        return encodeBytes(source, 0, source.length, options);
377    } // end encodeBytes
378
379    /**
380     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
381     *
382     * @param source The data to convert
383     * @param off Offset in array where conversion should begin
384     * @param len Length of data to convert
385     * @since 1.4
386     */
387    public static String encodeBytes(byte[] source, int off, int len) {
388        return encodeBytes(source, off, len, NO_OPTIONS);
389    } // end encodeBytes
390
391    /**
392     * Encodes a byte array into Base64 notation.
393     * <p>
394     * Valid options:
395     *
396     * <pre>
397     *   GZIP: gzip-compresses object before encoding it.
398     *   DONT_BREAK_LINES: don't break lines at 76 characters
399     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
400     * </pre>
401     * <p>
402     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
403     * <p>
404     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
405     *
406     * @param source The data to convert
407     * @param off Offset in array where conversion should begin
408     * @param len Length of data to convert
409     * @param options Specified options
410     * @see Base64#GZIP
411     * @see Base64#DONT_BREAK_LINES
412     * @since 2.0
413     */
414    public static String encodeBytes(byte[] source, int off, int len, int options) {
415        // Isolate options
416        int dontBreakLines = (options & DONT_BREAK_LINES);
417        int gzip = (options & GZIP);
418
419        // Compress?
420        if (gzip == GZIP) {
421            java.io.ByteArrayOutputStream baos = null;
422            java.util.zip.GZIPOutputStream gzos = null;
423            Base64.OutputStream b64os = null;
424
425            try {
426                // GZip -> Base64 -> ByteArray
427                baos = new java.io.ByteArrayOutputStream();
428                b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
429                gzos = new java.util.zip.GZIPOutputStream(b64os);
430
431                gzos.write(source, off, len);
432                gzos.close();
433            } // end try
434            catch (java.io.IOException e) {
435                e.printStackTrace();
436                return null;
437            } // end catch
438            finally {
439                IOUtils.closeQuietly(gzos);
440                IOUtils.closeQuietly(b64os);
441                IOUtils.closeQuietly(baos);
442            } // end finally
443
444            // Return value according to relevant encoding.
445            try {
446                return new String(baos.toByteArray(), PREFERRED_ENCODING);
447            } // end try
448            catch (java.io.UnsupportedEncodingException uue) {
449                return new String(baos.toByteArray());
450            } // end catch
451        } // end if: compress
452
453        // Else, don't compress. Better not to use streams at all then.
454        else {
455            // Convert option to boolean in way that code likes it.
456            boolean breakLines = dontBreakLines == 0;
457
458            int len43 = len * 4 / 3;
459            byte[] outBuff = new byte[(len43) // Main 4:3
460                    + ((len % 3) > 0 ? 4 : 0) // Account for padding
461                    + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
462            int d = 0;
463            int e = 0;
464            int len2 = len - 2;
465            int lineLength = 0;
466            for (; d < len2; d += 3, e += 4) {
467                encode3to4(source, d + off, 3, outBuff, e);
468
469                lineLength += 4;
470                if (breakLines && lineLength == MAX_LINE_LENGTH) {
471                    outBuff[e + 4] = NEW_LINE;
472                    e++;
473                    lineLength = 0;
474                } // end if: end of line
475            } // en dfor: each piece of array
476
477            if (d < len) {
478                encode3to4(source, d + off, len - d, outBuff, e);
479                e += 4;
480            } // end if: some padding needed
481
482            // Return value according to relevant encoding.
483            try {
484                return new String(outBuff, 0, e, PREFERRED_ENCODING);
485            } // end try
486            catch (java.io.UnsupportedEncodingException uue) {
487                return new String(outBuff, 0, e);
488            } // end catch
489
490        } // end else: don't compress
491
492    } // end encodeBytes
493
494    /* ******** D E C O D I N G M E T H O D S ******** */
495
496    /**
497     * Decodes four bytes from array <var>source</var> and writes the resulting bytes (up to three of them) to
498     * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by
499     * specifying <var>srcOffset</var> and <var>destOffset</var>. This method does not check to make sure your arrays
500     * are large enough to accomodate <var>srcOffset</var> + 4 for the <var>source</var> array or <var>destOffset</var>
501     * + 3 for the <var>destination</var> array. This method returns the actual number of bytes that were converted from
502     * the Base64 encoding.
503     *
504     * @param source the array to convert
505     * @param srcOffset the index where conversion begins
506     * @param destination the array to hold the conversion
507     * @param destOffset the index where output will be put
508     * @return the number of decoded bytes converted
509     * @since 1.3
510     */
511    private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) {
512        // Example: Dk==
513        if (source[srcOffset + 2] == EQUALS_SIGN) {
514            // Two ways to do the same thing. Don't know which way I like best.
515            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
516            // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
517            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
518                    | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
519
520            destination[destOffset] = (byte) (outBuff >>> 16);
521            return 1;
522        }
523
524        // Example: DkL=
525        else if (source[srcOffset + 3] == EQUALS_SIGN) {
526            // Two ways to do the same thing. Don't know which way I like best.
527            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
528            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
529            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
530            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
531                    | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
532                    | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
533
534            destination[destOffset] = (byte) (outBuff >>> 16);
535            destination[destOffset + 1] = (byte) (outBuff >>> 8);
536            return 2;
537        }
538
539        // Example: DkLE
540        else {
541            try {
542                // Two ways to do the same thing. Don't know which way I like best.
543                // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
544                // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
545                // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
546                // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
547                int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
548                        | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
549                        | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
550                        | ((DECODABET[source[srcOffset + 3]] & 0xFF));
551
552                destination[destOffset] = (byte) (outBuff >> 16);
553                destination[destOffset + 1] = (byte) (outBuff >> 8);
554                destination[destOffset + 2] = (byte) (outBuff);
555
556                return 3;
557            } catch (ArrayIndexOutOfBoundsException e) {
558                System.out.println("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]]));
559                System.out.println("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]]));
560                System.out.println("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]]));
561                System.out.println("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]]));
562                return -1;
563            } // e nd catch
564        }
565    } // end decodeToBytes
566
567    /**
568     * Very low-level access to decoding ASCII characters in the form of a byte array. Does not support automatically
569     * gunzipping or any other "fancy" features.
570     *
571     * @param source The Base64 encoded data
572     * @param off The offset of where to begin decoding
573     * @param len The length of characters to decode
574     * @return decoded data
575     * @since 1.3
576     */
577    public static byte[] decode(byte[] source, int off, int len) {
578        int len34 = len * 3 / 4;
579        byte[] outBuff = new byte[len34]; // Upper limit on size of output
580        int outBuffPosn = 0;
581
582        byte[] b4 = new byte[4];
583        int b4Posn = 0;
584        int i = 0;
585        byte sbiCrop = 0;
586        byte sbiDecode = 0;
587        for (i = off; i < off + len; i++) {
588            sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
589            sbiDecode = DECODABET[sbiCrop];
590
591            if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
592            {
593                if (sbiDecode >= EQUALS_SIGN_ENC) {
594                    b4[b4Posn++] = sbiCrop;
595                    if (b4Posn > 3) {
596                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
597                        b4Posn = 0;
598
599                        // If that was the equals sign, break out of 'for' loop
600                        if (sbiCrop == EQUALS_SIGN)
601                            break;
602                    } // end if: quartet built
603
604                } // end if: equals sign or better
605
606            } // end if: white space, equals sign or better
607            else {
608                System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)");
609                return null;
610            } // end else:
611        } // each input character
612
613        byte[] out = new byte[outBuffPosn];
614        System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
615        return out;
616    } // end decode
617
618    /**
619     * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
620     *
621     * @param s the string to decode
622     * @return the decoded data
623     * @since 1.4
624     */
625    public static byte[] decode(String s) {
626        byte[] bytes;
627        try {
628            bytes = s.getBytes(PREFERRED_ENCODING);
629        } // end try
630        catch (java.io.UnsupportedEncodingException uee) {
631            bytes = s.getBytes();
632        } // end catch
633        // </change>
634
635        // Decode
636        bytes = decode(bytes, 0, bytes.length);
637
638        // Check to see if it's gzip-compressed
639        // GZIP Magic Two-Byte Number: 0x8b1f (35615)
640        if (bytes != null && bytes.length >= 4) {
641
642            int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
643            if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
644                java.io.ByteArrayInputStream bais = null;
645                java.util.zip.GZIPInputStream gzis = null;
646                java.io.ByteArrayOutputStream baos = null;
647                byte[] buffer = new byte[2048];
648                int length = 0;
649
650                try {
651                    baos = new java.io.ByteArrayOutputStream();
652                    bais = new java.io.ByteArrayInputStream(bytes);
653                    gzis = new java.util.zip.GZIPInputStream(bais);
654
655                    while ((length = gzis.read(buffer)) >= 0) {
656                        baos.write(buffer, 0, length);
657                    } // end while: reading input
658
659                    // No error? Get new bytes.
660                    bytes = baos.toByteArray();
661
662                } // end try
663                catch (java.io.IOException e) {
664                    // Just return originally-decoded bytes
665                } // end catch
666                finally {
667                    IOUtils.closeQuietly(baos);
668                    IOUtils.closeQuietly(gzis);
669                    IOUtils.closeQuietly(bais);
670                } // end finally
671
672            } // end if: gzipped
673        } // end if: bytes.length >= 2
674
675        return bytes;
676    } // end decode
677
678    /**
679     * Attempts to decode Base64 data and deserialize a Java Object within. Returns <tt>null</tt> if there was an error.
680     *
681     * @param encodedObject The Base64 data to decode
682     * @return The decoded and deserialized object
683     * @since 1.5
684     */
685    public static Object decodeToObject(String encodedObject) {
686        // Decode and gunzip if necessary
687        byte[] objBytes = decode(encodedObject);
688
689        java.io.ByteArrayInputStream bais = null;
690        java.io.ObjectInputStream ois = null;
691        Object obj = null;
692
693        try {
694            bais = new java.io.ByteArrayInputStream(objBytes);
695            ois = new java.io.ObjectInputStream(bais);
696
697            obj = ois.readObject();
698        } // end try
699        catch (java.io.IOException e) {
700            e.printStackTrace();
701            obj = null;
702        } // end catch
703        catch (java.lang.ClassNotFoundException e) {
704            e.printStackTrace();
705            obj = null;
706        } // end catch
707        finally {
708            IOUtils.closeQuietly(bais);
709            IOUtils.closeQuietly(ois);
710        } // end finally
711
712        return obj;
713    } // end decodeObject
714
715    /**
716     * Convenience method for encoding data to a file.
717     *
718     * @param dataToEncode byte array of data to encode in base64 form
719     * @param filename Filename for saving encoded data
720     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
721     * @since 2.1
722     */
723    public static boolean encodeToFile(byte[] dataToEncode, String filename) {
724        boolean success = false;
725        Base64.OutputStream bos = null;
726        try {
727            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
728            bos.write(dataToEncode);
729            success = true;
730        } // end try
731        catch (java.io.IOException e) {
732
733            success = false;
734        } // end catch: IOException
735        finally {
736            IOUtils.closeQuietly(bos);
737        } // end finally
738
739        return success;
740    } // end encodeToFile
741
742    /**
743     * Convenience method for decoding data to a file.
744     *
745     * @param dataToDecode Base64-encoded data as a string
746     * @param filename Filename for saving decoded data
747     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
748     * @since 2.1
749     */
750    public static boolean decodeToFile(String dataToDecode, String filename) {
751        boolean success = false;
752        Base64.OutputStream bos = null;
753        try {
754            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
755            bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
756            success = true;
757        } // end try
758        catch (java.io.IOException e) {
759            success = false;
760        } // end catch: IOException
761        finally {
762            IOUtils.closeQuietly(bos);
763        } // end finally
764
765        return success;
766    } // end decodeToFile
767
768    /**
769     * Convenience method for reading a base64-encoded file and decoding it.
770     *
771     * @param filename Filename for reading encoded data
772     * @return decoded byte array or null if unsuccessful
773     * @since 2.1
774     */
775    public static byte[] decodeFromFile(String filename) {
776        byte[] decodedData = null;
777        Base64.InputStream bis = null;
778        try {
779            // Set up some useful variables
780            java.io.File file = new java.io.File(filename);
781            byte[] buffer = null;
782            int length = 0;
783            int numBytes = 0;
784
785            // Check for size of file
786            if (file.length() > Integer.MAX_VALUE) {
787                System.err.println("File is too big for this convenience method (" + file.length() + " bytes).");
788                return null;
789            } // end if: file too big for int index
790            buffer = new byte[(int) file.length()];
791
792            // Open a stream
793            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
794                    Base64.DECODE);
795
796            // Read until done
797            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
798                length += numBytes;
799
800            // Save in a variable to return
801            decodedData = new byte[length];
802            System.arraycopy(buffer, 0, decodedData, 0, length);
803
804        } // end try
805        catch (java.io.IOException e) {
806            System.err.println("Error decoding from file " + filename);
807        } // end catch: IOException
808        finally {
809            IOUtils.closeQuietly(bis);
810        } // end finally
811
812        return decodedData;
813    } // end decodeFromFile
814
815    /**
816     * Convenience method for reading a binary file and base64-encoding it.
817     *
818     * @param filename Filename for reading binary data
819     * @return base64-encoded string or null if unsuccessful
820     * @since 2.1
821     */
822    public static String encodeFromFile(String filename) {
823        String encodedData = null;
824        Base64.InputStream bis = null;
825        try {
826            // Set up some useful variables
827            java.io.File file = new java.io.File(filename);
828            byte[] buffer = new byte[(int) (file.length() * 1.4)];
829            int length = 0;
830            int numBytes = 0;
831
832            // Open a stream
833            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
834                    Base64.ENCODE);
835
836            // Read until done
837            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
838                length += numBytes;
839
840            // Save in a variable to return
841            encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
842
843        } // end try
844        catch (java.io.IOException e) {
845            System.err.println("Error encoding from file " + filename);
846        } // end catch: IOException
847        finally {
848            IOUtils.closeQuietly(bis);
849        } // end finally
850
851        return encodedData;
852    } // end encodeFromFile
853
854    /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
855
856    /**
857     * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor,
858     * and encode/decode to/from Base64 notation on the fly.
859     *
860     * @see Base64
861     * @since 1.3
862     */
863    public static class InputStream extends java.io.FilterInputStream {
864        private boolean encode; // Encoding or decoding
865
866        private int position; // Current position in the buffer
867
868        private byte[] buffer; // Small buffer holding converted data
869
870        private int bufferLength; // Length of buffer (3 or 4)
871
872        private int numSigBytes; // Number of meaningful bytes in the buffer
873
874        private int lineLength;
875
876        private boolean breakLines; // Break lines at less than 80 characters
877
878        /**
879         * Constructs a {@link Base64.InputStream} in DECODE mode.
880         *
881         * @param in the <tt>java.io.InputStream</tt> from which to read data.
882         * @since 1.3
883         */
884        public InputStream(java.io.InputStream in) {
885            this(in, DECODE);
886        } // end constructor
887
888        /**
889         * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode.
890         * <p>
891         * Valid options:
892         *
893         * <pre>
894         *   ENCODE or DECODE: Encode or Decode as data is read.
895         *   DONT_BREAK_LINES: don't break lines at 76 characters
896         *     (only meaningful when encoding)
897         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
898         * </pre>
899         * <p>
900         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
901         *
902         * @param in the <tt>java.io.InputStream</tt> from which to read data.
903         * @param options Specified options
904         * @see Base64#ENCODE
905         * @see Base64#DECODE
906         * @see Base64#DONT_BREAK_LINES
907         * @since 2.0
908         */
909        public InputStream(java.io.InputStream in, int options) {
910            super(in);
911            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
912            this.encode = (options & ENCODE) == ENCODE;
913            this.bufferLength = encode ? 4 : 3;
914            this.buffer = new byte[bufferLength];
915            this.position = -1;
916            this.lineLength = 0;
917        } // end constructor
918
919        /**
920         * Reads enough of the input stream to convert to/from Base64 and returns the next byte.
921         *
922         * @return next byte
923         * @since 1.3
924         */
925        public int read() throws java.io.IOException {
926            // Do we need to get data?
927            if (position < 0) {
928                if (encode) {
929                    byte[] b3 = new byte[3];
930                    int numBinaryBytes = 0;
931                    for (int i = 0; i < 3; i++) {
932                        try {
933                            int b = in.read();
934
935                            // If end of stream, b is -1.
936                            if (b >= 0) {
937                                b3[i] = (byte) b;
938                                numBinaryBytes++;
939                            } // end if: not end of stream
940
941                        } // end try: read
942                        catch (java.io.IOException e) {
943                            // Only a problem if we got no data at all.
944                            if (i == 0)
945                                throw e;
946
947                        } // end catch
948                    } // end for: each needed input byte
949
950                    if (numBinaryBytes > 0) {
951                        encode3to4(b3, 0, numBinaryBytes, buffer, 0);
952                        position = 0;
953                        numSigBytes = 4;
954                    } // end if: got data
955                    else {
956                        return -1;
957                    } // end else
958                } // end if: encoding
959
960                // Else decoding
961                else {
962                    byte[] b4 = new byte[4];
963                    int i = 0;
964                    for (i = 0; i < 4; i++) {
965                        // Read four "meaningful" bytes:
966                        int b = 0;
967                        do {
968                            b = in.read();
969                        } while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
970
971                        if (b < 0)
972                            break; // Reads a -1 if end of stream
973
974                        b4[i] = (byte) b;
975                    } // end for: each needed input byte
976
977                    if (i == 4) {
978                        numSigBytes = decode4to3(b4, 0, buffer, 0);
979                        position = 0;
980                    } // end if: got four characters
981                    else if (i == 0) {
982                        return -1;
983                    } // end else if: also padded correctly
984                    else {
985                        // Must have broken out from above.
986                        throw new java.io.IOException("Improperly padded Base64 input.");
987                    } // end
988
989                } // end else: decode
990            } // end else: get data
991
992            // Got data?
993            if (position >= 0) {
994                // End of relevant data?
995                if (/* !encode && */position >= numSigBytes)
996                    return -1;
997
998                if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
999                    lineLength = 0;
1000                    return '\n';
1001                } // end if
1002                else {
1003                    lineLength++; // This isn't important when decoding
1004                    // but throwing an extra "if" seems
1005                    // just as wasteful.
1006
1007                    int b = buffer[position++];
1008
1009                    if (position >= bufferLength)
1010                        position = -1;
1011
1012                    return b & 0xFF; // This is how you "cast" a byte that's
1013                    // intended to be unsigned.
1014                } // end else
1015            } // end if: position >= 0
1016
1017            // Else error
1018            else {
1019                // When JDK1.4 is more accepted, use an assertion here.
1020                throw new java.io.IOException("Error in Base64 code reading stream.");
1021            } // end else
1022        } // end read
1023
1024        /**
1025         * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns
1026         * number of bytes read into array or -1 if end of stream is encountered.
1027         *
1028         * @param dest array to hold values
1029         * @param off offset for array
1030         * @param len max number of bytes to read into array
1031         * @return bytes read into array or -1 if end of stream is encountered.
1032         * @since 1.3
1033         */
1034        public int read(byte[] dest, int off, int len) throws java.io.IOException {
1035            int i;
1036            int b;
1037            for (i = 0; i < len; i++) {
1038                b = read();
1039
1040                // if( b < 0 && i == 0 )
1041                // return -1;
1042
1043                if (b >= 0)
1044                    dest[off + i] = (byte) b;
1045                else if (i == 0)
1046                    return -1;
1047                else
1048                    break; // Out of 'for' loop
1049            } // end for: each byte read
1050            return i;
1051        } // end read
1052
1053    } // end inner class InputStream
1054
1055    /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1056
1057    /**
1058     * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor,
1059     * and encode/decode to/from Base64 notation on the fly.
1060     *
1061     * @see Base64
1062     * @since 1.3
1063     */
1064    public static class OutputStream extends java.io.FilterOutputStream {
1065        private boolean encode;
1066
1067        private int position;
1068
1069        private byte[] buffer;
1070
1071        private int bufferLength;
1072
1073        private int lineLength;
1074
1075        private boolean breakLines;
1076
1077        private byte[] b4; // Scratch used in a few places
1078
1079        private boolean suspendEncoding;
1080
1081        /**
1082         * Constructs a {@link Base64.OutputStream} in ENCODE mode.
1083         *
1084         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1085         * @since 1.3
1086         */
1087        public OutputStream(java.io.OutputStream out) {
1088            this(out, ENCODE);
1089        } // end constructor
1090
1091        /**
1092         * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode.
1093         * <p>
1094         * Valid options:
1095         *
1096         * <pre>
1097         *   ENCODE or DECODE: Encode or Decode as data is read.
1098         *   DONT_BREAK_LINES: don't break lines at 76 characters
1099         *     (only meaningful when encoding)
1100         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
1101         * </pre>
1102         * <p>
1103         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1104         *
1105         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1106         * @param options Specified options.
1107         * @see Base64#ENCODE
1108         * @see Base64#DECODE
1109         * @see Base64#DONT_BREAK_LINES
1110         * @since 1.3
1111         */
1112        public OutputStream(java.io.OutputStream out, int options) {
1113            super(out);
1114            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1115            this.encode = (options & ENCODE) == ENCODE;
1116            this.bufferLength = encode ? 3 : 4;
1117            this.buffer = new byte[bufferLength];
1118            this.position = 0;
1119            this.lineLength = 0;
1120            this.suspendEncoding = false;
1121            this.b4 = new byte[4];
1122        } // end constructor
1123
1124        /**
1125         * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are
1126         * buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are
1127         * buffered four at a time.
1128         *
1129         * @param theByte the byte to write
1130         * @since 1.3
1131         */
1132        public void write(int theByte) throws java.io.IOException {
1133            // Encoding suspended?
1134            if (suspendEncoding) {
1135                super.out.write(theByte);
1136                return;
1137            } // end if: supsended
1138
1139            // Encode?
1140            if (encode) {
1141                buffer[position++] = (byte) theByte;
1142                if (position >= bufferLength) // Enough to encode.
1143                {
1144                    out.write(encode3to4(b4, buffer, bufferLength));
1145
1146                    lineLength += 4;
1147                    if (breakLines && lineLength >= MAX_LINE_LENGTH) {
1148                        out.write(NEW_LINE);
1149                        lineLength = 0;
1150                    } // end if: end of line
1151
1152                    position = 0;
1153                } // end if: enough to output
1154            } // end if: encoding
1155
1156            // Else, Decoding
1157            else {
1158                // Meaningful Base64 character?
1159                if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
1160                    buffer[position++] = (byte) theByte;
1161                    if (position >= bufferLength) // Enough to output.
1162                    {
1163                        int len = Base64.decode4to3(buffer, 0, b4, 0);
1164                        out.write(b4, 0, len);
1165                        // out.write( Base64.decode4to3( buffer ) );
1166                        position = 0;
1167                    } // end if: enough to output
1168                } // end if: meaningful base64 character
1169                else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
1170                    throw new java.io.IOException("Invalid character in Base64 data.");
1171                } // end else: not white space either
1172            } // end else: decoding
1173        } // end write
1174
1175        /**
1176         * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written.
1177         *
1178         * @param theBytes array from which to read bytes
1179         * @param off offset for array
1180         * @param len max number of bytes to read into array
1181         * @since 1.3
1182         */
1183        public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
1184            // Encoding suspended?
1185            if (suspendEncoding) {
1186                super.out.write(theBytes, off, len);
1187                return;
1188            } // end if: supsended
1189
1190            for (int i = 0; i < len; i++) {
1191                write(theBytes[off + i]);
1192            } // end for: each byte written
1193
1194        } // end write
1195
1196        /**
1197         * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
1198         */
1199        public void flushBase64() throws java.io.IOException {
1200            if (position > 0) {
1201                if (encode) {
1202                    out.write(encode3to4(b4, buffer, position));
1203                    position = 0;
1204                } // end if: encoding
1205                else {
1206                    throw new java.io.IOException("Base64 input not properly padded.");
1207                } // end else: decoding
1208            } // end if: buffer partially full
1209
1210        } // end flush
1211
1212        /**
1213         * Flushes and closes (I think, in the superclass) the stream.
1214         *
1215         * @since 1.3
1216         */
1217        public void close() throws java.io.IOException {
1218            // 1. Ensure that pending characters are written
1219            flushBase64();
1220
1221            // 2. Actually close the stream
1222            // Base class both flushes and closes.
1223            super.close();
1224
1225            buffer = null;
1226            out = null;
1227        } // end close
1228
1229        /**
1230         * Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a
1231         * stream.
1232         *
1233         * @since 1.5.1
1234         */
1235        public void suspendEncoding() throws java.io.IOException {
1236            flushBase64();
1237            this.suspendEncoding = true;
1238        } // end suspendEncoding
1239
1240        /**
1241         * Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a
1242         * stream.
1243         *
1244         * @since 1.5.1
1245         */
1246        public void resumeEncoding() {
1247            this.suspendEncoding = false;
1248        } // end resumeEncoding
1249
1250    } // end inner class OutputStream
1251
1252} // end class Base64