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
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
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 =
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 }
136 catch (java.io.UnsupportedEncodingException use) {
137 __bytes = _NATIVE_ALPHABET;
138 }
139 ALPHABET = __bytes;
140 }
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,
150 -5, -5,
151 -9, -9,
152 -5,
153 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
154 -9, -9, -9, -9, -9,
155 -5,
156 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9,
157 62,
158 -9, -9, -9,
159 63,
160 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
161 -9, -9, -9,
162 -1,
163 -9, -9, -9,
164 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
165 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
166 -9, -9, -9, -9, -9, -9,
167 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
168 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
169 -9, -9, -9, -9
170
171
172
173
174
175
176
177
178
179
180 };
181
182
183
184 private final static byte WHITE_SPACE_ENC = -5;
185 private final static byte EQUALS_SIGN_ENC = -1;
186
187
188 /***
189 * Defeats instantiation.
190 */
191 private Base64() {
192 }
193
194
195
196
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 }
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
244
245
246
247
248
249
250
251
252
253
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 }
283 }
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 }
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
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
333 int gzip = (options & GZIP);
334 int dontBreakLines = (options & DONT_BREAK_LINES);
335
336 try {
337
338 baos = new java.io.ByteArrayOutputStream();
339 b64os = new Base64.OutputStream(baos, ENCODE | dontBreakLines);
340
341
342 if (gzip == GZIP) {
343 gzos = new java.util.zip.GZIPOutputStream(b64os);
344 oos = new java.io.ObjectOutputStream(gzos);
345 }
346 else {
347 oos = new java.io.ObjectOutputStream(b64os);
348 }
349
350 oos.writeObject(serializableObject);
351 }
352 catch (java.io.IOException e) {
353 e.printStackTrace();
354 return null;
355 }
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 }
374
375
376 try {
377 return new String(baos.toByteArray(), PREFERRED_ENCODING);
378 }
379 catch (java.io.UnsupportedEncodingException uue) {
380 return new String(baos.toByteArray());
381 }
382
383 }
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 }
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 }
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 }
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
459 int dontBreakLines = (options & DONT_BREAK_LINES);
460 int gzip = (options & GZIP);
461
462
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
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 }
478 catch (java.io.IOException e) {
479 e.printStackTrace();
480 return null;
481 }
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 }
496
497
498 try {
499 return new String(baos.toByteArray(), PREFERRED_ENCODING);
500 }
501 catch (java.io.UnsupportedEncodingException uue) {
502 return new String(baos.toByteArray());
503 }
504 }
505
506
507 else {
508
509 boolean breakLines = dontBreakLines == 0;
510
511 int len43 = len * 4 / 3;
512 byte[] outBuff = new byte[(len43)
513 + ((len % 3) > 0 ? 4 : 0)
514 + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)];
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 }
528 }
529
530 if (d < len) {
531 encode3to4(source, d + off, len - d, outBuff, e);
532 e += 4;
533 }
534
535
536
537 try {
538 return new String(outBuff, 0, e, PREFERRED_ENCODING);
539 }
540 catch (java.io.UnsupportedEncodingException uue) {
541 return new String(outBuff, 0, e);
542 }
543
544 }
545
546 }
547
548
549
550
551
552
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
578 if (source[srcOffset + 2] == EQUALS_SIGN) {
579
580
581
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
590 else if (source[srcOffset + 3] == EQUALS_SIGN) {
591
592
593
594
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
605 else {
606 try {
607
608
609
610
611
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 }
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 int len34 = len * 3 / 4;
647 byte[] outBuff = new byte[len34];
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);
657 sbiDecode = DECODABET[sbiCrop];
658
659 if (sbiDecode >= WHITE_SPACE_ENC)
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
668 if (sbiCrop == EQUALS_SIGN) {
669 break;
670 }
671 }
672
673 }
674
675 }
676 else {
677 System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)");
678 return null;
679 }
680 }
681
682 byte[] out = new byte[outBuffPosn];
683 System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
684 return out;
685 }
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 }
701 catch (java.io.UnsupportedEncodingException uee) {
702 bytes = s.getBytes();
703 }
704
705
706
707 bytes = decode(bytes, 0, bytes.length);
708
709
710
711
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 }
730
731
732 bytes = baos.toByteArray();
733
734 }
735 catch (java.io.IOException e) {
736
737 }
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 }
752
753 }
754 }
755
756 return bytes;
757 }
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
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 }
782 catch (java.io.IOException e) {
783 e.printStackTrace();
784 obj = null;
785 }
786 catch (java.lang.ClassNotFoundException e) {
787 e.printStackTrace();
788 obj = null;
789 }
790 finally {
791 try {
792 bais.close();
793 } catch (Exception e) {
794 }
795 try {
796 ois.close();
797 } catch (Exception e) {
798 }
799 }
800
801 return obj;
802 }
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 }
821 catch (java.io.IOException e) {
822
823 success = false;
824 }
825 finally {
826 try {
827 bos.close();
828 } catch (Exception e) {
829 }
830 }
831
832 return success;
833 }
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 }
852 catch (java.io.IOException e) {
853 success = false;
854 }
855 finally {
856 try {
857 bos.close();
858 } catch (Exception e) {
859 }
860 }
861
862 return success;
863 }
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
879 java.io.File file = new java.io.File(filename);
880 byte[] buffer = null;
881 int length = 0;
882 int numBytes = 0;
883
884
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 }
889 buffer = new byte[(int) file.length()];
890
891
892 bis = new Base64.InputStream(
893 new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.DECODE
894 );
895
896
897 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
898 length += numBytes;
899 }
900
901
902 decodedData = new byte[length];
903 System.arraycopy(buffer, 0, decodedData, 0, length);
904
905 }
906 catch (java.io.IOException e) {
907 System.err.println("Error decoding from file " + filename);
908 }
909 finally {
910 try {
911 bis.close();
912 } catch (Exception e) {
913 }
914 }
915
916 return decodedData;
917 }
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
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
939 bis = new Base64.InputStream(
940 new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.ENCODE
941 );
942
943
944 while ((numBytes = bis.read(buffer, length, 4096)) >= 0) {
945 length += numBytes;
946 }
947
948
949 encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
950
951 }
952 catch (java.io.IOException e) {
953 System.err.println("Error encoding from file " + filename);
954 }
955 finally {
956 try {
957 bis.close();
958 } catch (Exception e) {
959 }
960 }
961
962 return encodedData;
963 }
964
965
966
967
968
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;
982 private int position;
983 private byte[] buffer;
984 private int bufferLength;
985 private int numSigBytes;
986 private int lineLength;
987 private boolean breakLines;
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 }
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 }
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
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
1049 if (b >= 0) {
1050 b3[i] = (byte) b;
1051 numBinaryBytes++;
1052 }
1053
1054 }
1055 catch (java.io.IOException e) {
1056
1057 if (i == 0) {
1058 throw e;
1059 }
1060
1061 }
1062 }
1063
1064 if (numBinaryBytes > 0) {
1065 encode3to4(b3, 0, numBinaryBytes, buffer, 0);
1066 position = 0;
1067 numSigBytes = 4;
1068 }
1069 else {
1070 return -1;
1071 }
1072 }
1073
1074
1075 else {
1076 byte[] b4 = new byte[4];
1077 int i = 0;
1078 for (i = 0; i < 4; i++) {
1079
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;
1087 }
1088
1089 b4[i] = (byte) b;
1090 }
1091
1092 if (i == 4) {
1093 numSigBytes = decode4to3(b4, 0, buffer, 0);
1094 position = 0;
1095 }
1096 else if (i == 0) {
1097 return -1;
1098 }
1099 else {
1100
1101 throw new java.io.IOException("Improperly padded Base64 input.");
1102 }
1103
1104 }
1105 }
1106
1107
1108 if (position >= 0) {
1109
1110 if (
1111 return -1;
1112 }
1113
1114 if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
1115 lineLength = 0;
1116 return '\n';
1117 }
1118 else {
1119 lineLength++;
1120
1121
1122
1123 int b = buffer[position++];
1124
1125 if (position >= bufferLength) {
1126 position = -1;
1127 }
1128
1129 return b & 0xFF;
1130
1131 }
1132 }
1133
1134
1135 else {
1136
1137 throw new java.io.IOException("Error in Base64 code reading stream.");
1138 }
1139 }
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
1161
1162
1163 if (b >= 0) {
1164 dest[off + i] = (byte) b;
1165 } else if (i == 0) {
1166 return -1;
1167 } else {
1168 break;
1169 }
1170 }
1171 return i;
1172 }
1173
1174 }
1175
1176
1177
1178
1179
1180
1181
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;
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 }
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 }
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
1261 if (suspendEncoding) {
1262 super.out.write(theByte);
1263 return;
1264 }
1265
1266
1267 if (encode) {
1268 buffer[position++] = (byte) theByte;
1269 if (position >= bufferLength)
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 }
1278
1279 position = 0;
1280 }
1281 }
1282
1283
1284 else {
1285
1286 if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
1287 buffer[position++] = (byte) theByte;
1288 if (position >= bufferLength)
1289 {
1290 int len = Base64.decode4to3(buffer, 0, b4, 0);
1291 out.write(b4, 0, len);
1292
1293 position = 0;
1294 }
1295 }
1296 else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
1297 throw new java.io.IOException("Invalid character in Base64 data.");
1298 }
1299 }
1300 }
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
1314 if (suspendEncoding) {
1315 super.out.write(theBytes, off, len);
1316 return;
1317 }
1318
1319 for (int i = 0; i < len; i++) {
1320 write(theBytes[off + i]);
1321 }
1322
1323 }
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 }
1336 else {
1337 throw new java.io.IOException("Base64 input not properly padded.");
1338 }
1339 }
1340
1341 }
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
1351 flushBase64();
1352
1353
1354
1355 super.close();
1356
1357 buffer = null;
1358 out = null;
1359 }
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 }
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 }
1385
1386
1387 }
1388
1389
1390 }