1 /**
2 * Encodes and decodes to and from Base64 notation.
3 *
4 * <p>
5 * Change Log:
6 * </p>
7 * <ul>
8 * <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
9 * some convenience methods for reading and writing to and from files.</li>
10 * <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
11 * with other encodings (like EBCDIC).</li>
12 * <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
13 * encoded data was a single byte.</li>
14 * <li>v2.0 - I got rid of methods that used booleans to set options.
15 * Now everything is more consolidated and cleaner. The code now detects
16 * when data that's being decoded is gzip-compressed and will decompress it
17 * automatically. Generally things are cleaner. You'll probably have to
18 * change some method calls that you were making to support the new
19 * options format (<tt>int</tt>s that you "OR" together).</li>
20 * <li>v1.5.1 - Fixed bug when decompressing and decoding to a
21 * byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
22 * Added the ability to "suspend" encoding in the Output Stream so
23 * you can turn on and off the encoding if you need to embed base64
24 * data in an otherwise "normal" stream (like an XML file).</li>
25 * <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
26 * This helps when using GZIP streams.
27 * Added the ability to GZip-compress objects before encoding them.</li>
28 * <li>v1.4 - Added helper methods to read/write files.</li>
29 * <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
30 * <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
31 * where last buffer being read, if not completely full, was not returned.</li>
32 * <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
33 * <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
34 * </ul>
35 *
36 * <p>
37 * I am placing this code in the Public Domain. Do with it as you will.
38 * This software comes with no guarantees or warranties but with
39 * plenty of well-wishing instead!
40 * Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
41 * periodically to check for updates or to contribute improvements.
42 * </p>
43 *
44 * @author Robert Harder
45 * @author rob@iharder.net
46 * @version 2.1
47 */
48 package org.rcfaces.core.internal.util;
49
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 public class Base64 {
54 private static final String REVISION = "$Revision: 1.3 $";
55
56 private static final Log LOG = LogFactory.getLog(Base64.class);
57
58 /* ******** P U B L I C F I E L D S ******** */
59
60 /** No options specified. Value is zero. */
61 public final static int NO_OPTIONS = 0;
62
63 /** Specify encoding. */
64 public final static int ENCODE = 1;
65
66 /** Specify decoding. */
67 public final static int DECODE = 0;
68
69 /** Don't break lines when encoding (violates strict Base64 specification) */
70 public final static int DONT_BREAK_LINES = 8;
71
72 /* ******** P R I V A T E F I E L D S ******** */
73
74 /** Maximum line length (76) of Base64 output. */
75 private final static int MAX_LINE_LENGTH = 76;
76
77 /** The equals sign (=) as a byte. */
78 private final static byte EQUALS_SIGN = (byte) '=';
79
80 /** The new line character (\n) as a byte. */
81 private final static byte NEW_LINE = (byte) '\n';
82
83 /** Preferred encoding. */
84 private final static String PREFERRED_ENCODING = "UTF-8";
85
86 /** The 64 valid Base64 values. */
87 private final static byte[] ALPHABET;
88
89 private final static byte[] _NATIVE_ALPHABET = /*
90 * May be something funny
91 * like EBCDIC
92 */
93 { (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
94 (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
95 (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
96 (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
97 (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
98 (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
99 (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
100 (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
101 (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
102 (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
103 (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
104 (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
105 (byte) '9', (byte) '+', (byte) '/' };
106
107 /** Determine which ALPHABET to use. */
108 static {
109 byte[] __bytes;
110 try {
111 __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
112 .getBytes(PREFERRED_ENCODING);
113 } // end try
114 catch (java.io.UnsupportedEncodingException use) {
115 __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
116 } // end catch
117 ALPHABET = __bytes;
118 } // end static
119
120 /**
121 * Translates a Base64 value to either its 6-bit reconstruction value or a
122 * negative number indicating some other meaning.
123 */
124 private final static byte[] DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9,
125 -9, // Decimal 0 - 8
126 -5, -5, // Whitespace: Tab and Linefeed
127 -9, -9, // Decimal 11 - 12
128 -5, // Whitespace: Carriage Return
129 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 -
130 // 26
131 -9, -9, -9, -9, -9, // Decimal 27 - 31
132 -5, // Whitespace: Space
133 -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
134 62, // Plus sign at decimal 43
135 -9, -9, -9, // Decimal 44 - 46
136 63, // Slash at decimal 47
137 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
138 -9, -9, -9, // Decimal 58 - 60
139 -1, // Equals sign at decimal 61
140 -9, -9, -9, // Decimal 62 - 64
141 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A'
142 // through 'N'
143 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O'
144 // through 'Z'
145 -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
146 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a'
147 // through 'm'
148 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n'
149 // through 'z'
150 -9, -9, -9, -9 // Decimal 123 - 126
151 /*
152 * ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
153 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
154 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
155 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
156 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
157 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
158 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
159 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
160 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
161 * -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255
162 */
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
167 // encoding
168 private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in
169
170 // encoding
171
172 private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in
173
174 // encoding
175
176 /** Defeats instantiation. */
177 private Base64() {
178 }
179
180 /* ******** E N C O D I N G M E T H O D S ******** */
181
182 /**
183 * Encodes up to the first three bytes of array <var>threeBytes</var> and
184 * returns a four-byte array in Base64 notation. The actual number of
185 * significant bytes in your array is given by <var>numSigBytes</var>. The
186 * array <var>threeBytes</var> needs only be as big as <var>numSigBytes</var>.
187 * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
188 *
189 * @param b4
190 * A reusable byte array to reduce array instantiation
191 * @param threeBytes
192 * the array to convert
193 * @param numSigBytes
194 * the number of significant bytes in your array
195 * @return four byte array in Base64 notation.
196 * @since 1.5.1
197 */
198 private static byte[] encode3to4(byte[] b4, byte[] threeBytes,
199 int numSigBytes) {
200 encode3to4(threeBytes, 0, numSigBytes, b4, 0);
201 return b4;
202 } // end encode3to4
203
204 /**
205 * Encodes up to three bytes of the array <var>source</var> and writes the
206 * resulting four Base64 bytes to <var>destination</var>. The source and
207 * destination arrays can be manipulated anywhere along their length by
208 * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
209 * does not check to make sure your arrays are large enough to accomodate
210 * <var>srcOffset</var> + 3 for the <var>source</var> array or
211 * <var>destOffset</var> + 4 for the <var>destination</var> array. The
212 * actual number of significant bytes in your array is given by
213 * <var>numSigBytes</var>.
214 *
215 * @param source
216 * the array to convert
217 * @param srcOffset
218 * the index where conversion begins
219 * @param numSigBytes
220 * the number of significant bytes in your array
221 * @param destination
222 * the array to hold the conversion
223 * @param destOffset
224 * the index where output will be put
225 * @return the <var>destination</var> array
226 * @since 1.3
227 */
228 private static byte[] encode3to4(byte[] source, int srcOffset,
229 int numSigBytes, byte[] destination, int destOffset) {
230 // 1 2 3
231 // 01234567890123456789012345678901 Bit position
232 // --------000000001111111122222222 Array position from threeBytes
233 // --------| || || || | Six bit groups to index ALPHABET
234 // >>18 >>12 >> 6 >> 0 Right shift necessary
235 // 0x3f 0x3f 0x3f Additional AND
236
237 // Create buffer with zero-padding if there are only one or two
238 // significant bytes passed in the array.
239 // We have to shift left 24 in order to flush out the 1's that appear
240 // when Java treats a value as negative that is cast from a byte to an
241 // int.
242 int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
243 | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
244 | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
245
246 switch (numSigBytes) {
247 case 3:
248 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
249 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
250 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
251 destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f];
252 return destination;
253
254 case 2:
255 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
256 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
257 destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f];
258 destination[destOffset + 3] = EQUALS_SIGN;
259 return destination;
260
261 case 1:
262 destination[destOffset] = ALPHABET[(inBuff >>> 18)];
263 destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f];
264 destination[destOffset + 2] = EQUALS_SIGN;
265 destination[destOffset + 3] = EQUALS_SIGN;
266 return destination;
267
268 default:
269 return destination;
270 } // end switch
271 } // end encode3to4
272
273 /**
274 * Encodes a byte array into Base64 notation. Does not GZip-compress data.
275 *
276 * @param source
277 * The data to convert
278 * @since 1.4
279 */
280 public static String encodeBytes(byte[] source) {
281 return encodeBytes(source, 0, source.length, NO_OPTIONS);
282 } // end encodeBytes
283
284 /**
285 * Encodes a byte array into Base64 notation.
286 * <p>
287 * Valid options:
288 *
289 * <pre>
290 * GZIP: gzip-compresses object before encoding it.
291 * DONT_BREAK_LINES: don't break lines at 76 characters
292 * <i>Note: Technically, this makes your encoding non-compliant.</i>
293 * </pre>
294 *
295 * <p>
296 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
297 * <p>
298 * Example:
299 * <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
300 *
301 *
302 * @param source
303 * The data to convert
304 * @param options
305 * Specified options
306 * @see Base64#GZIP
307 * @see Base64#DONT_BREAK_LINES
308 * @since 2.0
309 */
310 public static String encodeBytes(byte[] source, int options) {
311 return encodeBytes(source, 0, source.length, options);
312 } // end encodeBytes
313
314 /**
315 * Encodes a byte array into Base64 notation. Does not GZip-compress data.
316 *
317 * @param source
318 * The data to convert
319 * @param off
320 * Offset in array where conversion should begin
321 * @param len
322 * Length of data to convert
323 * @since 1.4
324 */
325 public static String encodeBytes(byte[] source, int off, int len) {
326 return encodeBytes(source, off, len, NO_OPTIONS);
327 } // end encodeBytes
328
329 /**
330 * Encodes a byte array into Base64 notation.
331 * <p>
332 * Valid options:
333 *
334 * <pre>
335 * GZIP: gzip-compresses object before encoding it.
336 * DONT_BREAK_LINES: don't break lines at 76 characters
337 * <i>Note: Technically, this makes your encoding non-compliant.</i>
338 * </pre>
339 *
340 * <p>
341 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
342 * <p>
343 * Example:
344 * <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
345 *
346 *
347 * @param source
348 * The data to convert
349 * @param off
350 * Offset in array where conversion should begin
351 * @param len
352 * Length of data to convert
353 * @param options
354 * Specified options
355 * @see Base64#DONT_BREAK_LINES
356 * @since 2.0
357 */
358 public static String encodeBytes(byte[] source, int off, int len,
359 int options) {
360 // Isolate options
361 int dontBreakLines = (options & DONT_BREAK_LINES);
362
363 // Convert option to boolean in way that code likes it.
364 boolean breakLines = dontBreakLines == 0;
365
366 int len43 = len * 4 / 3;
367 byte[] outBuff = new byte[(len43) // Main 4:3
368 + ((len % 3) > 0 ? 4 : 0) // Account for padding
369 + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New
370 // lines
371 int d = 0;
372 int e = 0;
373 int len2 = len - 2;
374 int lineLength = 0;
375 for (; d < len2; d += 3, e += 4) {
376 encode3to4(source, d + off, 3, outBuff, e);
377
378 lineLength += 4;
379 if (breakLines && lineLength == MAX_LINE_LENGTH) {
380 outBuff[e + 4] = NEW_LINE;
381 e++;
382 lineLength = 0;
383 } // end if: end of line
384 } // en dfor: each piece of array
385
386 if (d < len) {
387 encode3to4(source, d + off, len - d, outBuff, e);
388 e += 4;
389 } // end if: some padding needed
390
391 // Return value according to relevant encoding.
392 try {
393 return new String(outBuff, 0, e, PREFERRED_ENCODING);
394 } // end try
395 catch (java.io.UnsupportedEncodingException uue) {
396 return new String(outBuff, 0, e);
397 } // end catch
398
399 // end else: don't compress
400
401 } // end encodeBytes
402
403 /* ******** D E C O D I N G M E T H O D S ******** */
404
405 /**
406 * Decodes four bytes from array <var>source</var> and writes the resulting
407 * bytes (up to three of them) to <var>destination</var>. The source and
408 * destination arrays can be manipulated anywhere along their length by
409 * specifying <var>srcOffset</var> and <var>destOffset</var>. This method
410 * does not check to make sure your arrays are large enough to accomodate
411 * <var>srcOffset</var> + 4 for the <var>source</var> array or
412 * <var>destOffset</var> + 3 for the <var>destination</var> array. This
413 * method returns the actual number of bytes that were converted from the
414 * Base64 encoding.
415 *
416 *
417 * @param source
418 * the array to convert
419 * @param srcOffset
420 * the index where conversion begins
421 * @param destination
422 * the array to hold the conversion
423 * @param destOffset
424 * the index where output will be put
425 * @return the number of decoded bytes converted
426 * @since 1.3
427 */
428 private static int decode4to3(byte[] source, int srcOffset,
429 byte[] destination, int destOffset) {
430 // Example: Dk==
431 if (source[srcOffset + 2] == EQUALS_SIGN) {
432 // Two ways to do the same thing. Don't know which way I like best.
433 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
434 // )
435 // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
436 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
437 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12);
438
439 destination[destOffset] = (byte) (outBuff >>> 16);
440 return 1;
441 }
442
443 // Example: DkL=
444 else if (source[srcOffset + 3] == EQUALS_SIGN) {
445 // Two ways to do the same thing. Don't know which way I like best.
446 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6
447 // )
448 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
449 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
450 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
451 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
452 | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6);
453
454 destination[destOffset] = (byte) (outBuff >>> 16);
455 destination[destOffset + 1] = (byte) (outBuff >>> 8);
456 return 2;
457 }
458
459 // Example: DkLE
460 else {
461 try {
462 // Two ways to do the same thing. Don't know which way I like
463 // best.
464 // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 )
465 // >>> 6 )
466 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
467 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
468 // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
469 int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18)
470 | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
471 | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6)
472 | ((DECODABET[source[srcOffset + 3]] & 0xFF));
473
474 destination[destOffset] = (byte) (outBuff >> 16);
475 destination[destOffset + 1] = (byte) (outBuff >> 8);
476 destination[destOffset + 2] = (byte) (outBuff);
477
478 return 3;
479
480 } catch (Exception e) {
481 LOG.error(e);
482 LOG.error("" + source[srcOffset] + ": "
483 + (DECODABET[source[srcOffset]]));
484 LOG.error("" + source[srcOffset + 1] + ": "
485 + (DECODABET[source[srcOffset + 1]]));
486 LOG.error("" + source[srcOffset + 2] + ": "
487 + (DECODABET[source[srcOffset + 2]]));
488 LOG.error("" + source[srcOffset + 3] + ": "
489 + (DECODABET[source[srcOffset + 3]]));
490 return -1;
491 } // e nd catch
492 }
493 } // end decodeToBytes
494
495 /**
496 * Very low-level access to decoding ASCII characters in the form of a byte
497 * array. Does not support automatically gunzipping or any other "fancy"
498 * features.
499 *
500 * @param source
501 * The Base64 encoded data
502 * @param off
503 * The offset of where to begin decoding
504 * @param len
505 * The length of characters to decode
506 * @return decoded data
507 * @since 1.3
508 */
509 public static byte[] decode(byte[] source, int off, int len) {
510 int len34 = len * 3 / 4;
511 byte[] outBuff = new byte[len34]; // Upper limit on size of output
512 int outBuffPosn = 0;
513
514 byte[] b4 = new byte[4];
515 int b4Posn = 0;
516 int i = 0;
517 byte sbiCrop = 0;
518 byte sbiDecode = 0;
519 for (i = off; i < off + len; i++) {
520 sbiCrop = (byte) (source[i] & 0x7f); // Only the low seven bits
521 sbiDecode = DECODABET[sbiCrop];
522
523 if (sbiDecode >= WHITE_SPACE_ENC) // White space, Equals sign or
524 // better
525 {
526 if (sbiDecode >= EQUALS_SIGN_ENC) {
527 b4[b4Posn++] = sbiCrop;
528 if (b4Posn > 3) {
529 outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
530 b4Posn = 0;
531
532 // If that was the equals sign, break out of 'for' loop
533 if (sbiCrop == EQUALS_SIGN)
534 break;
535 } // end if: quartet built
536
537 } // end if: equals sign or better
538
539 } // end if: white space, equals sign or better
540 else {
541 LOG.error("Bad Base64 input character at " + i + ": "
542 + source[i] + "(decimal)");
543 return null;
544 } // end else:
545 } // each input character
546
547 byte[] out = new byte[outBuffPosn];
548 System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
549 return out;
550 } // end decode
551
552 /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
553
554 /**
555 * A {@link Base64.Base64InputStream} will read data from another
556 * <tt>java.io.InputStream</tt>, given in the constructor, and
557 * encode/decode to/from Base64 notation on the fly.
558 *
559 * @see Base64
560 * @since 1.3
561 */
562 public static class Base64InputStream extends java.io.FilterInputStream {
563 private boolean encode; // Encoding or decoding
564
565 private int position; // Current position in the buffer
566
567 private byte[] buffer; // Small buffer holding converted data
568
569 private int bufferLength; // Length of buffer (3 or 4)
570
571 private int numSigBytes; // Number of meaningful bytes in the buffer
572
573 private int lineLength;
574
575 private boolean breakLines; // Break lines at less than 80 characters
576
577 /**
578 * Constructs a {@link Base64.Base64InputStream} in DECODE mode.
579 *
580 * @param in
581 * the <tt>java.io.InputStream</tt> from which to read
582 * data.
583 * @since 1.3
584 */
585 public Base64InputStream(java.io.InputStream in) {
586 this(in, DECODE);
587 } // end constructor
588
589 /**
590 * Constructs a {@link Base64.Base64InputStream} in either ENCODE or
591 * DECODE mode.
592 * <p>
593 * Valid options:
594 *
595 * <pre>
596 * ENCODE or DECODE: Encode or Decode as data is read.
597 * DONT_BREAK_LINES: don't break lines at 76 characters
598 * (only meaningful when encoding)
599 * <i>Note: Technically, this makes your encoding non-compliant.</i>
600 * </pre>
601 *
602 * <p>
603 * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
604 *
605 *
606 * @param in
607 * the <tt>java.io.InputStream</tt> from which to read
608 * data.
609 * @param options
610 * Specified options
611 * @see Base64#ENCODE
612 * @see Base64#DECODE
613 * @see Base64#DONT_BREAK_LINES
614 * @since 2.0
615 */
616 public Base64InputStream(java.io.InputStream in, int options) {
617 super(in);
618 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
619 this.encode = (options & ENCODE) == ENCODE;
620 this.bufferLength = encode ? 4 : 3;
621 this.buffer = new byte[bufferLength];
622 this.position = -1;
623 this.lineLength = 0;
624 }
625
626 /**
627 * Reads enough of the input stream to convert to/from Base64 and
628 * returns the next byte.
629 *
630 * @return next byte
631 * @since 1.3
632 */
633 public int read() throws java.io.IOException {
634 // Do we need to get data?
635 if (position < 0) {
636 if (encode) {
637 byte[] b3 = new byte[3];
638 int numBinaryBytes = 0;
639 for (int i = 0; i < 3; i++) {
640 try {
641 int b = in.read();
642
643 // If end of stream, b is -1.
644 if (b >= 0) {
645 b3[i] = (byte) b;
646 numBinaryBytes++;
647 } // end if: not end of stream
648
649 } // end try: read
650 catch (java.io.IOException e) {
651 // Only a problem if we got no data at all.
652 if (i == 0)
653 throw e;
654
655 } // end catch
656 } // end for: each needed input byte
657
658 if (numBinaryBytes > 0) {
659 encode3to4(b3, 0, numBinaryBytes, buffer, 0);
660 position = 0;
661 numSigBytes = 4;
662 } // end if: got data
663 else {
664 return -1;
665 } // end else
666 } // end if: encoding
667
668 // Else decoding
669 else {
670 byte[] b4 = new byte[4];
671 int i = 0;
672 for (i = 0; i < 4; i++) {
673 // Read four "meaningful" bytes:
674 int b = 0;
675 do {
676 b = in.read();
677 } while (b >= 0
678 && DECODABET[b & 0x7f] <= WHITE_SPACE_ENC);
679
680 if (b < 0)
681 break; // Reads a -1 if end of stream
682
683 b4[i] = (byte) b;
684 } // end for: each needed input byte
685
686 if (i == 4) {
687 numSigBytes = decode4to3(b4, 0, buffer, 0);
688 position = 0;
689 } // end if: got four characters
690 else if (i == 0) {
691 return -1;
692 } // end else if: also padded correctly
693 else {
694 // Must have broken out from above.
695 throw new java.io.IOException(
696 "Improperly padded Base64 input.");
697 } // end
698
699 } // end else: decode
700 } // end else: get data
701
702 // Got data?
703 if (position >= 0) {
704 // End of relevant data?
705 if ( /* !encode && */position >= numSigBytes)
706 return -1;
707
708 if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
709 lineLength = 0;
710 return '\n';
711 } // end if
712
713 lineLength++; // This isn't important when decoding
714 // but throwing an extra "if" seems
715 // just as wasteful.
716
717 int b = buffer[position++];
718
719 if (position >= bufferLength)
720 position = -1;
721
722 return b & 0xFF; // This is how you "cast" a byte that's
723 // intended to be unsigned.
724 // end else
725 } // end if: position >= 0
726
727 // Else error
728
729 // When JDK1.4 is more accepted, use an assertion here.
730 throw new java.io.IOException(
731 "Error in Base64 code reading stream.");
732 // end else
733 } // end read
734
735 /**
736 * Calls {@link #read()} repeatedly until the end of stream is reached
737 * or <var>len</var> bytes are read. Returns number of bytes read into
738 * array or -1 if end of stream is encountered.
739 *
740 * @param dest
741 * array to hold values
742 * @param off
743 * offset for array
744 * @param len
745 * max number of bytes to read into array
746 * @return bytes read into array or -1 if end of stream is encountered.
747 * @since 1.3
748 */
749 public int read(byte[] dest, int off, int len)
750 throws java.io.IOException {
751 int i;
752 for (i = 0; i < len; i++) {
753 int b = read();
754
755 // if( b < 0 && i == 0 )
756 // return -1;
757
758 if (b >= 0) {
759 dest[off + i] = (byte) b;
760 continue;
761 }
762
763 if (i == 0) {
764 return -1;
765 }
766
767 break;
768 }
769 return i;
770 }
771 }
772
773 /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
774
775 /**
776 * A {@link Base64.Base64OutputStream} will write data to another
777 * <tt>java.io.OutputStream</tt>, given in the constructor, and
778 * encode/decode to/from Base64 notation on the fly.
779 *
780 * @see Base64
781 * @since 1.3
782 */
783 public static class Base64OutputStream extends java.io.FilterOutputStream {
784 private boolean encode;
785
786 private int position;
787
788 private byte[] buffer;
789
790 private int bufferLength;
791
792 private int lineLength;
793
794 private boolean breakLines;
795
796 private byte[] b4; // Scratch used in a few places
797
798 private boolean suspendEncoding;
799
800 /**
801 * Constructs a {@link Base64.Base64OutputStream} in ENCODE mode.
802 *
803 * @param out
804 * the <tt>java.io.OutputStream</tt> to which data will be
805 * written.
806 * @since 1.3
807 */
808 public Base64OutputStream(java.io.OutputStream out) {
809 this(out, ENCODE);
810 } // end constructor
811
812 /**
813 * Constructs a {@link Base64.Base64OutputStream} in either ENCODE or
814 * DECODE mode.
815 * <p>
816 * Valid options:
817 *
818 * <pre>
819 * ENCODE or DECODE: Encode or Decode as data is read.
820 * DONT_BREAK_LINES: don't break lines at 76 characters
821 * (only meaningful when encoding)
822 * <i>Note: Technically, this makes your encoding non-compliant.</i>
823 * </pre>
824 *
825 * <p>
826 * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
827 *
828 * @param out
829 * the <tt>java.io.OutputStream</tt> to which data will be
830 * written.
831 * @param options
832 * Specified options.
833 * @see Base64#ENCODE
834 * @see Base64#DECODE
835 * @see Base64#DONT_BREAK_LINES
836 * @since 1.3
837 */
838 public Base64OutputStream(java.io.OutputStream out, int options) {
839 super(out);
840 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
841 this.encode = (options & ENCODE) == ENCODE;
842 this.bufferLength = encode ? 3 : 4;
843 this.buffer = new byte[bufferLength];
844 this.position = 0;
845 this.lineLength = 0;
846 this.suspendEncoding = false;
847 this.b4 = new byte[4];
848 } // end constructor
849
850 /**
851 * Writes the byte to the output stream after converting to/from Base64
852 * notation. When encoding, bytes are buffered three at a time before
853 * the output stream actually gets a write() call. When decoding, bytes
854 * are buffered four at a time.
855 *
856 * @param theByte
857 * the byte to write
858 * @since 1.3
859 */
860 public void write(int theByte) throws java.io.IOException {
861 // Encoding suspended?
862 if (suspendEncoding) {
863 super.out.write(theByte);
864 return;
865 } // end if: supsended
866
867 // Encode?
868 if (encode) {
869 buffer[position++] = (byte) theByte;
870 if (position >= bufferLength) // Enough to encode.
871 {
872 out.write(encode3to4(b4, buffer, bufferLength));
873
874 lineLength += 4;
875 if (breakLines && lineLength >= MAX_LINE_LENGTH) {
876 out.write(NEW_LINE);
877 lineLength = 0;
878 } // end if: end of line
879
880 position = 0;
881 } // end if: enough to output
882 } // end if: encoding
883
884 // Else, Decoding
885 else {
886 // Meaningful Base64 character?
887 if (DECODABET[theByte & 0x7f] > WHITE_SPACE_ENC) {
888 buffer[position++] = (byte) theByte;
889 if (position >= bufferLength) // Enough to output.
890 {
891 int len = Base64.decode4to3(buffer, 0, b4, 0);
892 out.write(b4, 0, len);
893 // out.write( Base64.decode4to3( buffer ) );
894 position = 0;
895 } // end if: enough to output
896 } // end if: meaningful base64 character
897 else if (DECODABET[theByte & 0x7f] != WHITE_SPACE_ENC) {
898 throw new java.io.IOException(
899 "Invalid character in Base64 data.");
900 } // end else: not white space either
901 } // end else: decoding
902 } // end write
903
904 /**
905 * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are
906 * written.
907 *
908 * @param theBytes
909 * array from which to read bytes
910 * @param off
911 * offset for array
912 * @param len
913 * max number of bytes to read into array
914 * @since 1.3
915 */
916 public void write(byte[] theBytes, int off, int len)
917 throws java.io.IOException {
918 // Encoding suspended?
919 if (suspendEncoding) {
920 super.out.write(theBytes, off, len);
921 return;
922 } // end if: supsended
923
924 for (int i = 0; i < len; i++) {
925 write(theBytes[off + i]);
926 } // end for: each byte written
927
928 }
929
930 /**
931 * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer
932 * without closing the stream.
933 */
934 public void flushBase64() throws java.io.IOException {
935 if (position < 1) {
936 return;
937 }
938
939 if (encode == false) {
940 throw new java.io.IOException(
941 "Base64 input not properly padded.");
942 }
943
944 out.write(encode3to4(b4, buffer, position));
945 position = 0;
946 }
947
948 /**
949 * Flushes and closes (I think, in the superclass) the stream.
950 *
951 * @since 1.3
952 */
953 public void close() throws java.io.IOException {
954 // 1. Ensure that pending characters are written
955 flushBase64();
956
957 // 2. Actually close the stream
958 // Base class both flushes and closes.
959 super.close();
960
961 buffer = null;
962 out = null;
963 }
964
965 /**
966 * Suspends encoding of the stream. May be helpful if you need to embed
967 * a piece of base640-encoded data in a stream.
968 *
969 * @since 1.5.1
970 */
971 public void suspendEncoding() throws java.io.IOException {
972 flushBase64();
973 this.suspendEncoding = true;
974 }
975
976 /**
977 * Resumes encoding of the stream. May be helpful if you need to embed a
978 * piece of base640-encoded data in a stream.
979 *
980 * @since 1.5.1
981 */
982 public void resumeEncoding() {
983 this.suspendEncoding = false;
984 }
985 }
986 }