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