source: trunk/gli/src/org/greenstone/gatherer/feedback/Base64.java@ 12051

Last change on this file since 12051 was 12051, checked in by mdewsnip, 18 years ago

(FindBugs) Removed a couple of pointless assignments.

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