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