Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | File Members

Base64.java

Go to the documentation of this file.
00001 /**
00002  * Encodes and decodes to and from Base64 notation.
00003  *
00004  * <p>
00005  * Change Log:
00006  * </p>
00007  * <ul>
00008  *  <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
00009  *   some convenience methods for reading and writing to and from files.</li>
00010  *  <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
00011  *   with other encodings (like EBCDIC).</li>
00012  *  <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
00013  *   encoded data was a single byte.</li>
00014  *  <li>v2.0 - I got rid of methods that used booleans to set options. 
00015  *   Now everything is more consolidated and cleaner. The code now detects
00016  *   when data that's being decoded is gzip-compressed and will decompress it
00017  *   automatically. Generally things are cleaner. You'll probably have to
00018  *   change some method calls that you were making to support the new
00019  *   options format (<tt>int</tt>s that you "OR" together).</li>
00020  *  <li>v1.5.1 - Fixed bug when decompressing and decoding to a             
00021  *   byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.      
00022  *   Added the ability to "suspend" encoding in the Output Stream so        
00023  *   you can turn on and off the encoding if you need to embed base64       
00024  *   data in an otherwise "normal" stream (like an XML file).</li>  
00025  *  <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
00026  *      This helps when using GZIP streams.
00027  *      Added the ability to GZip-compress objects before encoding them.</li>
00028  *  <li>v1.4 - Added helper methods to read/write files.</li>
00029  *  <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
00030  *  <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
00031  *      where last buffer being read, if not completely full, was not returned.</li>
00032  *  <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
00033  *  <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
00034  * </ul>
00035  *
00036  * <p>
00037  * I am placing this code in the Public Domain. Do with it as you will.
00038  * This software comes with no guarantees or warranties but with
00039  * plenty of well-wishing instead!
00040  * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
00041  * periodically to check for updates or to contribute improvements.
00042  * </p>
00043  *
00044  * @author Robert Harder
00045  * @author rob@iharder.net
00046  * @version 2.1
00047  */
00048 public class Base64
00049 {
00050     
00051 /* ********  P U B L I C   F I E L D S  ******** */   
00052     
00053     
00054     /** No options specified. Value is zero. */
00055     public final static int NO_OPTIONS = 0;
00056     
00057     /** Specify encoding. */
00058     public final static int ENCODE = 1;
00059     
00060     
00061     /** Specify decoding. */
00062     public final static int DECODE = 0;
00063     
00064     
00065     /** Specify that data should be gzip-compressed. */
00066     public final static int GZIP = 2;
00067     
00068     
00069     /** Don't break lines when encoding (violates strict Base64 specification) */
00070     public final static int DONT_BREAK_LINES = 8;
00071     
00072     
00073 /* ********  P R I V A T E   F I E L D S  ******** */  
00074     
00075     
00076     /** Maximum line length (76) of Base64 output. */
00077     private final static int MAX_LINE_LENGTH = 76;
00078     
00079     
00080     /** The equals sign (=) as a byte. */
00081     private final static byte EQUALS_SIGN = (byte)'=';
00082     
00083     
00084     /** The new line character (\n) as a byte. */
00085     private final static byte NEW_LINE = (byte)'\n';
00086     
00087     
00088     /** Preferred encoding. */
00089     private final static String PREFERRED_ENCODING = "UTF-8";
00090     
00091     
00092     /** The 64 valid Base64 values. */
00093     private final static byte[] ALPHABET;
00094     private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
00095     {
00096         (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
00097         (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
00098         (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 
00099         (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
00100         (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
00101         (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
00102         (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 
00103         (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
00104         (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 
00105         (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
00106     };
00107     
00108     /** Determine which ALPHABET to use. */
00109     static
00110     {
00111         byte[] __bytes;
00112         try
00113         {
00114             __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING );
00115         }   // end try
00116         catch (java.io.UnsupportedEncodingException use)
00117         {
00118             __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
00119         }   // end catch
00120         ALPHABET = __bytes;
00121     }   // end static
00122     
00123     
00124     /** 
00125      * Translates a Base64 value to either its 6-bit reconstruction value
00126      * or a negative number indicating some other meaning.
00127      **/
00128     private final static byte[] DECODABET =
00129     {   
00130         -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
00131         -5,-5,                                      // Whitespace: Tab and Linefeed
00132         -9,-9,                                      // Decimal 11 - 12
00133         -5,                                         // Whitespace: Carriage Return
00134         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
00135         -9,-9,-9,-9,-9,                             // Decimal 27 - 31
00136         -5,                                         // Whitespace: Space
00137         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
00138         62,                                         // Plus sign at decimal 43
00139         -9,-9,-9,                                   // Decimal 44 - 46
00140         63,                                         // Slash at decimal 47
00141         52,53,54,55,56,57,58,59,60,61,              // Numbers zero through nine
00142         -9,-9,-9,                                   // Decimal 58 - 60
00143         -1,                                         // Equals sign at decimal 61
00144         -9,-9,-9,                                      // Decimal 62 - 64
00145         0,1,2,3,4,5,6,7,8,9,10,11,12,13,            // Letters 'A' through 'N'
00146         14,15,16,17,18,19,20,21,22,23,24,25,        // Letters 'O' through 'Z'
00147         -9,-9,-9,-9,-9,-9,                          // Decimal 91 - 96
00148         26,27,28,29,30,31,32,33,34,35,36,37,38,     // Letters 'a' through 'm'
00149         39,40,41,42,43,44,45,46,47,48,49,50,51,     // Letters 'n' through 'z'
00150         -9,-9,-9,-9                                 // Decimal 123 - 126
00151         /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 139
00152         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
00153         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
00154         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
00155         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
00156         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
00157         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
00158         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
00159         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
00160         -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
00161     };
00162     
00163     // I think I end up not using the BAD_ENCODING indicator.
00164     //private final static byte BAD_ENCODING    = -9; // Indicates error in encoding
00165     private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
00166     private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
00167 
00168     
00169     /** Defeats instantiation. */
00170     private Base64(){}
00171     
00172     
00173     
00174 /* ********  E N C O D I N G   M E T H O D S  ******** */    
00175     
00176     
00177     /**
00178      * Encodes up to the first three bytes of array <var>threeBytes</var>
00179      * and returns a four-byte array in Base64 notation.
00180      * The actual number of significant bytes in your array is
00181      * given by <var>numSigBytes</var>.
00182      * The array <var>threeBytes</var> needs only be as big as
00183      * <var>numSigBytes</var>.
00184      * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
00185      *
00186      * @param b4 A reusable byte array to reduce array instantiation
00187      * @param threeBytes the array to convert
00188      * @param numSigBytes the number of significant bytes in your array
00189      * @return four byte array in Base64 notation.
00190      * @since 1.5.1
00191      */
00192     private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes )
00193     {
00194         encode3to4( threeBytes, 0, numSigBytes, b4, 0 );
00195         return b4;
00196     }   // end encode3to4
00197 
00198     
00199     /**
00200      * Encodes up to three bytes of the array <var>source</var>
00201      * and writes the resulting four Base64 bytes to <var>destination</var>.
00202      * The source and destination arrays can be manipulated
00203      * anywhere along their length by specifying 
00204      * <var>srcOffset</var> and <var>destOffset</var>.
00205      * This method does not check to make sure your arrays
00206      * are large enough to accomodate <var>srcOffset</var> + 3 for
00207      * the <var>source</var> array or <var>destOffset</var> + 4 for
00208      * the <var>destination</var> array.
00209      * The actual number of significant bytes in your array is
00210      * given by <var>numSigBytes</var>.
00211      *
00212      * @param source the array to convert
00213      * @param srcOffset the index where conversion begins
00214      * @param numSigBytes the number of significant bytes in your array
00215      * @param destination the array to hold the conversion
00216      * @param destOffset the index where output will be put
00217      * @return the <var>destination</var> array
00218      * @since 1.3
00219      */
00220     private static byte[] encode3to4( 
00221      byte[] source, int srcOffset, int numSigBytes,
00222      byte[] destination, int destOffset )
00223     {
00224         //           1         2         3  
00225         // 01234567890123456789012345678901 Bit position
00226         // --------000000001111111122222222 Array position from threeBytes
00227         // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
00228         //          >>18  >>12  >> 6  >> 0  Right shift necessary
00229         //                0x3f  0x3f  0x3f  Additional AND
00230         
00231         // Create buffer with zero-padding if there are only one or two
00232         // significant bytes passed in the array.
00233         // We have to shift left 24 in order to flush out the 1's that appear
00234         // when Java treats a value as negative that is cast from a byte to an int.
00235         int inBuff =   ( numSigBytes > 0 ? ((source[ srcOffset     ] << 24) >>>  8) : 0 )
00236                      | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
00237                      | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
00238 
00239         switch( numSigBytes )
00240         {
00241             case 3:
00242                 destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
00243                 destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
00244                 destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
00245                 destination[ destOffset + 3 ] = ALPHABET[ (inBuff       ) & 0x3f ];
00246                 return destination;
00247                 
00248             case 2:
00249                 destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
00250                 destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
00251                 destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
00252                 destination[ destOffset + 3 ] = EQUALS_SIGN;
00253                 return destination;
00254                 
00255             case 1:
00256                 destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
00257                 destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
00258                 destination[ destOffset + 2 ] = EQUALS_SIGN;
00259                 destination[ destOffset + 3 ] = EQUALS_SIGN;
00260                 return destination;
00261                 
00262             default:
00263                 return destination;
00264         }   // end switch
00265     }   // end encode3to4
00266     
00267     
00268     
00269     /**
00270      * Serializes an object and returns the Base64-encoded
00271      * version of that serialized object. If the object
00272      * cannot be serialized or there is another error,
00273      * the method will return <tt>null</tt>.
00274      * The object is not GZip-compressed before being encoded.
00275      *
00276      * @param serializableObject The object to encode
00277      * @return The Base64-encoded object
00278      * @since 1.4
00279      */
00280     public static String encodeObject( java.io.Serializable serializableObject )
00281     {
00282         return encodeObject( serializableObject, NO_OPTIONS );
00283     }   // end encodeObject
00284     
00285 
00286 
00287     /**
00288      * Serializes an object and returns the Base64-encoded
00289      * version of that serialized object. If the object
00290      * cannot be serialized or there is another error,
00291      * the method will return <tt>null</tt>.
00292      * <p>
00293      * Valid options:<pre>
00294      *   GZIP: gzip-compresses object before encoding it.
00295      *   DONT_BREAK_LINES: don't break lines at 76 characters
00296      *     <i>Note: Technically, this makes your encoding non-compliant.</i>
00297      * </pre>
00298      * <p>
00299      * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
00300      * <p>
00301      * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
00302      *
00303      * @param serializableObject The object to encode
00304      * @param options Specified options
00305      * @return The Base64-encoded object
00306      * @see Base64#GZIP
00307      * @see Base64#DONT_BREAK_LINES
00308      * @since 2.0
00309      */
00310     public static String encodeObject( java.io.Serializable serializableObject, int options )
00311     {
00312         // Streams
00313         java.io.ByteArrayOutputStream  baos  = null; 
00314         java.io.OutputStream           b64os = null; 
00315         java.io.ObjectOutputStream     oos   = null; 
00316         java.util.zip.GZIPOutputStream gzos  = null;
00317         
00318         // Isolate options
00319         int gzip           = (options & GZIP);
00320         int dontBreakLines = (options & DONT_BREAK_LINES);
00321         
00322         try
00323         {
00324             // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
00325             baos  = new java.io.ByteArrayOutputStream();
00326             b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
00327     
00328             // GZip?
00329             if( gzip == GZIP )
00330             {
00331                 gzos = new java.util.zip.GZIPOutputStream( b64os );
00332                 oos  = new java.io.ObjectOutputStream( gzos );
00333             }   // end if: gzip
00334             else
00335                 oos   = new java.io.ObjectOutputStream( b64os );
00336             
00337             oos.writeObject( serializableObject );
00338         }   // end try
00339         catch( java.io.IOException e )
00340         {
00341             e.printStackTrace();
00342             return null;
00343         }   // end catch
00344         finally
00345         {
00346             try{ oos.close();   } catch( Exception e ){}
00347             try{ gzos.close();  } catch( Exception e ){}
00348             try{ b64os.close(); } catch( Exception e ){}
00349             try{ baos.close();  } catch( Exception e ){}
00350         }   // end finally
00351         
00352         // Return value according to relevant encoding.
00353         try 
00354         {
00355             return new String( baos.toByteArray(), PREFERRED_ENCODING );
00356         }   // end try
00357         catch (java.io.UnsupportedEncodingException uue)
00358         {
00359             return new String( baos.toByteArray() );
00360         }   // end catch
00361         
00362     }   // end encode
00363     
00364     
00365 
00366     /**
00367      * Encodes a byte array into Base64 notation.
00368      * Does not GZip-compress data.
00369      *
00370      * @param source The data to convert
00371      * @since 1.4
00372      */
00373     public static String encodeBytes( byte[] source )
00374     {
00375         return encodeBytes( source, 0, source.length, NO_OPTIONS );
00376     }   // end encodeBytes
00377     
00378 
00379 
00380     /**
00381      * Encodes a byte array into Base64 notation.
00382      * <p>
00383      * Valid options:<pre>
00384      *   GZIP: gzip-compresses object before encoding it.
00385      *   DONT_BREAK_LINES: don't break lines at 76 characters
00386      *     <i>Note: Technically, this makes your encoding non-compliant.</i>
00387      * </pre>
00388      * <p>
00389      * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
00390      * <p>
00391      * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
00392      *
00393      *
00394      * @param source The data to convert
00395      * @param options Specified options
00396      * @see Base64#GZIP
00397      * @see Base64#DONT_BREAK_LINES
00398      * @since 2.0
00399      */
00400     public static String encodeBytes( byte[] source, int options )
00401     {   
00402         return encodeBytes( source, 0, source.length, options );
00403     }   // end encodeBytes
00404     
00405     
00406     /**
00407      * Encodes a byte array into Base64 notation.
00408      * Does not GZip-compress data.
00409      *
00410      * @param source The data to convert
00411      * @param off Offset in array where conversion should begin
00412      * @param len Length of data to convert
00413      * @since 1.4
00414      */
00415     public static String encodeBytes( byte[] source, int off, int len )
00416     {
00417         return encodeBytes( source, off, len, NO_OPTIONS );
00418     }   // end encodeBytes
00419     
00420     
00421 
00422     /**
00423      * Encodes a byte array into Base64 notation.
00424      * <p>
00425      * Valid options:<pre>
00426      *   GZIP: gzip-compresses object before encoding it.
00427      *   DONT_BREAK_LINES: don't break lines at 76 characters
00428      *     <i>Note: Technically, this makes your encoding non-compliant.</i>
00429      * </pre>
00430      * <p>
00431      * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
00432      * <p>
00433      * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
00434      *
00435      *
00436      * @param source The data to convert
00437      * @param off Offset in array where conversion should begin
00438      * @param len Length of data to convert
00439      * @param options Specified options
00440      * @see Base64#GZIP
00441      * @see Base64#DONT_BREAK_LINES
00442      * @since 2.0
00443      */
00444     public static String encodeBytes( byte[] source, int off, int len, int options )
00445     {
00446         // Isolate options
00447         int dontBreakLines = ( options & DONT_BREAK_LINES );
00448         int gzip           = ( options & GZIP   );
00449         
00450         // Compress?
00451         if( gzip == GZIP )
00452         {
00453             java.io.ByteArrayOutputStream  baos  = null;
00454             java.util.zip.GZIPOutputStream gzos  = null;
00455             Base64.OutputStream            b64os = null;
00456             
00457     
00458             try
00459             {
00460                 // GZip -> Base64 -> ByteArray
00461                 baos = new java.io.ByteArrayOutputStream();
00462                 b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
00463                 gzos  = new java.util.zip.GZIPOutputStream( b64os ); 
00464             
00465                 gzos.write( source, off, len );
00466                 gzos.close();
00467             }   // end try
00468             catch( java.io.IOException e )
00469             {
00470                 e.printStackTrace();
00471                 return null;
00472             }   // end catch
00473             finally
00474             {
00475                 try{ gzos.close();  } catch( Exception e ){}
00476                 try{ b64os.close(); } catch( Exception e ){}
00477                 try{ baos.close();  } catch( Exception e ){}
00478             }   // end finally
00479 
00480             // Return value according to relevant encoding.
00481             try
00482             {
00483                 return new String( baos.toByteArray(), PREFERRED_ENCODING );
00484             }   // end try
00485             catch (java.io.UnsupportedEncodingException uue)
00486             {
00487                 return new String( baos.toByteArray() );
00488             }   // end catch
00489         }   // end if: compress
00490         
00491         // Else, don't compress. Better not to use streams at all then.
00492         else
00493         {
00494             // Convert option to boolean in way that code likes it.
00495             boolean breakLines = dontBreakLines == 0;
00496             
00497             int    len43   = len * 4 / 3;
00498             byte[] outBuff = new byte[   ( len43 )                      // Main 4:3
00499                                        + ( (len % 3) > 0 ? 4 : 0 )      // Account for padding
00500                                        + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines      
00501             int d = 0;
00502             int e = 0;
00503             int len2 = len - 2;
00504             int lineLength = 0;
00505             for( ; d < len2; d+=3, e+=4 )
00506             {
00507                 encode3to4( source, d+off, 3, outBuff, e );
00508 
00509                 lineLength += 4;
00510                 if( breakLines && lineLength == MAX_LINE_LENGTH )
00511                 {   
00512                     outBuff[e+4] = NEW_LINE;
00513                     e++;
00514                     lineLength = 0;
00515                 }   // end if: end of line
00516             }   // en dfor: each piece of array
00517 
00518             if( d < len )
00519             {
00520                 encode3to4( source, d+off, len - d, outBuff, e );
00521                 e += 4;
00522             }   // end if: some padding needed
00523 
00524             
00525             // Return value according to relevant encoding.
00526             try
00527             {
00528                 return new String( outBuff, 0, e, PREFERRED_ENCODING );
00529             }   // end try
00530             catch (java.io.UnsupportedEncodingException uue)
00531             {
00532                 return new String( outBuff, 0, e );
00533             }   // end catch
00534             
00535         }   // end else: don't compress
00536         
00537     }   // end encodeBytes
00538     
00539 
00540     
00541     
00542     
00543 /* ********  D E C O D I N G   M E T H O D S  ******** */
00544     
00545     
00546     /**
00547      * Decodes four bytes from array <var>source</var>
00548      * and writes the resulting bytes (up to three of them)
00549      * to <var>destination</var>.
00550      * The source and destination arrays can be manipulated
00551      * anywhere along their length by specifying 
00552      * <var>srcOffset</var> and <var>destOffset</var>.
00553      * This method does not check to make sure your arrays
00554      * are large enough to accomodate <var>srcOffset</var> + 4 for
00555      * the <var>source</var> array or <var>destOffset</var> + 3 for
00556      * the <var>destination</var> array.
00557      * This method returns the actual number of bytes that 
00558      * were converted from the Base64 encoding.
00559      * 
00560      *
00561      * @param source the array to convert
00562      * @param srcOffset the index where conversion begins
00563      * @param destination the array to hold the conversion
00564      * @param destOffset the index where output will be put
00565      * @return the number of decoded bytes converted
00566      * @since 1.3
00567      */
00568     private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
00569     {
00570         // Example: Dk==
00571         if( source[ srcOffset + 2] == EQUALS_SIGN )
00572         {
00573             // Two ways to do the same thing. Don't know which way I like best.
00574             //int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] << 24 ) >>>  6 )
00575             //              | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
00576             int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] & 0xFF ) << 18 )
00577                           | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
00578             
00579             destination[ destOffset ] = (byte)( outBuff >>> 16 );
00580             return 1;
00581         }
00582         
00583         // Example: DkL=
00584         else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
00585         {
00586             // Two ways to do the same thing. Don't know which way I like best.
00587             //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
00588             //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
00589             //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
00590             int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
00591                           | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
00592                           | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6 );
00593             
00594             destination[ destOffset     ] = (byte)( outBuff >>> 16 );
00595             destination[ destOffset + 1 ] = (byte)( outBuff >>>  8 );
00596             return 2;
00597         }
00598         
00599         // Example: DkLE
00600         else
00601         {
00602             try{
00603             // Two ways to do the same thing. Don't know which way I like best.
00604             //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
00605             //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
00606             //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
00607             //              | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
00608             int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
00609                           | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
00610                           | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6)
00611                           | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF )      );
00612 
00613             
00614             destination[ destOffset     ] = (byte)( outBuff >> 16 );
00615             destination[ destOffset + 1 ] = (byte)( outBuff >>  8 );
00616             destination[ destOffset + 2 ] = (byte)( outBuff       );
00617 
00618             return 3;
00619             }catch( Exception e){
00620                 System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset     ] ]  ) );
00621                 System.out.println(""+source[srcOffset+1]+  ": " + ( DECODABET[ source[ srcOffset + 1 ] ]  ) );
00622                 System.out.println(""+source[srcOffset+2]+  ": " + ( DECODABET[ source[ srcOffset + 2 ] ]  ) );
00623                 System.out.println(""+source[srcOffset+3]+  ": " + ( DECODABET[ source[ srcOffset + 3 ] ]  ) );
00624                 return -1;
00625             }   //e nd catch
00626         }
00627     }   // end decodeToBytes
00628     
00629     
00630     
00631     
00632     /**
00633      * Very low-level access to decoding ASCII characters in
00634      * the form of a byte array. Does not support automatically
00635      * gunzipping or any other "fancy" features.
00636      *
00637      * @param source The Base64 encoded data
00638      * @param off    The offset of where to begin decoding
00639      * @param len    The length of characters to decode
00640      * @return decoded data
00641      * @since 1.3
00642      */
00643     public static byte[] decode( byte[] source, int off, int len )
00644     {
00645         int    len34   = len * 3 / 4;
00646         byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
00647         int    outBuffPosn = 0;
00648         
00649         byte[] b4        = new byte[4];
00650         int    b4Posn    = 0;
00651         int    i         = 0;
00652         byte   sbiCrop   = 0;
00653         byte   sbiDecode = 0;
00654         for( i = off; i < off+len; i++ )
00655         {
00656             sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
00657             sbiDecode = DECODABET[ sbiCrop ];
00658             
00659             if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
00660             {
00661                 if( sbiDecode >= EQUALS_SIGN_ENC )
00662                 {
00663                     b4[ b4Posn++ ] = sbiCrop;
00664                     if( b4Posn > 3 )
00665                     {
00666                         outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
00667                         b4Posn = 0;
00668                         
00669                         // If that was the equals sign, break out of 'for' loop
00670                         if( sbiCrop == EQUALS_SIGN )
00671                             break;
00672                     }   // end if: quartet built
00673                     
00674                 }   // end if: equals sign or better
00675                 
00676             }   // end if: white space, equals sign or better
00677             else
00678             {
00679                 System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
00680                 return null;
00681             }   // end else: 
00682         }   // each input character
00683                                    
00684         byte[] out = new byte[ outBuffPosn ];
00685         System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 
00686         return out;
00687     }   // end decode
00688     
00689     
00690     
00691     
00692     /**
00693      * Decodes data from Base64 notation, automatically
00694      * detecting gzip-compressed data and decompressing it.
00695      *
00696      * @param s the string to decode
00697      * @return the decoded data
00698      * @since 1.4
00699      */
00700     public static byte[] decode( String s )
00701     {   
00702         byte[] bytes;
00703         try
00704         {
00705             bytes = s.getBytes( PREFERRED_ENCODING );
00706         }   // end try
00707         catch( java.io.UnsupportedEncodingException uee )
00708         {
00709             bytes = s.getBytes();
00710         }   // end catch
00711                 //</change>
00712         
00713         // Decode
00714         bytes = decode( bytes, 0, bytes.length );
00715         
00716         
00717         // Check to see if it's gzip-compressed
00718         // GZIP Magic Two-Byte Number: 0x8b1f (35615)
00719         if( bytes != null && bytes.length >= 4 )
00720         {
00721             
00722             int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);       
00723             if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) 
00724             {
00725                 java.io.ByteArrayInputStream  bais = null;
00726                 java.util.zip.GZIPInputStream gzis = null;
00727                 java.io.ByteArrayOutputStream baos = null;
00728                 byte[] buffer = new byte[2048];
00729                 int    length = 0;
00730 
00731                 try
00732                 {
00733                     baos = new java.io.ByteArrayOutputStream();
00734                     bais = new java.io.ByteArrayInputStream( bytes );
00735                     gzis = new java.util.zip.GZIPInputStream( bais );
00736 
00737                     while( ( length = gzis.read( buffer ) ) >= 0 )
00738                     {
00739                         baos.write(buffer,0,length);
00740                     }   // end while: reading input
00741 
00742                     // No error? Get new bytes.
00743                     bytes = baos.toByteArray();
00744 
00745                 }   // end try
00746                 catch( java.io.IOException e )
00747                 {
00748                     // Just return originally-decoded bytes
00749                 }   // end catch
00750                 finally
00751                 {
00752                     try{ baos.close(); } catch( Exception e ){}
00753                     try{ gzis.close(); } catch( Exception e ){}
00754                     try{ bais.close(); } catch( Exception e ){}
00755                 }   // end finally
00756 
00757             }   // end if: gzipped
00758         }   // end if: bytes.length >= 2
00759         
00760         return bytes;
00761     }   // end decode
00762 
00763 
00764     
00765 
00766     /**
00767      * Attempts to decode Base64 data and deserialize a Java
00768      * Object within. Returns <tt>null</tt> if there was an error.
00769      *
00770      * @param encodedObject The Base64 data to decode
00771      * @return The decoded and deserialized object
00772      * @since 1.5
00773      */
00774     public static Object decodeToObject( String encodedObject )
00775     {
00776         // Decode and gunzip if necessary
00777         byte[] objBytes = decode( encodedObject );
00778         
00779         java.io.ByteArrayInputStream  bais = null;
00780         java.io.ObjectInputStream     ois  = null;
00781         Object obj = null;
00782         
00783         try
00784         {
00785             bais = new java.io.ByteArrayInputStream( objBytes );
00786             ois  = new java.io.ObjectInputStream( bais );
00787         
00788             obj = ois.readObject();
00789         }   // end try
00790         catch( java.io.IOException e )
00791         {
00792             e.printStackTrace();
00793             obj = null;
00794         }   // end catch
00795         catch( java.lang.ClassNotFoundException e )
00796         {
00797             e.printStackTrace();
00798             obj = null;
00799         }   // end catch
00800         finally
00801         {
00802             try{ bais.close(); } catch( Exception e ){}
00803             try{ ois.close();  } catch( Exception e ){}
00804         }   // end finally
00805         
00806         return obj;
00807     }   // end decodeObject
00808     
00809     
00810     
00811     /**
00812      * Convenience method for encoding data to a file.
00813      *
00814      * @param dataToEncode byte array of data to encode in base64 form
00815      * @param filename Filename for saving encoded data
00816      * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
00817      *
00818      * @since 2.1
00819      */
00820     public static boolean encodeToFile( byte[] dataToEncode, String filename )
00821     {
00822         boolean success = false;
00823         Base64.OutputStream bos = null;
00824         try
00825         {
00826             bos = new Base64.OutputStream( 
00827                       new java.io.FileOutputStream( filename ), Base64.ENCODE );
00828             bos.write( dataToEncode );
00829             success = true;
00830         }   // end try
00831         catch( java.io.IOException e )
00832         {
00833             
00834             success = false;
00835         }   // end catch: IOException
00836         finally
00837         {
00838             try{ bos.close(); } catch( Exception e ){}
00839         }   // end finally
00840         
00841         return success;
00842     }   // end encodeToFile
00843     
00844     
00845     /**
00846      * Convenience method for decoding data to a file.
00847      *
00848      * @param dataToDecode Base64-encoded data as a string
00849      * @param filename Filename for saving decoded data
00850      * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
00851      *
00852      * @since 2.1
00853      */
00854     public static boolean decodeToFile( String dataToDecode, String filename )
00855     {
00856         boolean success = false;
00857         Base64.OutputStream bos = null;
00858         try
00859         {
00860                 bos = new Base64.OutputStream( 
00861                           new java.io.FileOutputStream( filename ), Base64.DECODE );
00862                 bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
00863                 success = true;
00864         }   // end try
00865         catch( java.io.IOException e )
00866         {
00867             success = false;
00868         }   // end catch: IOException
00869         finally
00870         {
00871                 try{ bos.close(); } catch( Exception e ){}
00872         }   // end finally
00873         
00874         return success;
00875     }   // end decodeToFile
00876     
00877     
00878     
00879     
00880     /**
00881      * Convenience method for reading a base64-encoded
00882      * file and decoding it.
00883      *
00884      * @param filename Filename for reading encoded data
00885      * @return decoded byte array or null if unsuccessful
00886      *
00887      * @since 2.1
00888      */
00889     public static byte[] decodeFromFile( String filename )
00890     {
00891         byte[] decodedData = null;
00892         Base64.InputStream bis = null;
00893         try
00894         {
00895             // Set up some useful variables
00896             java.io.File file = new java.io.File( filename );
00897             byte[] buffer = null;
00898             int length   = 0;
00899             int numBytes = 0;
00900             
00901             // Check for size of file
00902             if( file.length() > Integer.MAX_VALUE )
00903             {
00904                 System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." );
00905                 return null;
00906             }   // end if: file too big for int index
00907             buffer = new byte[ (int)file.length() ];
00908             
00909             // Open a stream
00910             bis = new Base64.InputStream( 
00911                       new java.io.BufferedInputStream( 
00912                       new java.io.FileInputStream( file ) ), Base64.DECODE );
00913             
00914             // Read until done
00915             while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
00916                 length += numBytes;
00917             
00918             // Save in a variable to return
00919             decodedData = new byte[ length ];
00920             System.arraycopy( buffer, 0, decodedData, 0, length );
00921             
00922         }   // end try
00923         catch( java.io.IOException e )
00924         {
00925             System.err.println( "Error decoding from file " + filename );
00926         }   // end catch: IOException
00927         finally
00928         {
00929             try{ bis.close(); } catch( Exception e) {}
00930         }   // end finally
00931         
00932         return decodedData;
00933     }   // end decodeFromFile
00934     
00935     
00936     
00937     /**
00938      * Convenience method for reading a binary file
00939      * and base64-encoding it.
00940      *
00941      * @param filename Filename for reading binary data
00942      * @return base64-encoded string or null if unsuccessful
00943      *
00944      * @since 2.1
00945      */
00946     public static String encodeFromFile( String filename )
00947     {
00948         String encodedData = null;
00949         Base64.InputStream bis = null;
00950         try
00951         {
00952             // Set up some useful variables
00953             java.io.File file = new java.io.File( filename );
00954             byte[] buffer = new byte[ (int)(file.length() * 1.4) ];
00955             int length   = 0;
00956             int numBytes = 0;
00957             
00958             // Open a stream
00959             bis = new Base64.InputStream( 
00960                       new java.io.BufferedInputStream( 
00961                       new java.io.FileInputStream( file ) ), Base64.ENCODE );
00962             
00963             // Read until done
00964             while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
00965                 length += numBytes;
00966             
00967             // Save in a variable to return
00968             encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
00969                 
00970         }   // end try
00971         catch( java.io.IOException e )
00972         {
00973             System.err.println( "Error encoding from file " + filename );
00974         }   // end catch: IOException
00975         finally
00976         {
00977             try{ bis.close(); } catch( Exception e) {}
00978         }   // end finally
00979         
00980         return encodedData;
00981         }   // end encodeFromFile
00982     
00983     
00984     
00985     
00986     /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
00987     
00988     
00989     
00990     /**
00991      * A {@link Base64.InputStream} will read data from another
00992      * <tt>java.io.InputStream</tt>, given in the constructor,
00993      * and encode/decode to/from Base64 notation on the fly.
00994      *
00995      * @see Base64
00996      * @since 1.3
00997      */
00998     public static class InputStream extends java.io.FilterInputStream
00999     {
01000         private boolean encode;         // Encoding or decoding
01001         private int     position;       // Current position in the buffer
01002         private byte[]  buffer;         // Small buffer holding converted data
01003         private int     bufferLength;   // Length of buffer (3 or 4)
01004         private int     numSigBytes;    // Number of meaningful bytes in the buffer
01005         private int     lineLength;
01006         private boolean breakLines;     // Break lines at less than 80 characters
01007         
01008         
01009         /**
01010          * Constructs a {@link Base64.InputStream} in DECODE mode.
01011          *
01012          * @param in the <tt>java.io.InputStream</tt> from which to read data.
01013          * @since 1.3
01014          */
01015         public InputStream( java.io.InputStream in )
01016         {   
01017             this( in, DECODE );
01018         }   // end constructor
01019         
01020         
01021         /**
01022          * Constructs a {@link Base64.InputStream} in
01023          * either ENCODE or DECODE mode.
01024          * <p>
01025          * Valid options:<pre>
01026          *   ENCODE or DECODE: Encode or Decode as data is read.
01027          *   DONT_BREAK_LINES: don't break lines at 76 characters
01028          *     (only meaningful when encoding)
01029          *     <i>Note: Technically, this makes your encoding non-compliant.</i>
01030          * </pre>
01031          * <p>
01032          * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
01033          *
01034          *
01035          * @param in the <tt>java.io.InputStream</tt> from which to read data.
01036          * @param options Specified options
01037          * @see Base64#ENCODE
01038          * @see Base64#DECODE
01039          * @see Base64#DONT_BREAK_LINES
01040          * @since 2.0
01041          */
01042         public InputStream( java.io.InputStream in, int options )
01043         {   
01044             super( in );
01045             this.breakLines   = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
01046             this.encode       = (options & ENCODE) == ENCODE;
01047             this.bufferLength = encode ? 4 : 3;
01048             this.buffer   = new byte[ bufferLength ];
01049             this.position = -1;
01050             this.lineLength = 0;
01051         }   // end constructor
01052         
01053         /**
01054          * Reads enough of the input stream to convert
01055          * to/from Base64 and returns the next byte.
01056          *
01057          * @return next byte
01058          * @since 1.3
01059          */
01060         public int read() throws java.io.IOException 
01061         { 
01062             // Do we need to get data?
01063             if( position < 0 )
01064             {
01065                 if( encode )
01066                 {
01067                     byte[] b3 = new byte[3];
01068                     int numBinaryBytes = 0;
01069                     for( int i = 0; i < 3; i++ )
01070                     {
01071                         try
01072                         { 
01073                             int b = in.read();
01074                             
01075                             // If end of stream, b is -1.
01076                             if( b >= 0 )
01077                             {
01078                                 b3[i] = (byte)b;
01079                                 numBinaryBytes++;
01080                             }   // end if: not end of stream
01081                             
01082                         }   // end try: read
01083                         catch( java.io.IOException e )
01084                         {   
01085                             // Only a problem if we got no data at all.
01086                             if( i == 0 )
01087                                 throw e;
01088                             
01089                         }   // end catch
01090                     }   // end for: each needed input byte
01091                     
01092                     if( numBinaryBytes > 0 )
01093                     {
01094                         encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
01095                         position = 0;
01096                         numSigBytes = 4;
01097                     }   // end if: got data
01098                     else
01099                     {
01100                         return -1;
01101                     }   // end else
01102                 }   // end if: encoding
01103                 
01104                 // Else decoding
01105                 else
01106                 {
01107                     byte[] b4 = new byte[4];
01108                     int i = 0;
01109                     for( i = 0; i < 4; i++ )
01110                     {
01111                         // Read four "meaningful" bytes:
01112                         int b = 0;
01113                         do{ b = in.read(); }
01114                         while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
01115                         
01116                         if( b < 0 )
01117                             break; // Reads a -1 if end of stream
01118                         
01119                         b4[i] = (byte)b;
01120                     }   // end for: each needed input byte
01121                     
01122                     if( i == 4 )
01123                     {
01124                         numSigBytes = decode4to3( b4, 0, buffer, 0 );
01125                         position = 0;
01126                     }   // end if: got four characters
01127                     else if( i == 0 ){
01128                         return -1;
01129                     }   // end else if: also padded correctly
01130                     else
01131                     {
01132                         // Must have broken out from above.
01133                         throw new java.io.IOException( "Improperly padded Base64 input." );
01134                     }   // end 
01135                     
01136                 }   // end else: decode
01137             }   // end else: get data
01138             
01139             // Got data?
01140             if( position >= 0 )
01141             {
01142                 // End of relevant data?
01143                 if( /*!encode &&*/ position >= numSigBytes )
01144                     return -1;
01145                 
01146                 if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
01147                 {
01148                     lineLength = 0;
01149                     return '\n';
01150                 }   // end if
01151                 else
01152                 {
01153                     lineLength++;   // This isn't important when decoding
01154                                     // but throwing an extra "if" seems
01155                                     // just as wasteful.
01156                     
01157                     int b = buffer[ position++ ];
01158 
01159                     if( position >= bufferLength )
01160                         position = -1;
01161 
01162                     return b & 0xFF; // This is how you "cast" a byte that's
01163                                      // intended to be unsigned.
01164                 }   // end else
01165             }   // end if: position >= 0
01166             
01167             // Else error
01168             else
01169             {   
01170                 // When JDK1.4 is more accepted, use an assertion here.
01171                 throw new java.io.IOException( "Error in Base64 code reading stream." );
01172             }   // end else
01173         }   // end read
01174         
01175         
01176         /**
01177          * Calls {@link #read()} repeatedly until the end of stream
01178          * is reached or <var>len</var> bytes are read.
01179          * Returns number of bytes read into array or -1 if
01180          * end of stream is encountered.
01181          *
01182          * @param dest array to hold values
01183          * @param off offset for array
01184          * @param len max number of bytes to read into array
01185          * @return bytes read into array or -1 if end of stream is encountered.
01186          * @since 1.3
01187          */
01188         public int read( byte[] dest, int off, int len ) throws java.io.IOException
01189         {
01190             int i;
01191             int b;
01192             for( i = 0; i < len; i++ )
01193             {
01194                 b = read();
01195                 
01196                 //if( b < 0 && i == 0 )
01197                 //    return -1;
01198                 
01199                 if( b >= 0 )
01200                     dest[off + i] = (byte)b;
01201                 else if( i == 0 )
01202                     return -1;
01203                 else
01204                     break; // Out of 'for' loop
01205             }   // end for: each byte read
01206             return i;
01207         }   // end read
01208         
01209     }   // end inner class InputStream
01210     
01211     
01212     
01213     
01214     
01215     
01216     /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
01217     
01218     
01219     
01220     /**
01221      * A {@link Base64.OutputStream} will write data to another
01222      * <tt>java.io.OutputStream</tt>, given in the constructor,
01223      * and encode/decode to/from Base64 notation on the fly.
01224      *
01225      * @see Base64
01226      * @since 1.3
01227      */
01228     public static class OutputStream extends java.io.FilterOutputStream
01229     {
01230         private boolean encode;
01231         private int     position;
01232         private byte[]  buffer;
01233         private int     bufferLength;
01234         private int     lineLength;
01235         private boolean breakLines;
01236         private byte[]  b4; // Scratch used in a few places
01237         private boolean suspendEncoding;
01238         
01239         /**
01240          * Constructs a {@link Base64.OutputStream} in ENCODE mode.
01241          *
01242          * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
01243          * @since 1.3
01244          */
01245         public OutputStream( java.io.OutputStream out )
01246         {   
01247             this( out, ENCODE );
01248         }   // end constructor
01249         
01250         
01251         /**
01252          * Constructs a {@link Base64.OutputStream} in
01253          * either ENCODE or DECODE mode.
01254          * <p>
01255          * Valid options:<pre>
01256          *   ENCODE or DECODE: Encode or Decode as data is read.
01257          *   DONT_BREAK_LINES: don't break lines at 76 characters
01258          *     (only meaningful when encoding)
01259          *     <i>Note: Technically, this makes your encoding non-compliant.</i>
01260          * </pre>
01261          * <p>
01262          * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
01263          *
01264          * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
01265          * @param options Specified options.
01266          * @see Base64#ENCODE
01267          * @see Base64#DECODE
01268          * @see Base64#DONT_BREAK_LINES
01269          * @since 1.3
01270          */
01271         public OutputStream( java.io.OutputStream out, int options )
01272         {   
01273             super( out );
01274             this.breakLines   = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
01275             this.encode       = (options & ENCODE) == ENCODE;
01276             this.bufferLength = encode ? 3 : 4;
01277             this.buffer       = new byte[ bufferLength ];
01278             this.position     = 0;
01279             this.lineLength   = 0;
01280             this.suspendEncoding = false;
01281             this.b4           = new byte[4];
01282         }   // end constructor
01283         
01284         
01285         /**
01286          * Writes the byte to the output stream after
01287          * converting to/from Base64 notation.
01288          * When encoding, bytes are buffered three
01289          * at a time before the output stream actually
01290          * gets a write() call.
01291          * When decoding, bytes are buffered four
01292          * at a time.
01293          *
01294          * @param theByte the byte to write
01295          * @since 1.3
01296          */
01297         public void write(int theByte) throws java.io.IOException
01298         {
01299             // Encoding suspended?
01300             if( suspendEncoding )
01301             {
01302                 super.out.write( theByte );
01303                 return;
01304             }   // end if: supsended
01305             
01306             // Encode?
01307             if( encode )
01308             {
01309                 buffer[ position++ ] = (byte)theByte;
01310                 if( position >= bufferLength )  // Enough to encode.
01311                 {
01312                     out.write( encode3to4( b4, buffer, bufferLength ) );
01313 
01314                     lineLength += 4;
01315                     if( breakLines && lineLength >= MAX_LINE_LENGTH )
01316                     {
01317                         out.write( NEW_LINE );
01318                         lineLength = 0;
01319                     }   // end if: end of line
01320 
01321                     position = 0;
01322                 }   // end if: enough to output
01323             }   // end if: encoding
01324 
01325             // Else, Decoding
01326             else
01327             {
01328                 // Meaningful Base64 character?
01329                 if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
01330                 {
01331                     buffer[ position++ ] = (byte)theByte;
01332                     if( position >= bufferLength )  // Enough to output.
01333                     {
01334                         int len = Base64.decode4to3( buffer, 0, b4, 0 );
01335                         out.write( b4, 0, len );
01336                         //out.write( Base64.decode4to3( buffer ) );
01337                         position = 0;
01338                     }   // end if: enough to output
01339                 }   // end if: meaningful base64 character
01340                 else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
01341                 {
01342                     throw new java.io.IOException( "Invalid character in Base64 data." );
01343                 }   // end else: not white space either
01344             }   // end else: decoding
01345         }   // end write
01346         
01347         
01348         
01349         /**
01350          * Calls {@link #write(int)} repeatedly until <var>len</var> 
01351          * bytes are written.
01352          *
01353          * @param theBytes array from which to read bytes
01354          * @param off offset for array
01355          * @param len max number of bytes to read into array
01356          * @since 1.3
01357          */
01358         public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
01359         {
01360             // Encoding suspended?
01361             if( suspendEncoding )
01362             {
01363                 super.out.write( theBytes, off, len );
01364                 return;
01365             }   // end if: supsended
01366             
01367             for( int i = 0; i < len; i++ )
01368             {
01369                 write( theBytes[ off + i ] );
01370             }   // end for: each byte written
01371             
01372         }   // end write
01373         
01374         
01375         
01376         /**
01377          * Method added by PHIL. [Thanks, PHIL. -Rob]
01378          * This pads the buffer without closing the stream.
01379          */
01380         public void flushBase64() throws java.io.IOException 
01381         {
01382             if( position > 0 )
01383             {
01384                 if( encode )
01385                 {
01386                     out.write( encode3to4( b4, buffer, position ) );
01387                     position = 0;
01388                 }   // end if: encoding
01389                 else
01390                 {
01391                     throw new java.io.IOException( "Base64 input not properly padded." );
01392                 }   // end else: decoding
01393             }   // end if: buffer partially full
01394 
01395         }   // end flush
01396 
01397         
01398         /** 
01399          * Flushes and closes (I think, in the superclass) the stream. 
01400          *
01401          * @since 1.3
01402          */
01403         public void close() throws java.io.IOException
01404         {
01405             // 1. Ensure that pending characters are written
01406             flushBase64();
01407 
01408             // 2. Actually close the stream
01409             // Base class both flushes and closes.
01410             super.close();
01411             
01412             buffer = null;
01413             out    = null;
01414         }   // end close
01415         
01416         
01417         
01418         /**
01419          * Suspends encoding of the stream.
01420          * May be helpful if you need to embed a piece of
01421          * base640-encoded data in a stream.
01422          *
01423          * @since 1.5.1
01424          */
01425         public void suspendEncoding() throws java.io.IOException 
01426         {
01427             flushBase64();
01428             this.suspendEncoding = true;
01429         }   // end suspendEncoding
01430         
01431         
01432         /**
01433          * Resumes encoding of the stream.
01434          * May be helpful if you need to embed a piece of
01435          * base640-encoded data in a stream.
01436          *
01437          * @since 1.5.1
01438          */
01439         public void resumeEncoding()
01440         {
01441             this.suspendEncoding = false;
01442         }   // end resumeEncoding
01443         
01444         
01445         
01446     }   // end inner class OutputStream
01447     
01448     
01449 }   // end class Base64

Generated on Mon Mar 6 23:34:34 2006 by  doxygen 1.4.4