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