View Javadoc

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