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