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