/Users/lyon/j4p/src/utils/Base64.java

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