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