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