OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 17 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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