View Javadoc

    1   package org.codehaus.aspectwerkz.util;
    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.2 - 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.2
    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                      | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
    256                      | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
    257
    258         switch (numSigBytes) {
    259             case 3:
    260                 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
    261                 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
    262                 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
    263                 destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
    264                 return destination;
    265
    266             case 2:
    267                 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
    268                 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
    269                 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
    270                 destination[destOffset + 3] = EQUALS_SIGN;
    271                 return destination;
    272
    273             case 1:
    274                 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
    275                 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
    276                 destination[destOffset + 2] = EQUALS_SIGN;
    277                 destination[destOffset + 3] = EQUALS_SIGN;
    278                 return destination;
    279
    280             default:
    281                 return destination;
    282         }   // end switch
    283     }   // end encode3to4
    284
    285
    286     /***
    287      * Serializes an object and returns the Base64-encoded
    288      * version of that serialized object. If the object
    289      * cannot be serialized or there is another error,
    290      * the method will return <tt>null</tt>.
    291      * The object is not GZip-compressed before being encoded.
    292      *
    293      * @param serializableObject The object to encode
    294      * @return The Base64-encoded object
    295      * @since 1.4
    296      */
    297     public static String encodeObject(java.io.Serializable serializableObject) {
    298         return encodeObject(serializableObject, NO_OPTIONS);
    299     }   // end encodeObject
    300
    301
    302     /***
    303      * Serializes an object and returns the Base64-encoded
    304      * version of that serialized object. If the object
    305      * cannot be serialized or there is another error,
    306      * the method will return <tt>null</tt>.
    307      * <p/>
    308      * Valid options:<pre>
    309      *   GZIP: gzip-compresses object before encoding it.
    310      *   DONT_BREAK_LINES: don't break lines at 76 characters
    311      *     <i>Note: Technically, this makes your encoding non-compliant.</i>
    312      * </pre>
    313      * <p/>
    314      * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
    315      * <p/>
    316      * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
    317      *
    318      * @param serializableObject The object to encode
    319      * @param options            Specified options
    320      * @return The Base64-encoded object
    321      * @see Base64#GZIP
    322      * @see Base64#DONT_BREAK_LINES
    323      * @since 2.0
    324      */
    325     public static String encodeObject(java.io.Serializable serializableObject, int options) {
    326         // Streams
    327         java.io.ByteArrayOutputStream baos = null;
    328         java.io.OutputStream b64os = null;
    329         java.io.ObjectOutputStream oos = null;
    330         java.util.zip.GZIPOutputStream gzos = null;
    331
    332         // Isolate options
    333         int gzip = (options & GZIP);
    334         int dontBreakLines = (options & DONT_BREAK_LINES);
    335
    336         try {
    337             // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
    338             baos = new java.io.ByteArrayOutputStream();
    339             b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
    340
    341             // GZip?
    342             if (gzip == GZIP) {
    343                 gzos = new java.util.zip.GZIPOutputStream(b64os);
    344                 oos = new java.io.ObjectOutputStream(gzos);
    345             }   // end if: gzip
    346             else {
    347                 oos = new java.io.ObjectOutputStream(b64os);
    348             }
    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                                       + ((len % 3) > 0 ? 4 : 0)      // Account for padding
    514                                       + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
    515             int d = 0;
    516             int e = 0;
    517             int len2 = len - 2;
    518             int lineLength = 0;
    519             for (; d < len2; d += 3, e += 4) {
    520                 encode3to4(source, d + off, 3, outBuff, e);
    521
    522                 lineLength += 4;
    523                 if (breakLines && lineLength == MAX_LINE_LENGTH) {
    524                     outBuff[e + 4] = NEW_LINE;
    525                     e++;
    526                     lineLength = 0;
    527                 }   // end if: end of line
    528             }   // en dfor: each piece of array
    529
    530             if (d < len) {
    531                 encode3to4(source, d + off, len - d, outBuff, e);
    532                 e += 4;
    533             }   // end if: some padding needed
    534
    535
    536             // Return value according to relevant encoding.
    537             try {
    538                 return new String(outBuff, 0, e, PREFERRED_ENCODING);
    539             }   // end try
    540             catch (java.io.UnsupportedEncodingException uue) {
    541                 return new String(outBuff, 0, e);
    542             }   // end catch
    543
    544         }   // end else: don't compress
    545
    546     }   // end encodeBytes
    547
    548
    549
    550
    551
    552 /* ********  D E C O D I N G   M E T H O D S  ******** */
    553
    554
    555     /***
    556      * Decodes four bytes from array <var>source</var>
    557      * and writes the resulting bytes (up to three of them)
    558      * to <var>destination</var>.
    559      * The source and destination arrays can be manipulated
    560      * anywhere along their length by specifying
    561      * <var>srcOffset</var> and <var>destOffset</var>.
    562      * This method does not check to make sure your arrays
    563      * are large enough to accomodate <var>srcOffset</var> + 4 for
    564      * the <var>source</var> array or <var>destOffset</var> + 3 for
    565      * the <var>destination</var> array.
    566      * This method returns the actual number of bytes that
    567      * were converted from the Base64 encoding.
    568      *
    569      * @param source      the array to convert
    570      * @param srcOffset   the index where conversion begins
    571      * @param destination the array to hold the conversion
    572      * @param destOffset  the index where output will be put
    573      * @return the number of decoded bytes converted
    574      * @since 1.3
    575      */
    576     private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset) {
    577         // Example: Dk==
    578         if (source[srcOffset + 2] == EQUALS_SIGN) {
    579             // Two ways to do the same thing. Don't know which way I like best.
    580             //int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] << 24 ) >>>  6 )
    581             //              | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
    582             int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
    583                           | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
    584
    585             destination[destOffset] = (byte) (outBuff >>> 16);
    586             return 1;
    587         }
    588
    589         // Example: DkL=
    590         else if (source[srcOffset + 3] == EQUALS_SIGN) {
    591             // Two ways to do the same thing. Don't know which way I like best.
    592             //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
    593             //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
    594             //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
    595             int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
    596                           | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
    597                           | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
    598
    599             destination[destOffset] = (byte) (outBuff >>> 16);
    600             destination[destOffset + 1] = (byte) (outBuff >>> 8);
    601             return 2;
    602         }
    603
    604         // Example: DkLE
    605         else {
    606             try {
    607                 // Two ways to do the same thing. Don't know which way I like best.
    608                 //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
    609                 //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
    610                 //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
    611                 //              | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
    612                 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
    613                               | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
    614                               | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
    615                               | ((DECODABET[source[srcOffset + 3]] & 0xFF));
    616
    617
    618                 destination[destOffset] = (byte) (outBuff >> 16);
    619                 destination[destOffset + 1] = (byte) (outBuff >> 8);
    620                 destination[destOffset + 2] = (byte) (outBuff);
    621
    622                 return 3;
    623             } catch (Exception e) {
    624                 System.out.println("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]]));
    625                 System.out.println("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]]));
    626                 System.out.println("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]]));
    627                 System.out.println("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]]));
    628                 return -1;
    629             }   //e nd catch
    630         }
    631     }   // end decodeToBytes
    632
    633
    634     /***
    635      * Very low-level access to decoding ASCII characters in
    636      * the form of a byte array. Does not support automatically
    637      * gunzipping or any other "fancy" features.
    638      *
    639      * @param source The Base64 encoded data
    640      * @param off    The offset of where to begin decoding
    641      * @param len    The length of characters to decode
    642      * @return decoded data
    643      * @since 1.3
    644      */
    645     public static byte[] decode(byte[] source, int off, int len) {
    646         int len34 = len * 3 / 4;
    647         byte[] outBuff = new byte[len34]; // Upper limit on size of output
    648         int outBuffPosn = 0;
    649
    650         byte[] b4 = new byte[4];
    651         int b4Posn = 0;
    652         int i = 0;
    653         byte sbiCrop = 0;
    654         byte sbiDecode = 0;
    655         for (i = off; i < off + len; i++) {
    656             sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
    657             sbiDecode = DECODABET[sbiCrop];
    658
    659             if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or better
    660             {
    661                 if (sbiDecode >= EQUALS_SIGN_ENC) {
    662                     b4[b4Posn++] = sbiCrop;
    663                     if (b4Posn > 3) {
    664                         outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
    665                         b4Posn = 0;
    666
    667                         // If that was the equals sign, break out of 'for' loop
    668                         if (sbiCrop == EQUALS_SIGN) {
    669                             break;
    670                         }
    671                     }   // end if: quartet built
    672
    673                 }   // end if: equals sign or better
    674
    675             }   // end if: white space, equals sign or better
    676             else {
    677                 System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)");
    678                 return null;
    679             }   // end else:
    680         }   // each input character
    681
    682         byte[] out = new byte[outBuffPosn];
    683         System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
    684         return out;
    685     }   // end decode
    686
    687
    688     /***
    689      * Decodes data from Base64 notation, automatically
    690      * detecting gzip-compressed data and decompressing it.
    691      *
    692      * @param s the string to decode
    693      * @return the decoded data
    694      * @since 1.4
    695      */
    696     public static byte[] decode(String s) {
    697         byte[] bytes;
    698         try {
    699             bytes = s.getBytes(PREFERRED_ENCODING);
    700         }   // end try
    701         catch (java.io.UnsupportedEncodingException uee) {
    702             bytes = s.getBytes();
    703         }   // end catch
    704         //</change>
    705
    706         // Decode
    707         bytes = decode(bytes, 0, bytes.length);
    708
    709
    710         // Check to see if it's gzip-compressed
    711         // GZIP Magic Two-Byte Number: 0x8b1f (35615)
    712         if (bytes != null && bytes.length >= 4) {
    713
    714             int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
    715             if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
    716                 java.io.ByteArrayInputStream bais = null;
    717                 java.util.zip.GZIPInputStream gzis = null;
    718                 java.io.ByteArrayOutputStream baos = null;
    719                 byte[] buffer = new byte[2048];
    720                 int length = 0;
    721
    722                 try {
    723                     baos = new java.io.ByteArrayOutputStream();
    724                     bais = new java.io.ByteArrayInputStream(bytes);
    725                     gzis = new java.util.zip.GZIPInputStream(bais);
    726
    727                     while ((length = gzis.read(buffer)) >= 0) {
    728                         baos.write(buffer, 0, length);
    729                     }   // end while: reading input
    730
    731                     // No error? Get new bytes.
    732                     bytes = baos.toByteArray();
    733
    734                 }   // end try
    735                 catch (java.io.IOException e) {
    736                     // Just return originally-decoded bytes
    737                 }   // end catch
    738                 finally {
    739                     try {
    740                         baos.close();
    741                     } catch (Exception e) {
    742                     }
    743                     try {
    744                         gzis.close();
    745                     } catch (Exception e) {
    746                     }
    747                     try {
    748                         bais.close();
    749                     } catch (Exception e) {
    750                     }
    751                 }   // end finally
    752
    753             }   // end if: gzipped
    754         }   // end if: bytes.length >= 2
    755
    756         return bytes;
    757     }   // end decode
    758
    759
    760     /***
    761      * Attempts to decode Base64 data and deserialize a Java
    762      * Object within. Returns <tt>null</tt> if there was an error.
    763      *
    764      * @param encodedObject The Base64 data to decode
    765      * @return The decoded and deserialized object
    766      * @since 1.5
    767      */
    768     public static Object decodeToObject(String encodedObject) {
    769         // Decode and gunzip if necessary
    770         byte[] objBytes = decode(encodedObject);
    771
    772         java.io.ByteArrayInputStream bais = null;
    773         java.io.ObjectInputStream ois = null;
    774         Object obj = null;
    775
    776         try {
    777             bais = new java.io.ByteArrayInputStream(objBytes);
    778             ois = new java.io.ObjectInputStream(bais);
    779
    780             obj = ois.readObject();
    781         }   // end try
    782         catch (java.io.IOException e) {
    783             e.printStackTrace();
    784             obj = null;
    785         }   // end catch
    786         catch (java.lang.ClassNotFoundException e) {
    787             e.printStackTrace();
    788             obj = null;
    789         }   // end catch
    790         finally {
    791             try {
    792                 bais.close();
    793             } catch (Exception e) {
    794             }
    795             try {
    796                 ois.close();
    797             } catch (Exception e) {
    798             }
    799         }   // end finally
    800
    801         return obj;
    802     }   // end decodeObject
    803
    804
    805     /***
    806      * Convenience method for encoding data to a file.
    807      *
    808      * @param dataToEncode byte array of data to encode in base64 form
    809      * @param filename     Filename for saving encoded data
    810      * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
    811      * @since 2.1
    812      */
    813     public static boolean encodeToFile(byte[] dataToEncode, String filename) {
    814         boolean success = false;
    815         Base64.OutputStream bos = null;
    816         try {
    817             bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
    818             bos.write(dataToEncode);
    819             success = true;
    820         }   // end try
    821         catch (java.io.IOException e) {
    822
    823             success = false;
    824         }   // end catch: IOException
    825         finally {
    826             try {
    827                 bos.close();
    828             } catch (Exception e) {
    829             }
    830         }   // end finally
    831
    832         return success;
    833     }   // end encodeToFile
    834
    835
    836     /***
    837      * Convenience method for decoding data to a file.
    838      *
    839      * @param dataToDecode Base64-encoded data as a string
    840      * @param filename     Filename for saving decoded data
    841      * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
    842      * @since 2.1
    843      */
    844     public static boolean decodeToFile(String dataToDecode, String filename) {
    845         boolean success = false;
    846         Base64.OutputStream bos = null;
    847         try {
    848             bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
    849             bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
    850             success = true;
    851         }   // end try
    852         catch (java.io.IOException e) {
    853             success = false;
    854         }   // end catch: IOException
    855         finally {
    856             try {
    857                 bos.close();
    858             } catch (Exception e) {
    859             }
    860         }   // end finally
    861
    862         return success;
    863     }   // end decodeToFile
    864
    865
    866     /***
    867      * Convenience method for reading a base64-encoded
    868      * file and decoding it.
    869      *
    870      * @param filename Filename for reading encoded data
    871      * @return decoded byte array or null if unsuccessful
    872      * @since 2.1
    873      */
    874     public static byte[] decodeFromFile(String filename) {
    875         byte[] decodedData = null;
    876         Base64.InputStream bis = null;
    877         try {
    878             // Set up some useful variables
    879             java.io.File file = new java.io.File(filename);
    880             byte[] buffer = null;
    881             int length = 0;
    882             int numBytes = 0;
    883
    884             // Check for size of file
    885             if (file.length() > Integer.MAX_VALUE) {
    886                 System.err.println("File is too big for this convenience method (" + file.length() + " bytes).");
    887                 return null;
    888             }   // end if: file too big for int index
    889             buffer = new byte[(int) file.length()];
    890
    891             // Open a stream
    892             bis = new Base64.InputStream(
    893                     new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.DECODE
    894             );
    895
    896             // Read until done
    897             while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
    898                 length += numBytes;
    899             }
    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(
    940                     new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.ENCODE
    941             );
    942
    943             // Read until done
    944             while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
    945                 length += numBytes;
    946             }
    947
    948             // Save in a variable to return
    949             encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
    950
    951         }   // end try
    952         catch (java.io.IOException e) {
    953             System.err.println("Error encoding from file " + filename);
    954         }   // end catch: IOException
    955         finally {
    956             try {
    957                 bis.close();
    958             } catch (Exception e) {
    959             }
    960         }   // end finally
    961
    962         return encodedData;
    963     }   // end encodeFromFile
    964
    965
    966
    967
    968     /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
    969
    970
    971
    972     /***
    973      * A {@link Base64.InputStream} will read data from another
    974      * <tt>java.io.InputStream</tt>, given in the constructor,
    975      * and encode/decode to/from Base64 notation on the fly.
    976      *
    977      * @see Base64
    978      * @since 1.3
    979      */
    980     public static class InputStream extends java.io.FilterInputStream {
    981         private boolean encode;         // Encoding or decoding
    982         private int position;       // Current position in the buffer
    983         private byte[] buffer;         // Small buffer holding converted data
    984         private int bufferLength;   // Length of buffer (3 or 4)
    985         private int numSigBytes;    // Number of meaningful bytes in the buffer
    986         private int lineLength;
    987         private boolean breakLines;     // Break lines at less than 80 characters
    988
    989
    990         /***
    991          * Constructs a {@link Base64.InputStream} in DECODE mode.
    992          *
    993          * @param in the <tt>java.io.InputStream</tt> from which to read data.
    994          * @since 1.3
    995          */
    996         public InputStream(java.io.InputStream in) {
    997             this(in, DECODE);
    998         }   // end constructor
    999
    1000
    1001         /***
    1002          * Constructs a {@link Base64.InputStream} in
    1003          * either ENCODE or DECODE mode.
    1004          * <p/>
    1005          * Valid options:<pre>
    1006          *   ENCODE or DECODE: Encode or Decode as data is read.
    1007          *   DONT_BREAK_LINES: don't break lines at 76 characters
    1008          *     (only meaningful when encoding)
    1009          *     <i>Note: Technically, this makes your encoding non-compliant.</i>
    1010          * </pre>
    1011          * <p/>
    1012          * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
    1013          *
    1014          * @param in      the <tt>java.io.InputStream</tt> from which to read data.
    1015          * @param options Specified options
    1016          * @see Base64#ENCODE
    1017          * @see Base64#DECODE
    1018          * @see Base64#DONT_BREAK_LINES
    1019          * @since 2.0
    1020          */
    1021         public InputStream(java.io.InputStream in, int options) {
    1022             super(in);
    1023             this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
    1024             this.encode = (options & ENCODE) == ENCODE;
    1025             this.bufferLength = encode ? 4 : 3;
    1026             this.buffer = new byte[bufferLength];
    1027             this.position = -1;
    1028             this.lineLength = 0;
    1029         }   // end constructor
    1030
    1031         /***
    1032          * Reads enough of the input stream to convert
    1033          * to/from Base64 and returns the next byte.
    1034          *
    1035          * @return next byte
    1036          * @since 1.3
    1037          */
    1038         public int read() throws java.io.IOException {
    1039             // Do we need to get data?
    1040             if (position < 0) {
    1041                 if (encode) {
    1042                     byte[] b3 = new byte[3];
    1043                     int numBinaryBytes = 0;
    1044                     for (int i = 0; i < 3; i++) {
    1045                         try {
    1046                             int b = in.read();
    1047
    1048                             // If end of stream, b is -1.
    1049                             if (b >= 0) {
    1050                                 b3[i] = (byte) b;
    1051                                 numBinaryBytes++;
    1052                             }   // end if: not end of stream
    1053
    1054                         }   // end try: read
    1055                         catch (java.io.IOException e) {
    1056                             // Only a problem if we got no data at all.
    1057                             if (i == 0) {
    1058                                 throw e;
    1059                             }
    1060
    1061                         }   // end catch
    1062                     }   // end for: each needed input byte
    1063
    1064                     if (numBinaryBytes > 0) {
    1065                         encode3to4(b3, 0, numBinaryBytes, buffer, 0);
    1066                         position = 0;
    1067                         numSigBytes = 4;
    1068                     }   // end if: got data
    1069                     else {
    1070                         return -1;
    1071                     }   // end else
    1072                 }   // end if: encoding
    1073
    1074                 // Else decoding
    1075                 else {
    1076                     byte[] b4 = new byte[4];
    1077                     int i = 0;
    1078                     for (i = 0; i < 4; i++) {
    1079                         // Read four "meaningful" bytes:
    1080                         int b = 0;
    1081                         do {
    1082                             b = in.read();
    1083                         } while (b >= 0 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
    1084
    1085                         if (b < 0) {
    1086                             break; // Reads a -1 if end of stream
    1087                         }
    1088
    1089                         b4[i] = (byte) b;
    1090                     }   // end for: each needed input byte
    1091
    1092                     if (i == 4) {
    1093                         numSigBytes = decode4to3(b4, 0, buffer, 0);
    1094                         position = 0;
    1095                     }   // end if: got four characters
    1096                     else if (i == 0) {
    1097                         return -1;
    1098                     }   // end else if: also padded correctly
    1099                     else {
    1100                         // Must have broken out from above.
    1101                         throw new java.io.IOException("Improperly padded Base64 input.");
    1102                     }   // end
    1103
    1104                 }   // end else: decode
    1105             }   // end else: get data
    1106
    1107             // Got data?
    1108             if (position >= 0) {
    1109                 // End of relevant data?
    1110                 if (/*!encode &&*/ position >= numSigBytes) {
    1111                     return -1;
    1112                 }
    1113
    1114                 if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
    1115                     lineLength = 0;
    1116                     return '\n';
    1117                 }   // end if
    1118                 else {
    1119                     lineLength++;   // This isn't important when decoding
    1120                     // but throwing an extra "if" seems
    1121                     // just as wasteful.
    1122
    1123                     int b = buffer[position++];
    1124
    1125                     if (position >= bufferLength) {
    1126                         position = -1;
    1127                     }
    1128
    1129                     return b & 0xFF; // This is how you "cast" a byte that's
    1130                     // intended to be unsigned.
    1131                 }   // end else
    1132             }   // end if: position >= 0
    1133
    1134             // Else error
    1135             else {
    1136                 // When JDK1.4 is more accepted, use an assertion here.
    1137                 throw new java.io.IOException("Error in Base64 code reading stream.");
    1138             }   // end else
    1139         }   // end read
    1140
    1141
    1142         /***
    1143          * Calls {@link #read()} repeatedly until the end of stream
    1144          * is reached or <var>len</var> bytes are read.
    1145          * Returns number of bytes read into array or -1 if
    1146          * end of stream is encountered.
    1147          *
    1148          * @param dest array to hold values
    1149          * @param off  offset for array
    1150          * @param len  max number of bytes to read into array
    1151          * @return bytes read into array or -1 if end of stream is encountered.
    1152          * @since 1.3
    1153          */
    1154         public int read(byte[] dest, int off, int len) throws java.io.IOException {
    1155             int i;
    1156             int b;
    1157             for (i = 0; i < len; i++) {
    1158                 b = read();
    1159
    1160                 //if( b < 0 && i == 0 )
    1161                 //    return -1;
    1162
    1163                 if (b >= 0) {
    1164                     dest[off + i] = (byte) b;
    1165                 } else if (i == 0) {
    1166                     return -1;
    1167                 } else {
    1168                     break; // Out of 'for' loop
    1169                 }
    1170             }   // end for: each byte read
    1171             return i;
    1172         }   // end read
    1173
    1174     }   // end inner class InputStream
    1175
    1176
    1177
    1178
    1179
    1180
    1181     /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
    1182
    1183
    1184
    1185     /***
    1186      * A {@link Base64.OutputStream} will write data to another
    1187      * <tt>java.io.OutputStream</tt>, given in the constructor,
    1188      * and encode/decode to/from Base64 notation on the fly.
    1189      *
    1190      * @see Base64
    1191      * @since 1.3
    1192      */
    1193     public static class OutputStream extends java.io.FilterOutputStream {
    1194         private boolean encode;
    1195         private int position;
    1196         private byte[] buffer;
    1197         private int bufferLength;
    1198         private int lineLength;
    1199         private boolean breakLines;
    1200         private byte[] b4; // Scratch used in a few places
    1201         private boolean suspendEncoding;
    1202
    1203         /***
    1204          * Constructs a {@link Base64.OutputStream} in ENCODE mode.
    1205          *
    1206          * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
    1207          * @since 1.3
    1208          */
    1209         public OutputStream(java.io.OutputStream out) {
    1210             this(out, ENCODE);
    1211         }   // end constructor
    1212
    1213
    1214         /***
    1215          * Constructs a {@link Base64.OutputStream} in
    1216          * either ENCODE or DECODE mode.
    1217          * <p/>
    1218          * Valid options:<pre>
    1219          *   ENCODE or DECODE: Encode or Decode as data is read.
    1220          *   DONT_BREAK_LINES: don't break lines at 76 characters
    1221          *     (only meaningful when encoding)
    1222          *     <i>Note: Technically, this makes your encoding non-compliant.</i>
    1223          * </pre>
    1224          * <p/>
    1225          * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
    1226          *
    1227          * @param out     the <tt>java.io.OutputStream</tt> to which data will be written.
    1228          * @param options Specified options.
    1229          * @see Base64#ENCODE
    1230          * @see Base64#DECODE
    1231          * @see Base64#DONT_BREAK_LINES
    1232          * @since 1.3
    1233          */
    1234         public OutputStream(java.io.OutputStream out, int options) {
    1235             super(out);
    1236             this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
    1237             this.encode = (options & ENCODE) == ENCODE;
    1238             this.bufferLength = encode ? 3 : 4;
    1239             this.buffer = new byte[bufferLength];
    1240             this.position = 0;
    1241             this.lineLength = 0;
    1242             this.suspendEncoding = false;
    1243             this.b4 = new byte[4];
    1244         }   // end constructor
    1245
    1246
    1247         /***
    1248          * Writes the byte to the output stream after
    1249          * converting to/from Base64 notation.
    1250          * When encoding, bytes are buffered three
    1251          * at a time before the output stream actually
    1252          * gets a write() call.
    1253          * When decoding, bytes are buffered four
    1254          * at a time.
    1255          *
    1256          * @param theByte the byte to write
    1257          * @since 1.3
    1258          */
    1259         public void write(int theByte) throws java.io.IOException {
    1260             // Encoding suspended?
    1261             if (suspendEncoding) {
    1262                 super.out.write(theByte);
    1263                 return;
    1264             }   // end if: supsended
    1265
    1266             // Encode?
    1267             if (encode) {
    1268                 buffer[position++] = (byte) theByte;
    1269                 if (position >= bufferLength)  // Enough to encode.
    1270                 {
    1271                     out.write(encode3to4(b4, buffer, bufferLength));
    1272
    1273                     lineLength += 4;
    1274                     if (breakLines && lineLength >= MAX_LINE_LENGTH) {
    1275                         out.write(NEW_LINE);
    1276                         lineLength = 0;
    1277                     }   // end if: end of line
    1278
    1279                     position = 0;
    1280                 }   // end if: enough to output
    1281             }   // end if: encoding
    1282
    1283             // Else, Decoding
    1284             else {
    1285                 // Meaningful Base64 character?
    1286                 if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
    1287                     buffer[position++] = (byte) theByte;
    1288                     if (position >= bufferLength)  // Enough to output.
    1289                     {
    1290                         int len = Base64.decode4to3(buffer, 0, b4, 0);
    1291                         out.write(b4, 0, len);
    1292                         //out.write( Base64.decode4to3( buffer ) );
    1293                         position = 0;
    1294                     }   // end if: enough to output
    1295                 }   // end if: meaningful base64 character
    1296                 else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
    1297                     throw new java.io.IOException("Invalid character in Base64 data.");
    1298                 }   // end else: not white space either
    1299             }   // end else: decoding
    1300         }   // end write
    1301
    1302
    1303         /***
    1304          * Calls {@link #write(int)} repeatedly until <var>len</var>
    1305          * bytes are written.
    1306          *
    1307          * @param theBytes array from which to read bytes
    1308          * @param off      offset for array
    1309          * @param len      max number of bytes to read into array
    1310          * @since 1.3
    1311          */
    1312         public void write(byte[] theBytes, int off, int len) throws java.io.IOException {
    1313             // Encoding suspended?
    1314             if (suspendEncoding) {
    1315                 super.out.write(theBytes, off, len);
    1316                 return;
    1317             }   // end if: supsended
    1318
    1319             for (int i = 0; i < len; i++) {
    1320                 write(theBytes[off + i]);
    1321             }   // end for: each byte written
    1322
    1323         }   // end write
    1324
    1325
    1326         /***
    1327          * Method added by PHIL. [Thanks, PHIL. -Rob]
    1328          * This pads the buffer without closing the stream.
    1329          */
    1330         public void flushBase64() throws java.io.IOException {
    1331             if (position > 0) {
    1332                 if (encode) {
    1333                     out.write(encode3to4(b4, buffer, position));
    1334                     position = 0;
    1335                 }   // end if: encoding
    1336                 else {
    1337                     throw new java.io.IOException("Base64 input not properly padded.");
    1338                 }   // end else: decoding
    1339             }   // end if: buffer partially full
    1340
    1341         }   // end flush
    1342
    1343
    1344         /***
    1345          * Flushes and closes (I think, in the superclass) the stream.
    1346          *
    1347          * @since 1.3
    1348          */
    1349         public void close() throws java.io.IOException {
    1350             // 1. Ensure that pending characters are written
    1351             flushBase64();
    1352
    1353             // 2. Actually close the stream
    1354             // Base class both flushes and closes.
    1355             super.close();
    1356
    1357             buffer = null;
    1358             out = null;
    1359         }   // end close
    1360
    1361
    1362         /***
    1363          * Suspends encoding of the stream.
    1364          * May be helpful if you need to embed a piece of
    1365          * base640-encoded data in a stream.
    1366          *
    1367          * @since 1.5.2
    1368          */
    1369         public void suspendEncoding() throws java.io.IOException {
    1370             flushBase64();
    1371             this.suspendEncoding = true;
    1372         }   // end suspendEncoding
    1373
    1374
    1375         /***
    1376          * Resumes encoding of the stream.
    1377          * May be helpful if you need to embed a piece of
    1378          * base640-encoded data in a stream.
    1379          *
    1380          * @since 1.5.2
    1381          */
    1382         public void resumeEncoding() {
    1383             this.suspendEncoding = false;
    1384         }   // end resumeEncoding
    1385
    1386
    1387     }   // end inner class OutputStream
    1388
    1389
    1390 }   // end class Base64