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