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