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