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