View Javadoc

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