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