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

Last change on this file since 24915 was 24915, checked in by ak19, 12 years ago

Dr Bainbridge fixed the problem identified by Blandine and Silver in the mailing list where moving files from one subfolder to another within an existing remote collection would fail if the filename had about 32 or more characters in it (even though the characters were plain ASCII). The reason turned out to be that the feedback\Base64.java class was set to break lines after 76 characters when encoding strings into Base64. This is what happened when Base64 encoding filenames which then got moved about. This is now fixed in feedback\Base64.java with the new method encodeBytesInSingleLine() which sets the base64 encoding options to not break the lines.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 47.1 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 * Added this variant of the method in so that RemoteGreenstoneServer.java
383 * can process large source and target filenames, without Base64 encoding
384 * them introducing a new line.
385 * @param source The data to convert
386 * @since January
387 */
388 public static String encodeBytesInSingleLine( byte[] source)
389 {
390 return encodeBytes( source, 0, source.length, DONT_BREAK_LINES);
391 } // end encodeBytes
392
393 /**
394 * Encodes a byte array into Base64 notation.
395 * <p>
396 * Valid options:<pre>
397 * GZIP: gzip-compresses object before encoding it.
398 * DONT_BREAK_LINES: don't break lines at 76 characters
399 * <i>Note: Technically, this makes your encoding non-compliant.</i>
400 * </pre>
401 * <p>
402 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
403 * <p>
404 * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
405 *
406 *
407 * @param source The data to convert
408 * @param options Specified options
409 * @see Base64#GZIP
410 * @see Base64#DONT_BREAK_LINES
411 * @since 2.0
412 */
413 public static String encodeBytes( byte[] source, int options )
414 {
415 return encodeBytes( source, 0, source.length, options );
416 } // end encodeBytes
417
418
419 /**
420 * Encodes a byte array into Base64 notation.
421 * Does not GZip-compress data.
422 *
423 * @param source The data to convert
424 * @param off Offset in array where conversion should begin
425 * @param len Length of data to convert
426 * @since 1.4
427 */
428 public static String encodeBytes( byte[] source, int off, int len )
429 {
430 return encodeBytes( source, off, len, NO_OPTIONS );
431 } // end encodeBytes
432
433
434
435 /**
436 * Encodes a byte array into Base64 notation.
437 * <p>
438 * Valid options:<pre>
439 * GZIP: gzip-compresses object before encoding it.
440 * DONT_BREAK_LINES: don't break lines at 76 characters
441 * <i>Note: Technically, this makes your encoding non-compliant.</i>
442 * </pre>
443 * <p>
444 * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
445 * <p>
446 * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
447 *
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 // Isolate options
460 int dontBreakLines = ( options & DONT_BREAK_LINES );
461 int gzip = ( options & GZIP );
462
463 // Compress?
464 if( gzip == GZIP )
465 {
466 java.io.ByteArrayOutputStream baos = null;
467 java.util.zip.GZIPOutputStream gzos = null;
468 Base64.OutputStream b64os = null;
469
470
471 try
472 {
473 // GZip -> Base64 -> ByteArray
474 baos = new java.io.ByteArrayOutputStream();
475 b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
476 gzos = new java.util.zip.GZIPOutputStream( b64os );
477
478 gzos.write( source, off, len );
479 gzos.close();
480 } // end try
481 catch( java.io.IOException e )
482 {
483 e.printStackTrace();
484 return null;
485 } // end catch
486 finally
487 {
488 try{ gzos.close(); } catch( Exception e ){}
489 try{ b64os.close(); } catch( Exception e ){}
490 try{ baos.close(); } catch( Exception e ){}
491 } // end finally
492
493 return new String( baos.toByteArray() );
494 } // end if: compress
495
496 // Else, don't compress. Better not to use streams at all then.
497 else
498 {
499 // Convert option to boolean in way that code likes it.
500 boolean breakLines = dontBreakLines == 0;
501
502 int len43 = len * 4 / 3;
503 byte[] outBuff = new byte[ ( len43 ) // Main 4:3
504 + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
505 + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
506 int d = 0;
507 int e = 0;
508 int len2 = len - 2;
509 int lineLength = 0;
510 for( ; d < len2; d+=3, e+=4 )
511 {
512 encode3to4( source, d+off, 3, outBuff, e );
513
514 lineLength += 4;
515 if( breakLines && lineLength == MAX_LINE_LENGTH )
516 {
517 outBuff[e+4] = NEW_LINE;
518 e++;
519 lineLength = 0;
520 } // end if: end of line
521 } // en dfor: each piece of array
522
523 if( d < len )
524 {
525 encode3to4( source, d+off, len - d, outBuff, e );
526 e += 4;
527 } // end if: some padding needed
528
529 return new String( outBuff, 0, e );
530 } // end else: don't compress
531
532 } // end encodeBytes
533
534
535
536
537
538/* ******** D E C O D I N G M E T H O D S ******** */
539
540
541 /**
542 * Decodes the first four bytes of array <var>fourBytes</var>
543 * and returns an array up to three bytes long with the
544 * decoded values.
545 *
546 * @param fourBytes the array with Base64 content
547 * @return array with decoded values
548 * @since 1.3
549 */
550 private static byte[] decode4to3( byte[] fourBytes )
551 {
552 byte[] outBuff1 = new byte[3];
553 int count = decode4to3( fourBytes, 0, outBuff1, 0 );
554 byte[] outBuff2 = new byte[ count ];
555
556 for( int i = 0; i < count; i++ )
557 outBuff2[i] = outBuff1[i];
558
559 return outBuff2;
560 }
561
562
563
564
565 /**
566 * Decodes four bytes from array <var>source</var>
567 * and writes the resulting bytes (up to three of them)
568 * to <var>destination</var>.
569 * The source and destination arrays can be manipulated
570 * anywhere along their length by specifying
571 * <var>srcOffset</var> and <var>destOffset</var>.
572 * This method does not check to make sure your arrays
573 * are large enough to accomodate <var>srcOffset</var> + 4 for
574 * the <var>source</var> array or <var>destOffset</var> + 3 for
575 * the <var>destination</var> array.
576 * This method returns the actual number of bytes that
577 * were converted from the Base64 encoding.
578 *
579 *
580 * @param source the array to convert
581 * @param srcOffset the index where conversion begins
582 * @param destination the array to hold the conversion
583 * @param destOffset the index where output will be put
584 * @return the number of decoded bytes converted
585 * @since 1.3
586 */
587 private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
588 {
589 // Example: Dk==
590 if( source[ srcOffset + 2] == EQUALS_SIGN )
591 {
592 // Two ways to do the same thing. Don't know which way I like best.
593 //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
594 // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
595 int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
596 | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
597
598 destination[ destOffset ] = (byte)( outBuff >>> 16 );
599 return 1;
600 }
601
602 // Example: DkL=
603 else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
604 {
605 // Two ways to do the same thing. Don't know which way I like best.
606 //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
607 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
608 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
609 int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
610 | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
611 | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
612
613 destination[ destOffset ] = (byte)( outBuff >>> 16 );
614 destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
615 return 2;
616 }
617
618 // Example: DkLE
619 else
620 {
621 try{
622 // Two ways to do the same thing. Don't know which way I like best.
623 //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
624 // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
625 // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
626 // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
627 int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
628 | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
629 | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
630 | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
631
632
633 destination[ destOffset ] = (byte)( outBuff >> 16 );
634 destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
635 destination[ destOffset + 2 ] = (byte)( outBuff );
636
637 return 3;
638 }catch( Exception e){
639 System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) );
640 System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) );
641 System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) );
642 System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) );
643 return -1;
644 } //e nd catch
645 }
646 } // end decodeToBytes
647
648
649
650
651 /**
652 * Very low-level access to decoding ASCII characters in
653 * the form of a byte array. Does not support automatically
654 * gunzipping or any other "fancy" features.
655 *
656 * @param source The Base64 encoded data
657 * @param off The offset of where to begin decoding
658 * @param len The length of characters to decode
659 * @return decoded data
660 * @since 1.3
661 */
662 public static byte[] decode( byte[] source, int off, int len )
663 {
664 int len34 = len * 3 / 4;
665 byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
666 int outBuffPosn = 0;
667
668 byte[] b4 = new byte[4];
669 int b4Posn = 0;
670 int i = 0;
671 byte sbiCrop = 0;
672 byte sbiDecode = 0;
673 for( i = off; i < off+len; i++ )
674 {
675 sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
676 sbiDecode = DECODABET[ sbiCrop ];
677
678 if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
679 {
680 if( sbiDecode >= EQUALS_SIGN_ENC )
681 {
682 b4[ b4Posn++ ] = sbiCrop;
683 if( b4Posn > 3 )
684 {
685 outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
686 b4Posn = 0;
687
688 // If that was the equals sign, break out of 'for' loop
689 if( sbiCrop == EQUALS_SIGN )
690 break;
691 } // end if: quartet built
692
693 } // end if: equals sign or better
694
695 } // end if: white space, equals sign or better
696 else
697 {
698 System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
699 return null;
700 } // end else:
701 } // each input character
702
703 byte[] out = new byte[ outBuffPosn ];
704 System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
705 return out;
706 } // end decode
707
708
709
710
711 /**
712 * Decodes data from Base64 notation, automatically
713 * detecting gzip-compressed data and decompressing it.
714 *
715 * @param s the string to decode
716 * @return the decoded data
717 * @since 1.4
718 */
719 public static byte[] decode( String s )
720 {
721 byte[] bytes = s.getBytes();
722 bytes = decode( bytes, 0, bytes.length );
723
724 // Check to see if it's gzip-compressed
725 // GZIP Magic Two-Byte Number: 0x8b1f (35615)
726 if( bytes.length >= 2 )
727 {
728
729 int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
730 if(
731 bytes != null && // In case decoding returned null
732 bytes.length >= 4 && // Don't want to get ArrayIndexOutOfBounds exception
733 java.util.zip.GZIPInputStream.GZIP_MAGIC == head )
734 {
735 java.io.ByteArrayInputStream bais = null;
736 java.util.zip.GZIPInputStream gzis = null;
737 java.io.ByteArrayOutputStream baos = null;
738 byte[] buffer = new byte[2048];
739 int length = 0;
740
741 try
742 {
743 baos = new java.io.ByteArrayOutputStream();
744 bais = new java.io.ByteArrayInputStream( bytes );
745 gzis = new java.util.zip.GZIPInputStream( bais );
746
747 while( ( length = gzis.read( buffer ) ) >= 0 )
748 {
749 baos.write(buffer,0,length);
750 } // end while: reading input
751
752 // No error? Get new bytes.
753 bytes = baos.toByteArray();
754
755 } // end try
756 catch( java.io.IOException e )
757 {
758 // Just return originally-decoded bytes
759 } // end catch
760 finally
761 {
762 try{ baos.close(); } catch( Exception e ){}
763 try{ gzis.close(); } catch( Exception e ){}
764 try{ bais.close(); } catch( Exception e ){}
765 } // end finally
766
767 } // end if: gzipped
768 } // end if: bytes.length >= 2
769
770 return bytes;
771 } // end decode
772
773
774
775
776 /**
777 * Attempts to decode Base64 data and deserialize a Java
778 * Object within. Returns <tt>null</tt> if there was an error.
779 *
780 * @param encodedObject The Base64 data to decode
781 * @return The decoded and deserialized object
782 * @since 1.5
783 */
784 public static Object decodeToObject( String encodedObject )
785 {
786 // Decode and gunzip if necessary
787 byte[] objBytes = decode( encodedObject );
788
789 java.io.ByteArrayInputStream bais = null;
790 java.io.ObjectInputStream ois = null;
791 Object obj = null;
792
793 try
794 {
795 bais = new java.io.ByteArrayInputStream( objBytes );
796 ois = new java.io.ObjectInputStream( bais );
797
798 obj = ois.readObject();
799 } // end try
800 catch( java.io.IOException e )
801 {
802 e.printStackTrace();
803 obj = null;
804 } // end catch
805 catch( java.lang.ClassNotFoundException e )
806 {
807 e.printStackTrace();
808 obj = null;
809 } // end catch
810 finally
811 {
812 try{ bais.close(); } catch( Exception e ){}
813 try{ ois.close(); } catch( Exception e ){}
814 } // end finally
815
816 return obj;
817 } // end decodeObject
818
819
820 /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
821
822
823
824 /**
825 * It will read data from another
826 * {@link java.io.InputStream}, given in the constructor,
827 * and encode/decode to/from Base64 notation on the fly.
828 *
829 * @see Base64
830 * @see java.io.FilterInputStream
831 * @since 1.3
832 */
833 public static class InputStream extends java.io.FilterInputStream
834 {
835 private int options; // Options specified
836 private boolean encode; // Encoding or decoding
837 private int position; // Current position in the buffer
838 private byte[] buffer; // Small buffer holding converted data
839 private int bufferLength; // Length of buffer (3 or 4)
840 private int numSigBytes; // Number of meaningful bytes in the buffer
841 private int lineLength;
842 private boolean breakLines; // Break lines at less than 80 characters
843
844
845 /**
846 * Constructs a InputStream in DECODE mode.
847 *
848 * @param in the {@link java.io.InputStream} from which to read data.
849 * @since 1.3
850 */
851 public InputStream( java.io.InputStream in )
852 {
853 this( in, DECODE );
854 } // end constructor
855
856
857 /**
858 * Constructs a InputStream in
859 * either ENCODE or DECODE mode.
860 * <p>
861 * Valid options:<pre>
862 * ENCODE or DECODE: Encode or Decode as data is read.
863 * DONT_BREAK_LINES: don't break lines at 76 characters
864 * (only meaningful when encoding)
865 * <i>Note: Technically, this makes your encoding non-compliant.</i>
866 * </pre>
867 * <p>
868 * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
869 *
870 *
871 * @param in the {@link java.io.InputStream} from which to read data.
872 * @param options Specified options
873 * @see Base64#ENCODE
874 * @see Base64#DECODE
875 * @see Base64#DONT_BREAK_LINES
876 * @since 2.0
877 */
878 public InputStream( java.io.InputStream in, int options )
879 {
880 super( in );
881 this.options = options;
882 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
883 this.encode = (options & ENCODE) == ENCODE;
884 this.bufferLength = encode ? 4 : 3;
885 this.buffer = new byte[ bufferLength ];
886 this.position = -1;
887 this.lineLength = 0;
888 } // end constructor
889
890 /**
891 * Reads enough of the input stream to convert
892 * to/from Base64 and returns the next byte.
893 *
894 * @return next byte
895 * @since 1.3
896 */
897 public int read() throws java.io.IOException
898 {
899 // Do we need to get data?
900 if( position < 0 )
901 {
902 if( encode )
903 {
904 byte[] b3 = new byte[3];
905 int numBinaryBytes = 0;
906 for( int i = 0; i < 3; i++ )
907 {
908 try
909 {
910 int b = in.read();
911
912 // If end of stream, b is -1.
913 if( b >= 0 )
914 {
915 b3[i] = (byte)b;
916 numBinaryBytes++;
917 } // end if: not end of stream
918
919 } // end try: read
920 catch( java.io.IOException e )
921 {
922 // Only a problem if we got no data at all.
923 if( i == 0 )
924 throw e;
925
926 } // end catch
927 } // end for: each needed input byte
928
929 if( numBinaryBytes > 0 )
930 {
931 encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
932 position = 0;
933 numSigBytes = 4;
934 } // end if: got data
935 else
936 {
937 return -1;
938 } // end else
939 } // end if: encoding
940
941 // Else decoding
942 else
943 {
944 byte[] b4 = new byte[4];
945 int i = 0;
946 for( i = 0; i < 4; i++ )
947 {
948 // Read four "meaningful" bytes:
949 int b = 0;
950 do{ b = in.read(); }
951 while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
952
953 if( b < 0 )
954 break; // Reads a -1 if end of stream
955
956 b4[i] = (byte)b;
957 } // end for: each needed input byte
958
959 if( i == 4 )
960 {
961 numSigBytes = decode4to3( b4, 0, buffer, 0 );
962 position = 0;
963 } // end if: got four characters
964 else if( i == 0 ){
965 return -1;
966 } // end else if: also padded correctly
967 else
968 {
969 // Must have broken out from above.
970 throw new java.io.IOException( "Improperly padded Base64 input." );
971 } // end
972
973 } // end else: decode
974 } // end else: get data
975
976 // Got data?
977 if( position >= 0 )
978 {
979 // End of relevant data?
980 if( /*!encode &&*/ position >= numSigBytes )
981 return -1;
982
983 if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
984 {
985 lineLength = 0;
986 return '\n';
987 } // end if
988 else
989 {
990 lineLength++; // This isn't important when decoding
991 // but throwing an extra "if" seems
992 // just as wasteful.
993
994 int b = buffer[ position++ ];
995
996 if( position >= bufferLength )
997 position = -1;
998
999 return b & 0xFF; // This is how you "cast" a byte that's
1000 // intended to be unsigned.
1001 } // end else
1002 } // end if: position >= 0
1003
1004 // Else error
1005 else
1006 {
1007 // When JDK1.4 is more accepted, use an assertion here.
1008 throw new java.io.IOException( "Error in Base64 code reading stream." );
1009 } // end else
1010 } // end read
1011
1012
1013 /**
1014 * Calls {@link #read} repeatedly until the end of stream
1015 * is reached or <var>len</var> bytes are read.
1016 * Returns number of bytes read into array or -1 if
1017 * end of stream is encountered.
1018 *
1019 * @param dest array to hold values
1020 * @param off offset for array
1021 * @param len max number of bytes to read into array
1022 * @return bytes read into array or -1 if end of stream is encountered.
1023 * @since 1.3
1024 */
1025 public int read( byte[] dest, int off, int len ) throws java.io.IOException
1026 {
1027 int i;
1028 int b;
1029 for( i = 0; i < len; i++ )
1030 {
1031 b = read();
1032
1033 //if( b < 0 && i == 0 )
1034 // return -1;
1035
1036 if( b >= 0 )
1037 dest[off + i] = (byte)b;
1038 else if( i == 0 )
1039 return -1;
1040 else
1041 break; // Out of 'for' loop
1042 } // end for: each byte read
1043 return i;
1044 } // end read
1045
1046 } // end inner class InputStream
1047
1048
1049
1050
1051
1052
1053 /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
1054
1055
1056
1057 /**
1058 * A OutputStream will write data to another
1059 * {@link java.io.OutputStream}, given in the constructor,
1060 * and encode/decode to/from Base64 notation on the fly.
1061 *
1062 * @see Base64
1063 * @see java.io.FilterOutputStream
1064 * @since 1.3
1065 */
1066 public static class OutputStream extends java.io.FilterOutputStream
1067 {
1068 private int options;
1069 private boolean encode;
1070 private int position;
1071 private byte[] buffer;
1072 private int bufferLength;
1073 private int lineLength;
1074 private boolean breakLines;
1075 private byte[] b4; // Scratch used in a few places
1076 private boolean suspendEncoding;
1077
1078 /**
1079 * Constructs a OutputStream in ENCODE mode.
1080 *
1081 * @param out the {@link java.io.OutputStream} to which data will be written.
1082 * @since 1.3
1083 */
1084 public OutputStream( java.io.OutputStream out )
1085 {
1086 this( out, ENCODE );
1087 } // end constructor
1088
1089
1090 /**
1091 * Constructs a OutputStream in
1092 * either ENCODE or DECODE mode.
1093 * <p>
1094 * Valid options:<pre>
1095 * ENCODE or DECODE: Encode or Decode as data is read.
1096 * DONT_BREAK_LINES: don't break lines at 76 characters
1097 * (only meaningful when encoding)
1098 * <i>Note: Technically, this makes your encoding non-compliant.</i>
1099 * </pre>
1100 * <p>
1101 * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
1102 *
1103 * @param out the {@link java.io.OutputStream} to which data will be written.
1104 * @param options Specified options.
1105 * @see Base64#ENCODE
1106 * @see Base64#DECODE
1107 * @see Base64#DONT_BREAK_LINES
1108 * @since 1.3
1109 */
1110 public OutputStream( java.io.OutputStream out, int options )
1111 {
1112 super( out );
1113 this.options = options;
1114 this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
1115 this.encode = (options & ENCODE) == ENCODE;
1116 this.bufferLength = encode ? 3 : 4;
1117 this.buffer = new byte[ bufferLength ];
1118 this.position = 0;
1119 this.lineLength = 0;
1120 this.suspendEncoding = false;
1121 this.b4 = new byte[4];
1122 } // end constructor
1123
1124
1125 /**
1126 * Writes the byte to the output stream after
1127 * converting to/from Base64 notation.
1128 * When encoding, bytes are buffered three
1129 * at a time before the output stream actually
1130 * gets a write() call.
1131 * When decoding, bytes are buffered four
1132 * at a time.
1133 *
1134 * @param theByte the byte to write
1135 * @since 1.3
1136 */
1137 public void write(int theByte) throws java.io.IOException
1138 {
1139 // Encoding suspended?
1140 if( suspendEncoding )
1141 {
1142 super.out.write( theByte );
1143 return;
1144 } // end if: supsended
1145
1146 // Encode?
1147 if( encode )
1148 {
1149 buffer[ position++ ] = (byte)theByte;
1150 if( position >= bufferLength ) // Enough to encode.
1151 {
1152 out.write( encode3to4( b4, buffer, bufferLength ) );
1153
1154 lineLength += 4;
1155 if( breakLines && lineLength >= MAX_LINE_LENGTH )
1156 {
1157 out.write( NEW_LINE );
1158 lineLength = 0;
1159 } // end if: end of line
1160
1161 position = 0;
1162 } // end if: enough to output
1163 } // end if: encoding
1164
1165 // Else, Decoding
1166 else
1167 {
1168 // Meaningful Base64 character?
1169 if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
1170 {
1171 buffer[ position++ ] = (byte)theByte;
1172 if( position >= bufferLength ) // Enough to output.
1173 {
1174 int len = Base64.decode4to3( buffer, 0, b4, 0 );
1175 out.write( b4, 0, len );
1176 //out.write( Base64.decode4to3( buffer ) );
1177 position = 0;
1178 } // end if: enough to output
1179 } // end if: meaningful base64 character
1180 else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
1181 {
1182 throw new java.io.IOException( "Invalid character in Base64 data." );
1183 } // end else: not white space either
1184 } // end else: decoding
1185 } // end write
1186
1187
1188
1189 /**
1190 * Calls {@link #write} repeatedly until <var>len</var>
1191 * bytes are written.
1192 *
1193 * @param theBytes array from which to read bytes
1194 * @param off offset for array
1195 * @param len max number of bytes to read into array
1196 * @since 1.3
1197 */
1198 public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
1199 {
1200 // Encoding suspended?
1201 if( suspendEncoding )
1202 {
1203 super.out.write( theBytes, off, len );
1204 return;
1205 } // end if: supsended
1206
1207 for( int i = 0; i < len; i++ )
1208 {
1209 write( theBytes[ off + i ] );
1210 } // end for: each byte written
1211
1212 } // end write
1213
1214
1215
1216 /**
1217 * Method added by PHIL. [Thanks, PHIL. -Rob]
1218 * This pads the buffer without closing the stream.
1219 */
1220 public void flushBase64() throws java.io.IOException
1221 {
1222 if( position > 0 )
1223 {
1224 if( encode )
1225 {
1226 out.write( encode3to4( b4, buffer, position ) );
1227 position = 0;
1228 } // end if: encoding
1229 else
1230 {
1231 throw new java.io.IOException( "Base64 input not properly padded." );
1232 } // end else: decoding
1233 } // end if: buffer partially full
1234
1235 } // end flush
1236
1237
1238 /**
1239 * Flushes and closes (I think, in the superclass) the stream.
1240 *
1241 * @since 1.3
1242 */
1243 public void close() throws java.io.IOException
1244 {
1245 // 1. Ensure that pending characters are written
1246 flushBase64();
1247
1248 // 2. Actually close the stream
1249 // Base class both flushes and closes.
1250 super.close();
1251
1252 buffer = null;
1253 out = null;
1254 } // end close
1255
1256
1257
1258 /**
1259 * Suspends encoding of the stream.
1260 * May be helpful if you need to embed a piece of
1261 * base640-encoded data in a stream.
1262 *
1263 * @since 1.5.1
1264 */
1265 public void suspendEncoding() throws java.io.IOException
1266 {
1267 flushBase64();
1268 this.suspendEncoding = true;
1269 } // end suspendEncoding
1270
1271
1272 /**
1273 * Resumes encoding of the stream.
1274 * May be helpful if you need to embed a piece of
1275 * base640-encoded data in a stream.
1276 *
1277 * @since 1.5.1
1278 */
1279 public void resumeEncoding()
1280 {
1281 this.suspendEncoding = false;
1282 } // end resumeEncoding
1283
1284
1285
1286 } // end inner class OutputStream
1287
1288
1289} // end class Base64
Note: See TracBrowser for help on using the repository browser.