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        } // end catch
702        catch (java.lang.ClassNotFoundException e) {
703            e.printStackTrace();
704        } // end catch
705        finally {
706            IOUtils.closeQuietly(bais);
707            IOUtils.closeQuietly(ois);
708        } // end finally
709
710        return obj;
711    } // end decodeObject
712
713    /**
714     * Convenience method for encoding data to a file.
715     *
716     * @param dataToEncode byte array of data to encode in base64 form
717     * @param filename Filename for saving encoded data
718     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
719     * @since 2.1
720     */
721    public static boolean encodeToFile(byte[] dataToEncode, String filename) {
722        boolean success = false;
723        Base64.OutputStream bos = null;
724        try {
725            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
726            bos.write(dataToEncode);
727            success = true;
728        } // end try
729        catch (java.io.IOException e) {
730            // success is false
731        } // end catch: IOException
732        finally {
733            IOUtils.closeQuietly(bos);
734        } // end finally
735
736        return success;
737    } // end encodeToFile
738
739    /**
740     * Convenience method for decoding data to a file.
741     *
742     * @param dataToDecode Base64-encoded data as a string
743     * @param filename Filename for saving decoded data
744     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
745     * @since 2.1
746     */
747    public static boolean decodeToFile(String dataToDecode, String filename) {
748        boolean success = false;
749        Base64.OutputStream bos = null;
750        try {
751            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
752            bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
753            success = true;
754        } // end try
755        catch (java.io.IOException e) {
756            // success is false
757        } // end catch: IOException
758        finally {
759            IOUtils.closeQuietly(bos);
760        } // end finally
761
762        return success;
763    } // end decodeToFile
764
765    /**
766     * Convenience method for reading a base64-encoded file and decoding it.
767     *
768     * @param filename Filename for reading encoded data
769     * @return decoded byte array or null if unsuccessful
770     * @since 2.1
771     */
772    public static byte[] decodeFromFile(String filename) {
773        byte[] decodedData = null;
774        Base64.InputStream bis = null;
775        try {
776            // Set up some useful variables
777            java.io.File file = new java.io.File(filename);
778            byte[] buffer = null;
779            int length = 0;
780            int numBytes = 0;
781
782            // Check for size of file
783            if (file.length() > Integer.MAX_VALUE) {
784                System.err.println("File is too big for this convenience method (" + file.length() + " bytes).");
785                return null;
786            } // end if: file too big for int index
787            buffer = new byte[(int) file.length()];
788
789            // Open a stream
790            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
791                    Base64.DECODE);
792
793            // Read until done
794            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
795                length += numBytes;
796
797            // Save in a variable to return
798            decodedData = new byte[length];
799            System.arraycopy(buffer, 0, decodedData, 0, length);
800
801        } // end try
802        catch (java.io.IOException e) {
803            System.err.println("Error decoding from file " + filename);
804        } // end catch: IOException
805        finally {
806            IOUtils.closeQuietly(bis);
807        } // end finally
808
809        return decodedData;
810    } // end decodeFromFile
811
812    /**
813     * Convenience method for reading a binary file and base64-encoding it.
814     *
815     * @param filename Filename for reading binary data
816     * @return base64-encoded string or null if unsuccessful
817     * @since 2.1
818     */
819    public static String encodeFromFile(String filename) {
820        String encodedData = null;
821        Base64.InputStream bis = null;
822        try {
823            // Set up some useful variables
824            java.io.File file = new java.io.File(filename);
825            byte[] buffer = new byte[(int) (file.length() * 1.4)];
826            int length = 0;
827            int numBytes = 0;
828
829            // Open a stream
830            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)),
831                    Base64.ENCODE);
832
833            // Read until done
834            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
835                length += numBytes;
836
837            // Save in a variable to return
838            encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
839
840        } // end try
841        catch (java.io.IOException e) {
842            System.err.println("Error encoding from file " + filename);
843        } // end catch: IOException
844        finally {
845            IOUtils.closeQuietly(bis);
846        } // end finally
847
848        return encodedData;
849    } // end encodeFromFile
850
851    /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
852
853    /**
854     * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor,
855     * and encode/decode to/from Base64 notation on the fly.
856     *
857     * @see Base64
858     * @since 1.3
859     */
860    public static class InputStream extends java.io.FilterInputStream {
861        private boolean encode; // Encoding or decoding
862
863        private int position; // Current position in the buffer
864
865        private byte[] buffer; // Small buffer holding converted data
866
867        private int bufferLength; // Length of buffer (3 or 4)
868
869        private int numSigBytes; // Number of meaningful bytes in the buffer
870
871        private int lineLength;
872
873        private boolean breakLines; // Break lines at less than 80 characters
874
875        /**
876         * Constructs a {@link Base64.InputStream} in DECODE mode.
877         *
878         * @param in the <tt>java.io.InputStream</tt> from which to read data.
879         * @since 1.3
880         */
881        public InputStream(java.io.InputStream in) {
882            this(in, DECODE);
883        } // end constructor
884
885        /**
886         * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode.
887         * <p>
888         * Valid options:
889         *
890         * <pre>
891         *   ENCODE or DECODE: Encode or Decode as data is read.
892         *   DONT_BREAK_LINES: don't break lines at 76 characters
893         *     (only meaningful when encoding)
894         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
895         * </pre>
896         * <p>
897         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
898         *
899         * @param in the <tt>java.io.InputStream</tt> from which to read data.
900         * @param options Specified options
901         * @see Base64#ENCODE
902         * @see Base64#DECODE
903         * @see Base64#DONT_BREAK_LINES
904         * @since 2.0
905         */
906        public InputStream(java.io.InputStream in, int options) {
907            super(in);
908            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
909            this.encode = (options & ENCODE) == ENCODE;
910            this.bufferLength = encode ? 4 : 3;
911            this.buffer = new byte[bufferLength];
912            this.position = -1;
913            this.lineLength = 0;
914        } // end constructor
915
916        /**
917         * Reads enough of the input stream to convert to/from Base64 and returns the next byte.
918         *
919         * @return next byte
920         * @since 1.3
921         */
922        public int read() throws java.io.IOException {
923            // Do we need to get data?
924            if (position < 0) {
925                if (encode) {
926                    byte[] b3 = new byte[3];
927                    int numBinaryBytes = 0;
928                    for (int i = 0; i < 3; i++) {
929                        try {
930                            int b = in.read();
931
932                            // If end of stream, b is -1.
933                            if (b >= 0) {
934                                b3[i] = (byte) b;
935                                numBinaryBytes++;
936                            } // end if: not end of stream
937
938                        } // end try: read
939                        catch (java.io.IOException e) {
940                            // Only a problem if we got no data at all.
941                            if (i == 0)
942                                throw e;
943
944                        } // end catch
945                    } // end for: each needed input byte
946
947                    if (numBinaryBytes > 0) {
948                        encode3to4(b3, 0, numBinaryBytes, buffer, 0);
949                        position = 0;
950                        numSigBytes = 4;
951                    } // end if: got data
952                    else {
953                        return -1;
954                    } // end else
955                } // end if: encoding
956
957                // Else decoding
958                else {
959                    byte[] b4 = new byte[4];
960                    int i = 0;
961                    for (i = 0; i < 4; i++) {
962                        // Read four "meaningful" bytes:
963                        int b = 0;
964                        do {
965                            b = in.read();
966                        } while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
967
968                        if (b < 0)
969                            break; // Reads a -1 if end of stream
970
971                        b4[i] = (byte) b;
972                    } // end for: each needed input byte
973
974                    if (i == 4) {
975                        numSigBytes = decode4to3(b4, 0, buffer, 0);
976                        position = 0;
977                    } // end if: got four characters
978                    else if (i == 0) {
979                        return -1;
980                    } // end else if: also padded correctly
981                    else {
982                        // Must have broken out from above.
983                        throw new java.io.IOException("Improperly padded Base64 input.");
984                    } // end
985
986                } // end else: decode
987            } // end else: get data
988
989            // Got data?
990            if (position >= 0) {
991                // End of relevant data?
992                if (/* !encode && */position >= numSigBytes)
993                    return -1;
994
995                if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
996                    lineLength = 0;
997                    return '\n';
998                } // end if
999                else {
1000                    lineLength++; // This isn't important when decoding
1001                    // but throwing an extra "if" seems
1002                    // just as wasteful.
1003
1004                    int b = buffer[position++];
1005
1006                    if (position >= bufferLength)
1007                        position = -1;
1008
1009                    return b & 0xFF; // This is how you "cast" a byte that's
1010                    // intended to be unsigned.
1011                } // end else
1012            } // end if: position >= 0
1013
1014            // Else error
1015            else {
1016                // When JDK1.4 is more accepted, use an assertion here.
1017                throw new java.io.IOException("Error in Base64 code reading stream.");
1018            } // end else
1019        } // end read
1020
1021        /**
1022         * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns
1023         * number of bytes read into array or -1 if end of stream is encountered.
1024         *
1025         * @param dest array to hold values
1026         * @param off offset for array
1027         * @param len max number of bytes to read into array
1028         * @return bytes read into array or -1 if end of stream is encountered.
1029         * @since 1.3
1030         */
1031        public int read(byte[] dest, int off, int len) throws java.io.IOException {
1032            int i;
1033            int b;
1034            for (i = 0; i < len; i++) {
1035                b = read();
1036
1037                // if( b < 0 && i == 0 )
1038                // return -1;
1039
1040                if (b >= 0)
1041                    dest[off + i] = (byte) b;
1042                else if (i == 0)
1043                    return -1;
1044                else
1045                    break; // Out of 'for' loop
1046            } // end for: each byte read
1047            return i;
1048        } // end read
1049
1050    } // end inner class InputStream
1051
1052    /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1053
1054    /**
1055     * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor,
1056     * and encode/decode to/from Base64 notation on the fly.
1057     *
1058     * @see Base64
1059     * @since 1.3
1060     */
1061    public static class OutputStream extends java.io.FilterOutputStream {
1062        private boolean encode;
1063
1064        private int position;
1065
1066        private byte[] buffer;
1067
1068        private int bufferLength;
1069
1070        private int lineLength;
1071
1072        private boolean breakLines;
1073
1074        private byte[] b4; // Scratch used in a few places
1075
1076        private boolean suspendEncoding;
1077
1078        /**
1079         * Constructs a {@link Base64.OutputStream} in ENCODE mode.
1080         *
1081         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1082         * @since 1.3
1083         */
1084        public OutputStream(java.io.OutputStream out) {
1085            this(out, ENCODE);
1086        } // end constructor
1087
1088        /**
1089         * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode.
1090         * <p>
1091         * Valid options:
1092         *
1093         * <pre>
1094         *   ENCODE or DECODE: Encode or Decode as data is read.
1095         *   DONT_BREAK_LINES: don't break lines at 76 characters
1096         *     (only meaningful when encoding)
1097         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
1098         * </pre>
1099         * <p>
1100         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1101         *
1102         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
1103         * @param options Specified options.
1104         * @see Base64#ENCODE
1105         * @see Base64#DECODE
1106         * @see Base64#DONT_BREAK_LINES
1107         * @since 1.3
1108         */
1109        public OutputStream(java.io.OutputStream out, int options) {
1110            super(out);
1111            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1112            this.encode = (options & ENCODE) == ENCODE;
1113            this.bufferLength = encode ? 3 : 4;
1114            this.buffer = new byte[bufferLength];
1115            this.position = 0;
1116            this.lineLength = 0;
1117            this.suspendEncoding = false;
1118            this.b4 = new byte[4];
1119        } // end constructor
1120
1121        /**
1122         * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are
1123         * buffered three at a time before the output stream actually gets a write() call. When decoding, bytes are
1124         * buffered four at a time.
1125         *
1126         * @param theByte the byte to write
1127         * @since 1.3
1128         */
1129        public void write(int theByte) throws java.io.IOException {
1130            // Encoding suspended?
1131            if (suspendEncoding) {
1132                super.out.write(theByte);
1133                return;
1134            } // end if: supsended
1135
1136            // Encode?
1137            if (encode) {
1138                buffer[position++] = (byte) theByte;
1139                if (position >= bufferLength) // Enough to encode.
1140                {
1141                    out.write(encode3to4(b4, buffer, bufferLength));
1142
1143                    lineLength += 4;
1144                    if (breakLines && lineLength >= MAX_LINE_LENGTH) {
1145                        out.write(NEW_LINE);
1146                        lineLength = 0;
1147                    } // end if: end of line
1148
1149                    position = 0;
1150                } // end if: enough to output
1151            } // end if: encoding
1152
1153            // Else, Decoding
1154            else {
1155                // Meaningful Base64 character?
1156                if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
1157                    buffer[position++] = (byte) theByte;
1158                    if (position >= bufferLength) // Enough to output.
1159                    {
1160                        int len = Base64.decode4to3(buffer, 0, b4, 0);
1161                        out.write(b4, 0, len);
1162                        // out.write( Base64.decode4to3( buffer ) );
1163                        position = 0;
1164                    } // end if: enough to output
1165                } // end if: meaningful base64 character
1166                else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
1167                    throw new java.io.IOException("Invalid character in Base64 data.");
1168                } // end else: not white space either
1169            } // end else: decoding
1170        } // end write
1171
1172        /**
1173         * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written.
1174         *
1175         * @param theBytes array from which to read bytes
1176         * @param off offset for array
1177         * @param len max number of bytes to read into array
1178         * @since 1.3
1179         */
1180        public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
1181            // Encoding suspended?
1182            if (suspendEncoding) {
1183                super.out.write(theBytes, off, len);
1184                return;
1185            } // end if: supsended
1186
1187            for (int i = 0; i < len; i++) {
1188                write(theBytes[off + i]);
1189            } // end for: each byte written
1190
1191        } // end write
1192
1193        /**
1194         * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
1195         */
1196        public void flushBase64() throws java.io.IOException {
1197            if (position > 0) {
1198                if (encode) {
1199                    out.write(encode3to4(b4, buffer, position));
1200                    position = 0;
1201                } // end if: encoding
1202                else {
1203                    throw new java.io.IOException("Base64 input not properly padded.");
1204                } // end else: decoding
1205            } // end if: buffer partially full
1206
1207        } // end flush
1208
1209        /**
1210         * Flushes and closes (I think, in the superclass) the stream.
1211         *
1212         * @since 1.3
1213         */
1214        public void close() throws java.io.IOException {
1215            // 1. Ensure that pending characters are written
1216            flushBase64();
1217
1218            // 2. Actually close the stream
1219            // Base class both flushes and closes.
1220            super.close();
1221
1222            buffer = null;
1223            out = null;
1224        } // end close
1225
1226        /**
1227         * Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a
1228         * stream.
1229         *
1230         * @since 1.5.1
1231         */
1232        public void suspendEncoding() throws java.io.IOException {
1233            flushBase64();
1234            this.suspendEncoding = true;
1235        } // end suspendEncoding
1236
1237        /**
1238         * Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a
1239         * stream.
1240         *
1241         * @since 1.5.1
1242         */
1243        public void resumeEncoding() {
1244            this.suspendEncoding = false;
1245        } // end resumeEncoding
1246
1247    } // end inner class OutputStream
1248
1249} // end class Base64