View Javadoc

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