Line 0
Link Here
|
|
|
1 |
/** |
2 |
* <p>Encodes and decodes to and from Base64 notation.</p> |
3 |
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p> |
4 |
* |
5 |
* <p>Example:</p> |
6 |
* |
7 |
* <code>String encoded = Base64.encode( myByteArray );</code> |
8 |
* <br /> |
9 |
* <code>byte[] myByteArray = Base64.decode( encoded );</code> |
10 |
* |
11 |
* <p>The <tt>options</tt> parameter, which appears in a few places, is used to pass |
12 |
* several pieces of information to the encoder. In the "higher level" methods such as |
13 |
* encodeBytes( bytes, options ) the options parameter can be used to indicate such |
14 |
* things as first gzipping the bytes before encoding them, not inserting linefeeds, |
15 |
* and encoding using the URL-safe and Ordered dialects.</p> |
16 |
* |
17 |
* <p>Note, according to <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>, |
18 |
* Section 2.1, implementations should not add line feeds unless explicitly told |
19 |
* to do so. I've got Base64 set to this behavior now, although earlier versions |
20 |
* broke lines by default.</p> |
21 |
* |
22 |
* <p>The constants defined in Base64 can be OR-ed together to combine options, so you |
23 |
* might make a call like this:</p> |
24 |
* |
25 |
* <code>String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES );</code> |
26 |
* <p>to compress the data before encoding it and then making the output have newline characters.</p> |
27 |
* <p>Also...</p> |
28 |
* <code>String encoded = Base64.encodeBytes( crazyString.getBytes() );</code> |
29 |
* |
30 |
* |
31 |
* |
32 |
* <p> |
33 |
* Change Log: |
34 |
* </p> |
35 |
* <ul> |
36 |
* <li>v2.3.7 - Fixed subtle bug when base 64 input stream contained the |
37 |
* value 01111111, which is an invalid base 64 character but should not |
38 |
* throw an ArrayIndexOutOfBoundsException either. Led to discovery of |
39 |
* mishandling (or potential for better handling) of other bad input |
40 |
* characters. You should now get an IOException if you try decoding |
41 |
* something that has bad characters in it.</li> |
42 |
* <li>v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded |
43 |
* string ended in the last column; the buffer was not properly shrunk and |
44 |
* contained an extra (null) byte that made it into the string.</li> |
45 |
* <li>v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size |
46 |
* was wrong for files of size 31, 34, and 37 bytes.</li> |
47 |
* <li>v2.3.4 - Fixed bug when working with gzipped streams whereby flushing |
48 |
* the Base64.OutputStream closed the Base64 encoding (by padding with equals |
49 |
* signs) too soon. Also added an option to suppress the automatic decoding |
50 |
* of gzipped streams. Also added experimental support for specifying a |
51 |
* class loader when using the |
52 |
* {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} |
53 |
* method.</li> |
54 |
* <li>v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java |
55 |
* footprint with its CharEncoders and so forth. Fixed some javadocs that were |
56 |
* inconsistent. Removed imports and specified things like java.io.IOException |
57 |
* explicitly inline.</li> |
58 |
* <li>v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the |
59 |
* final encoded data will be so that the code doesn't have to create two output |
60 |
* arrays: an oversized initial one and then a final, exact-sized one. Big win |
61 |
* when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not |
62 |
* using the gzip options which uses a different mechanism with streams and stuff).</li> |
63 |
* <li>v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some |
64 |
* similar helper methods to be more efficient with memory by not returning a |
65 |
* String but just a byte array.</li> |
66 |
* <li>v2.3 - <strong>This is not a drop-in replacement!</strong> This is two years of comments |
67 |
* and bug fixes queued up and finally executed. Thanks to everyone who sent |
68 |
* me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. |
69 |
* Much bad coding was cleaned up including throwing exceptions where necessary |
70 |
* instead of returning null values or something similar. Here are some changes |
71 |
* that may affect you: |
72 |
* <ul> |
73 |
* <li><em>Does not break lines, by default.</em> This is to keep in compliance with |
74 |
* <a href="http://www.faqs.org/rfcs/rfc3548.html">RFC3548</a>.</li> |
75 |
* <li><em>Throws exceptions instead of returning null values.</em> Because some operations |
76 |
* (especially those that may permit the GZIP option) use IO streams, there |
77 |
* is a possiblity of an java.io.IOException being thrown. After some discussion and |
78 |
* thought, I've changed the behavior of the methods to throw java.io.IOExceptions |
79 |
* rather than return null if ever there's an error. I think this is more |
80 |
* appropriate, though it will require some changes to your code. Sorry, |
81 |
* it should have been done this way to begin with.</li> |
82 |
* <li><em>Removed all references to System.out, System.err, and the like.</em> |
83 |
* Shame on me. All I can say is sorry they were ever there.</li> |
84 |
* <li><em>Throws NullPointerExceptions and IllegalArgumentExceptions</em> as needed |
85 |
* such as when passed arrays are null or offsets are invalid.</li> |
86 |
* <li>Cleaned up as much javadoc as I could to avoid any javadoc warnings. |
87 |
* This was especially annoying before for people who were thorough in their |
88 |
* own projects and then had gobs of javadoc warnings on this file.</li> |
89 |
* </ul> |
90 |
* <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug |
91 |
* when using very small files (~< 40 bytes).</li> |
92 |
* <li>v2.2 - Added some helper methods for encoding/decoding directly from |
93 |
* one file to the next. Also added a main() method to support command line |
94 |
* encoding/decoding from one file to the next. Also added these Base64 dialects: |
95 |
* <ol> |
96 |
* <li>The default is RFC3548 format.</li> |
97 |
* <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates |
98 |
* URL and file name friendly format as described in Section 4 of RFC3548. |
99 |
* http://www.faqs.org/rfcs/rfc3548.html</li> |
100 |
* <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates |
101 |
* URL and file name friendly format that preserves lexical ordering as described |
102 |
* in http://www.faqs.org/qa/rfcc-1940.html</li> |
103 |
* </ol> |
104 |
* Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a> |
105 |
* for contributing the new Base64 dialects. |
106 |
* </li> |
107 |
* |
108 |
* <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added |
109 |
* some convenience methods for reading and writing to and from files.</li> |
110 |
* <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems |
111 |
* with other encodings (like EBCDIC).</li> |
112 |
* <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the |
113 |
* encoded data was a single byte.</li> |
114 |
* <li>v2.0 - I got rid of methods that used booleans to set options. |
115 |
* Now everything is more consolidated and cleaner. The code now detects |
116 |
* when data that's being decoded is gzip-compressed and will decompress it |
117 |
* automatically. Generally things are cleaner. You'll probably have to |
118 |
* change some method calls that you were making to support the new |
119 |
* options format (<tt>int</tt>s that you "OR" together).</li> |
120 |
* <li>v1.5.1 - Fixed bug when decompressing and decoding to a |
121 |
* byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>. |
122 |
* Added the ability to "suspend" encoding in the Output Stream so |
123 |
* you can turn on and off the encoding if you need to embed base64 |
124 |
* data in an otherwise "normal" stream (like an XML file).</li> |
125 |
* <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself. |
126 |
* This helps when using GZIP streams. |
127 |
* Added the ability to GZip-compress objects before encoding them.</li> |
128 |
* <li>v1.4 - Added helper methods to read/write files.</li> |
129 |
* <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li> |
130 |
* <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream |
131 |
* where last buffer being read, if not completely full, was not returned.</li> |
132 |
* <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li> |
133 |
* <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li> |
134 |
* </ul> |
135 |
* |
136 |
* <p> |
137 |
* I am placing this code in the Public Domain. Do with it as you will. |
138 |
* This software comes with no guarantees or warranties but with |
139 |
* plenty of well-wishing instead! |
140 |
* Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a> |
141 |
* periodically to check for updates or to contribute improvements. |
142 |
* </p> |
143 |
* |
144 |
* @author Robert Harder |
145 |
* @author rob@iharder.net |
146 |
* @version 2.3.7 |
147 |
*/ |
148 |
package org.netbeans.modules.db.dataview.util; |
149 |
|
150 |
public class Base64 |
151 |
{ |
152 |
|
153 |
/* ******** P U B L I C F I E L D S ******** */ |
154 |
|
155 |
|
156 |
/** No options specified. Value is zero. */ |
157 |
public final static int NO_OPTIONS = 0; |
158 |
|
159 |
/** Specify encoding in first bit. Value is one. */ |
160 |
public final static int ENCODE = 1; |
161 |
|
162 |
|
163 |
/** Specify decoding in first bit. Value is zero. */ |
164 |
public final static int DECODE = 0; |
165 |
|
166 |
|
167 |
/** Specify that data should be gzip-compressed in second bit. Value is two. */ |
168 |
public final static int GZIP = 2; |
169 |
|
170 |
/** Specify that gzipped data should <em>not</em> be automatically gunzipped. */ |
171 |
public final static int DONT_GUNZIP = 4; |
172 |
|
173 |
|
174 |
/** Do break lines when encoding. Value is 8. */ |
175 |
public final static int DO_BREAK_LINES = 8; |
176 |
|
177 |
/** |
178 |
* Encode using Base64-like encoding that is URL- and Filename-safe as described |
179 |
* in Section 4 of RFC3548: |
180 |
* <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. |
181 |
* It is important to note that data encoded this way is <em>not</em> officially valid Base64, |
182 |
* or at the very least should not be called Base64 without also specifying that is |
183 |
* was encoded using the URL- and Filename-safe dialect. |
184 |
*/ |
185 |
public final static int URL_SAFE = 16; |
186 |
|
187 |
|
188 |
/** |
189 |
* Encode using the special "ordered" dialect of Base64 described here: |
190 |
* <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. |
191 |
*/ |
192 |
public final static int ORDERED = 32; |
193 |
|
194 |
|
195 |
/* ******** P R I V A T E F I E L D S ******** */ |
196 |
|
197 |
|
198 |
/** Maximum line length (76) of Base64 output. */ |
199 |
private final static int MAX_LINE_LENGTH = 76; |
200 |
|
201 |
|
202 |
/** The equals sign (=) as a byte. */ |
203 |
private final static byte EQUALS_SIGN = (byte)'='; |
204 |
|
205 |
|
206 |
/** The new line character (\n) as a byte. */ |
207 |
private final static byte NEW_LINE = (byte)'\n'; |
208 |
|
209 |
|
210 |
/** Preferred encoding. */ |
211 |
private final static String PREFERRED_ENCODING = "US-ASCII"; |
212 |
|
213 |
|
214 |
private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding |
215 |
private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding |
216 |
|
217 |
|
218 |
/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ |
219 |
|
220 |
/** The 64 valid Base64 values. */ |
221 |
/* Host platform me be something funny like EBCDIC, so we hardcode these values. */ |
222 |
private final static byte[] _STANDARD_ALPHABET = { |
223 |
(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', |
224 |
(byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', |
225 |
(byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', |
226 |
(byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', |
227 |
(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', |
228 |
(byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', |
229 |
(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', |
230 |
(byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', |
231 |
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', |
232 |
(byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' |
233 |
}; |
234 |
|
235 |
|
236 |
/** |
237 |
* Translates a Base64 value to either its 6-bit reconstruction value |
238 |
* or a negative number indicating some other meaning. |
239 |
**/ |
240 |
private final static byte[] _STANDARD_DECODABET = { |
241 |
-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 |
242 |
-5,-5, // Whitespace: Tab and Linefeed |
243 |
-9,-9, // Decimal 11 - 12 |
244 |
-5, // Whitespace: Carriage Return |
245 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 |
246 |
-9,-9,-9,-9,-9, // Decimal 27 - 31 |
247 |
-5, // Whitespace: Space |
248 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 |
249 |
62, // Plus sign at decimal 43 |
250 |
-9,-9,-9, // Decimal 44 - 46 |
251 |
63, // Slash at decimal 47 |
252 |
52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine |
253 |
-9,-9,-9, // Decimal 58 - 60 |
254 |
-1, // Equals sign at decimal 61 |
255 |
-9,-9,-9, // Decimal 62 - 64 |
256 |
0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' |
257 |
14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' |
258 |
-9,-9,-9,-9,-9,-9, // Decimal 91 - 96 |
259 |
26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' |
260 |
39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' |
261 |
-9,-9,-9,-9,-9 // Decimal 123 - 127 |
262 |
,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 |
263 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 |
264 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 |
265 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 |
266 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 |
267 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 |
268 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 |
269 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 |
270 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 |
271 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 |
272 |
}; |
273 |
|
274 |
|
275 |
/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ |
276 |
|
277 |
/** |
278 |
* Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: |
279 |
* <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. |
280 |
* Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." |
281 |
*/ |
282 |
private final static byte[] _URL_SAFE_ALPHABET = { |
283 |
(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', |
284 |
(byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', |
285 |
(byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', |
286 |
(byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', |
287 |
(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', |
288 |
(byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', |
289 |
(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', |
290 |
(byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', |
291 |
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', |
292 |
(byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' |
293 |
}; |
294 |
|
295 |
/** |
296 |
* Used in decoding URL- and Filename-safe dialects of Base64. |
297 |
*/ |
298 |
private final static byte[] _URL_SAFE_DECODABET = { |
299 |
-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 |
300 |
-5,-5, // Whitespace: Tab and Linefeed |
301 |
-9,-9, // Decimal 11 - 12 |
302 |
-5, // Whitespace: Carriage Return |
303 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 |
304 |
-9,-9,-9,-9,-9, // Decimal 27 - 31 |
305 |
-5, // Whitespace: Space |
306 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 |
307 |
-9, // Plus sign at decimal 43 |
308 |
-9, // Decimal 44 |
309 |
62, // Minus sign at decimal 45 |
310 |
-9, // Decimal 46 |
311 |
-9, // Slash at decimal 47 |
312 |
52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine |
313 |
-9,-9,-9, // Decimal 58 - 60 |
314 |
-1, // Equals sign at decimal 61 |
315 |
-9,-9,-9, // Decimal 62 - 64 |
316 |
0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' |
317 |
14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' |
318 |
-9,-9,-9,-9, // Decimal 91 - 94 |
319 |
63, // Underscore at decimal 95 |
320 |
-9, // Decimal 96 |
321 |
26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' |
322 |
39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' |
323 |
-9,-9,-9,-9,-9 // Decimal 123 - 127 |
324 |
,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 |
325 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 |
326 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 |
327 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 |
328 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 |
329 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 |
330 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 |
331 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 |
332 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 |
333 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 |
334 |
}; |
335 |
|
336 |
|
337 |
|
338 |
/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ |
339 |
|
340 |
/** |
341 |
* I don't get the point of this technique, but someone requested it, |
342 |
* and it is described here: |
343 |
* <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>. |
344 |
*/ |
345 |
private final static byte[] _ORDERED_ALPHABET = { |
346 |
(byte)'-', |
347 |
(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', |
348 |
(byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', |
349 |
(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', |
350 |
(byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', |
351 |
(byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', |
352 |
(byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', |
353 |
(byte)'_', |
354 |
(byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', |
355 |
(byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', |
356 |
(byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', |
357 |
(byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' |
358 |
}; |
359 |
|
360 |
/** |
361 |
* Used in decoding the "ordered" dialect of Base64. |
362 |
*/ |
363 |
private final static byte[] _ORDERED_DECODABET = { |
364 |
-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 |
365 |
-5,-5, // Whitespace: Tab and Linefeed |
366 |
-9,-9, // Decimal 11 - 12 |
367 |
-5, // Whitespace: Carriage Return |
368 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 |
369 |
-9,-9,-9,-9,-9, // Decimal 27 - 31 |
370 |
-5, // Whitespace: Space |
371 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 |
372 |
-9, // Plus sign at decimal 43 |
373 |
-9, // Decimal 44 |
374 |
0, // Minus sign at decimal 45 |
375 |
-9, // Decimal 46 |
376 |
-9, // Slash at decimal 47 |
377 |
1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine |
378 |
-9,-9,-9, // Decimal 58 - 60 |
379 |
-1, // Equals sign at decimal 61 |
380 |
-9,-9,-9, // Decimal 62 - 64 |
381 |
11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' |
382 |
24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' |
383 |
-9,-9,-9,-9, // Decimal 91 - 94 |
384 |
37, // Underscore at decimal 95 |
385 |
-9, // Decimal 96 |
386 |
38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' |
387 |
51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' |
388 |
-9,-9,-9,-9,-9 // Decimal 123 - 127 |
389 |
,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 |
390 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 |
391 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 |
392 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 |
393 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 |
394 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 |
395 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 |
396 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 |
397 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 |
398 |
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 |
399 |
}; |
400 |
|
401 |
|
402 |
/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ |
403 |
|
404 |
|
405 |
/** |
406 |
* Returns one of the _SOMETHING_ALPHABET byte arrays depending on |
407 |
* the options specified. |
408 |
* It's possible, though silly, to specify ORDERED <b>and</b> URLSAFE |
409 |
* in which case one of them will be picked, though there is |
410 |
* no guarantee as to which one will be picked. |
411 |
*/ |
412 |
private final static byte[] getAlphabet( int options ) { |
413 |
if ((options & URL_SAFE) == URL_SAFE) { |
414 |
return _URL_SAFE_ALPHABET; |
415 |
} else if ((options & ORDERED) == ORDERED) { |
416 |
return _ORDERED_ALPHABET; |
417 |
} else { |
418 |
return _STANDARD_ALPHABET; |
419 |
} |
420 |
} // end getAlphabet |
421 |
|
422 |
|
423 |
/** |
424 |
* Returns one of the _SOMETHING_DECODABET byte arrays depending on |
425 |
* the options specified. |
426 |
* It's possible, though silly, to specify ORDERED and URL_SAFE |
427 |
* in which case one of them will be picked, though there is |
428 |
* no guarantee as to which one will be picked. |
429 |
*/ |
430 |
private final static byte[] getDecodabet( int options ) { |
431 |
if( (options & URL_SAFE) == URL_SAFE) { |
432 |
return _URL_SAFE_DECODABET; |
433 |
} else if ((options & ORDERED) == ORDERED) { |
434 |
return _ORDERED_DECODABET; |
435 |
} else { |
436 |
return _STANDARD_DECODABET; |
437 |
} |
438 |
} // end getAlphabet |
439 |
|
440 |
|
441 |
|
442 |
/** Defeats instantiation. */ |
443 |
private Base64(){} |
444 |
|
445 |
|
446 |
|
447 |
|
448 |
/* ******** E N C O D I N G M E T H O D S ******** */ |
449 |
|
450 |
|
451 |
/** |
452 |
* Encodes up to the first three bytes of array <var>threeBytes</var> |
453 |
* and returns a four-byte array in Base64 notation. |
454 |
* The actual number of significant bytes in your array is |
455 |
* given by <var>numSigBytes</var>. |
456 |
* The array <var>threeBytes</var> needs only be as big as |
457 |
* <var>numSigBytes</var>. |
458 |
* Code can reuse a byte array by passing a four-byte array as <var>b4</var>. |
459 |
* |
460 |
* @param b4 A reusable byte array to reduce array instantiation |
461 |
* @param threeBytes the array to convert |
462 |
* @param numSigBytes the number of significant bytes in your array |
463 |
* @return four byte array in Base64 notation. |
464 |
* @since 1.5.1 |
465 |
*/ |
466 |
private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { |
467 |
encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); |
468 |
return b4; |
469 |
} // end encode3to4 |
470 |
|
471 |
|
472 |
/** |
473 |
* <p>Encodes up to three bytes of the array <var>source</var> |
474 |
* and writes the resulting four Base64 bytes to <var>destination</var>. |
475 |
* The source and destination arrays can be manipulated |
476 |
* anywhere along their length by specifying |
477 |
* <var>srcOffset</var> and <var>destOffset</var>. |
478 |
* This method does not check to make sure your arrays |
479 |
* are large enough to accomodate <var>srcOffset</var> + 3 for |
480 |
* the <var>source</var> array or <var>destOffset</var> + 4 for |
481 |
* the <var>destination</var> array. |
482 |
* The actual number of significant bytes in your array is |
483 |
* given by <var>numSigBytes</var>.</p> |
484 |
* <p>This is the lowest level of the encoding methods with |
485 |
* all possible parameters.</p> |
486 |
* |
487 |
* @param source the array to convert |
488 |
* @param srcOffset the index where conversion begins |
489 |
* @param numSigBytes the number of significant bytes in your array |
490 |
* @param destination the array to hold the conversion |
491 |
* @param destOffset the index where output will be put |
492 |
* @return the <var>destination</var> array |
493 |
* @since 1.3 |
494 |
*/ |
495 |
private static byte[] encode3to4( |
496 |
byte[] source, int srcOffset, int numSigBytes, |
497 |
byte[] destination, int destOffset, int options ) { |
498 |
|
499 |
byte[] ALPHABET = getAlphabet( options ); |
500 |
|
501 |
// 1 2 3 |
502 |
// 01234567890123456789012345678901 Bit position |
503 |
// --------000000001111111122222222 Array position from threeBytes |
504 |
// --------| || || || | Six bit groups to index ALPHABET |
505 |
// >>18 >>12 >> 6 >> 0 Right shift necessary |
506 |
// 0x3f 0x3f 0x3f Additional AND |
507 |
|
508 |
// Create buffer with zero-padding if there are only one or two |
509 |
// significant bytes passed in the array. |
510 |
// We have to shift left 24 in order to flush out the 1's that appear |
511 |
// when Java treats a value as negative that is cast from a byte to an int. |
512 |
int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) |
513 |
| ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) |
514 |
| ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); |
515 |
|
516 |
switch( numSigBytes ) |
517 |
{ |
518 |
case 3: |
519 |
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; |
520 |
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; |
521 |
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; |
522 |
destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; |
523 |
return destination; |
524 |
|
525 |
case 2: |
526 |
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; |
527 |
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; |
528 |
destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; |
529 |
destination[ destOffset + 3 ] = EQUALS_SIGN; |
530 |
return destination; |
531 |
|
532 |
case 1: |
533 |
destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; |
534 |
destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; |
535 |
destination[ destOffset + 2 ] = EQUALS_SIGN; |
536 |
destination[ destOffset + 3 ] = EQUALS_SIGN; |
537 |
return destination; |
538 |
|
539 |
default: |
540 |
return destination; |
541 |
} // end switch |
542 |
} // end encode3to4 |
543 |
|
544 |
|
545 |
|
546 |
/** |
547 |
* Performs Base64 encoding on the <code>raw</code> ByteBuffer, |
548 |
* writing it to the <code>encoded</code> ByteBuffer. |
549 |
* This is an experimental feature. Currently it does not |
550 |
* pass along any options (such as {@link #DO_BREAK_LINES} |
551 |
* or {@link #GZIP}. |
552 |
* |
553 |
* @param raw input buffer |
554 |
* @param encoded output buffer |
555 |
* @since 2.3 |
556 |
*/ |
557 |
public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ |
558 |
byte[] raw3 = new byte[3]; |
559 |
byte[] enc4 = new byte[4]; |
560 |
|
561 |
while( raw.hasRemaining() ){ |
562 |
int rem = Math.min(3,raw.remaining()); |
563 |
raw.get(raw3,0,rem); |
564 |
Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); |
565 |
encoded.put(enc4); |
566 |
} // end input remaining |
567 |
} |
568 |
|
569 |
|
570 |
/** |
571 |
* Performs Base64 encoding on the <code>raw</code> ByteBuffer, |
572 |
* writing it to the <code>encoded</code> CharBuffer. |
573 |
* This is an experimental feature. Currently it does not |
574 |
* pass along any options (such as {@link #DO_BREAK_LINES} |
575 |
* or {@link #GZIP}. |
576 |
* |
577 |
* @param raw input buffer |
578 |
* @param encoded output buffer |
579 |
* @since 2.3 |
580 |
*/ |
581 |
public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ |
582 |
byte[] raw3 = new byte[3]; |
583 |
byte[] enc4 = new byte[4]; |
584 |
|
585 |
while( raw.hasRemaining() ){ |
586 |
int rem = Math.min(3,raw.remaining()); |
587 |
raw.get(raw3,0,rem); |
588 |
Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); |
589 |
for( int i = 0; i < 4; i++ ){ |
590 |
encoded.put( (char)(enc4[i] & 0xFF) ); |
591 |
} |
592 |
} // end input remaining |
593 |
} |
594 |
|
595 |
|
596 |
|
597 |
|
598 |
/** |
599 |
* Serializes an object and returns the Base64-encoded |
600 |
* version of that serialized object. |
601 |
* |
602 |
* <p>As of v 2.3, if the object |
603 |
* cannot be serialized or there is another error, |
604 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
605 |
* In earlier versions, it just returned a null value, but |
606 |
* in retrospect that's a pretty poor way to handle it.</p> |
607 |
* |
608 |
* The object is not GZip-compressed before being encoded. |
609 |
* |
610 |
* @param serializableObject The object to encode |
611 |
* @return The Base64-encoded object |
612 |
* @throws java.io.IOException if there is an error |
613 |
* @throws NullPointerException if serializedObject is null |
614 |
* @since 1.4 |
615 |
*/ |
616 |
public static String encodeObject( java.io.Serializable serializableObject ) |
617 |
throws java.io.IOException { |
618 |
return encodeObject( serializableObject, NO_OPTIONS ); |
619 |
} // end encodeObject |
620 |
|
621 |
|
622 |
|
623 |
/** |
624 |
* Serializes an object and returns the Base64-encoded |
625 |
* version of that serialized object. |
626 |
* |
627 |
* <p>As of v 2.3, if the object |
628 |
* cannot be serialized or there is another error, |
629 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
630 |
* In earlier versions, it just returned a null value, but |
631 |
* in retrospect that's a pretty poor way to handle it.</p> |
632 |
* |
633 |
* The object is not GZip-compressed before being encoded. |
634 |
* <p> |
635 |
* Example options:<pre> |
636 |
* GZIP: gzip-compresses object before encoding it. |
637 |
* DO_BREAK_LINES: break lines at 76 characters |
638 |
* </pre> |
639 |
* <p> |
640 |
* Example: <code>encodeObject( myObj, Base64.GZIP )</code> or |
641 |
* <p> |
642 |
* Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES )</code> |
643 |
* |
644 |
* @param serializableObject The object to encode |
645 |
* @param options Specified options |
646 |
* @return The Base64-encoded object |
647 |
* @see Base64#GZIP |
648 |
* @see Base64#DO_BREAK_LINES |
649 |
* @throws java.io.IOException if there is an error |
650 |
* @since 2.0 |
651 |
*/ |
652 |
public static String encodeObject( java.io.Serializable serializableObject, int options ) |
653 |
throws java.io.IOException { |
654 |
|
655 |
if( serializableObject == null ){ |
656 |
throw new NullPointerException( "Cannot serialize a null object." ); |
657 |
} // end if: null |
658 |
|
659 |
// Streams |
660 |
java.io.ByteArrayOutputStream baos = null; |
661 |
java.io.OutputStream b64os = null; |
662 |
java.util.zip.GZIPOutputStream gzos = null; |
663 |
java.io.ObjectOutputStream oos = null; |
664 |
|
665 |
|
666 |
try { |
667 |
// ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream |
668 |
baos = new java.io.ByteArrayOutputStream(); |
669 |
b64os = new Base64.OutputStream( baos, ENCODE | options ); |
670 |
if( (options & GZIP) != 0 ){ |
671 |
// Gzip |
672 |
gzos = new java.util.zip.GZIPOutputStream(b64os); |
673 |
oos = new java.io.ObjectOutputStream( gzos ); |
674 |
} else { |
675 |
// Not gzipped |
676 |
oos = new java.io.ObjectOutputStream( b64os ); |
677 |
} |
678 |
oos.writeObject( serializableObject ); |
679 |
} // end try |
680 |
catch( java.io.IOException e ) { |
681 |
// Catch it and then throw it immediately so that |
682 |
// the finally{} block is called for cleanup. |
683 |
throw e; |
684 |
} // end catch |
685 |
finally { |
686 |
try{ oos.close(); } catch( Exception e ){} |
687 |
try{ gzos.close(); } catch( Exception e ){} |
688 |
try{ b64os.close(); } catch( Exception e ){} |
689 |
try{ baos.close(); } catch( Exception e ){} |
690 |
} // end finally |
691 |
|
692 |
// Return value according to relevant encoding. |
693 |
try { |
694 |
return new String( baos.toByteArray(), PREFERRED_ENCODING ); |
695 |
} // end try |
696 |
catch (java.io.UnsupportedEncodingException uue){ |
697 |
// Fall back to some Java default |
698 |
return new String( baos.toByteArray() ); |
699 |
} // end catch |
700 |
|
701 |
} // end encode |
702 |
|
703 |
|
704 |
|
705 |
/** |
706 |
* Encodes a byte array into Base64 notation. |
707 |
* Does not GZip-compress data. |
708 |
* |
709 |
* @param source The data to convert |
710 |
* @return The data in Base64-encoded form |
711 |
* @throws NullPointerException if source array is null |
712 |
* @since 1.4 |
713 |
*/ |
714 |
public static String encodeBytes( byte[] source ) { |
715 |
// Since we're not going to have the GZIP encoding turned on, |
716 |
// we're not going to have an java.io.IOException thrown, so |
717 |
// we should not force the user to have to catch it. |
718 |
String encoded = null; |
719 |
try { |
720 |
encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); |
721 |
} catch (java.io.IOException ex) { |
722 |
assert false : ex.getMessage(); |
723 |
} // end catch |
724 |
assert encoded != null; |
725 |
return encoded; |
726 |
} // end encodeBytes |
727 |
|
728 |
|
729 |
|
730 |
/** |
731 |
* Encodes a byte array into Base64 notation. |
732 |
* <p> |
733 |
* Example options:<pre> |
734 |
* GZIP: gzip-compresses object before encoding it. |
735 |
* DO_BREAK_LINES: break lines at 76 characters |
736 |
* <i>Note: Technically, this makes your encoding non-compliant.</i> |
737 |
* </pre> |
738 |
* <p> |
739 |
* Example: <code>encodeBytes( myData, Base64.GZIP )</code> or |
740 |
* <p> |
741 |
* Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> |
742 |
* |
743 |
* |
744 |
* <p>As of v 2.3, if there is an error with the GZIP stream, |
745 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
746 |
* In earlier versions, it just returned a null value, but |
747 |
* in retrospect that's a pretty poor way to handle it.</p> |
748 |
* |
749 |
* |
750 |
* @param source The data to convert |
751 |
* @param options Specified options |
752 |
* @return The Base64-encoded data as a String |
753 |
* @see Base64#GZIP |
754 |
* @see Base64#DO_BREAK_LINES |
755 |
* @throws java.io.IOException if there is an error |
756 |
* @throws NullPointerException if source array is null |
757 |
* @since 2.0 |
758 |
*/ |
759 |
public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { |
760 |
return encodeBytes( source, 0, source.length, options ); |
761 |
} // end encodeBytes |
762 |
|
763 |
|
764 |
/** |
765 |
* Encodes a byte array into Base64 notation. |
766 |
* Does not GZip-compress data. |
767 |
* |
768 |
* <p>As of v 2.3, if there is an error, |
769 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
770 |
* In earlier versions, it just returned a null value, but |
771 |
* in retrospect that's a pretty poor way to handle it.</p> |
772 |
* |
773 |
* |
774 |
* @param source The data to convert |
775 |
* @param off Offset in array where conversion should begin |
776 |
* @param len Length of data to convert |
777 |
* @return The Base64-encoded data as a String |
778 |
* @throws NullPointerException if source array is null |
779 |
* @throws IllegalArgumentException if source array, offset, or length are invalid |
780 |
* @since 1.4 |
781 |
*/ |
782 |
public static String encodeBytes( byte[] source, int off, int len ) { |
783 |
// Since we're not going to have the GZIP encoding turned on, |
784 |
// we're not going to have an java.io.IOException thrown, so |
785 |
// we should not force the user to have to catch it. |
786 |
String encoded = null; |
787 |
try { |
788 |
encoded = encodeBytes( source, off, len, NO_OPTIONS ); |
789 |
} catch (java.io.IOException ex) { |
790 |
assert false : ex.getMessage(); |
791 |
} // end catch |
792 |
assert encoded != null; |
793 |
return encoded; |
794 |
} // end encodeBytes |
795 |
|
796 |
|
797 |
|
798 |
/** |
799 |
* Encodes a byte array into Base64 notation. |
800 |
* <p> |
801 |
* Example options:<pre> |
802 |
* GZIP: gzip-compresses object before encoding it. |
803 |
* DO_BREAK_LINES: break lines at 76 characters |
804 |
* <i>Note: Technically, this makes your encoding non-compliant.</i> |
805 |
* </pre> |
806 |
* <p> |
807 |
* Example: <code>encodeBytes( myData, Base64.GZIP )</code> or |
808 |
* <p> |
809 |
* Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES )</code> |
810 |
* |
811 |
* |
812 |
* <p>As of v 2.3, if there is an error with the GZIP stream, |
813 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
814 |
* In earlier versions, it just returned a null value, but |
815 |
* in retrospect that's a pretty poor way to handle it.</p> |
816 |
* |
817 |
* |
818 |
* @param source The data to convert |
819 |
* @param off Offset in array where conversion should begin |
820 |
* @param len Length of data to convert |
821 |
* @param options Specified options |
822 |
* @return The Base64-encoded data as a String |
823 |
* @see Base64#GZIP |
824 |
* @see Base64#DO_BREAK_LINES |
825 |
* @throws java.io.IOException if there is an error |
826 |
* @throws NullPointerException if source array is null |
827 |
* @throws IllegalArgumentException if source array, offset, or length are invalid |
828 |
* @since 2.0 |
829 |
*/ |
830 |
public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { |
831 |
byte[] encoded = encodeBytesToBytes( source, off, len, options ); |
832 |
|
833 |
// Return value according to relevant encoding. |
834 |
try { |
835 |
return new String( encoded, PREFERRED_ENCODING ); |
836 |
} // end try |
837 |
catch (java.io.UnsupportedEncodingException uue) { |
838 |
return new String( encoded ); |
839 |
} // end catch |
840 |
|
841 |
} // end encodeBytes |
842 |
|
843 |
|
844 |
|
845 |
|
846 |
/** |
847 |
* Similar to {@link #encodeBytes(byte[])} but returns |
848 |
* a byte array instead of instantiating a String. This is more efficient |
849 |
* if you're working with I/O streams and have large data sets to encode. |
850 |
* |
851 |
* |
852 |
* @param source The data to convert |
853 |
* @return The Base64-encoded data as a byte[] (of ASCII characters) |
854 |
* @throws NullPointerException if source array is null |
855 |
* @since 2.3.1 |
856 |
*/ |
857 |
public static byte[] encodeBytesToBytes( byte[] source ) { |
858 |
byte[] encoded = null; |
859 |
try { |
860 |
encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); |
861 |
} catch( java.io.IOException ex ) { |
862 |
assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); |
863 |
} |
864 |
return encoded; |
865 |
} |
866 |
|
867 |
|
868 |
/** |
869 |
* Similar to {@link #encodeBytes(byte[], int, int, int)} but returns |
870 |
* a byte array instead of instantiating a String. This is more efficient |
871 |
* if you're working with I/O streams and have large data sets to encode. |
872 |
* |
873 |
* |
874 |
* @param source The data to convert |
875 |
* @param off Offset in array where conversion should begin |
876 |
* @param len Length of data to convert |
877 |
* @param options Specified options |
878 |
* @return The Base64-encoded data as a String |
879 |
* @see Base64#GZIP |
880 |
* @see Base64#DO_BREAK_LINES |
881 |
* @throws java.io.IOException if there is an error |
882 |
* @throws NullPointerException if source array is null |
883 |
* @throws IllegalArgumentException if source array, offset, or length are invalid |
884 |
* @since 2.3.1 |
885 |
*/ |
886 |
public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { |
887 |
|
888 |
if( source == null ){ |
889 |
throw new NullPointerException( "Cannot serialize a null array." ); |
890 |
} // end if: null |
891 |
|
892 |
if( off < 0 ){ |
893 |
throw new IllegalArgumentException( "Cannot have negative offset: " + off ); |
894 |
} // end if: off < 0 |
895 |
|
896 |
if( len < 0 ){ |
897 |
throw new IllegalArgumentException( "Cannot have length offset: " + len ); |
898 |
} // end if: len < 0 |
899 |
|
900 |
if( off + len > source.length ){ |
901 |
throw new IllegalArgumentException( |
902 |
String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); |
903 |
} // end if: off < 0 |
904 |
|
905 |
|
906 |
|
907 |
// Compress? |
908 |
if( (options & GZIP) != 0 ) { |
909 |
java.io.ByteArrayOutputStream baos = null; |
910 |
java.util.zip.GZIPOutputStream gzos = null; |
911 |
Base64.OutputStream b64os = null; |
912 |
|
913 |
try { |
914 |
// GZip -> Base64 -> ByteArray |
915 |
baos = new java.io.ByteArrayOutputStream(); |
916 |
b64os = new Base64.OutputStream( baos, ENCODE | options ); |
917 |
gzos = new java.util.zip.GZIPOutputStream( b64os ); |
918 |
|
919 |
gzos.write( source, off, len ); |
920 |
gzos.close(); |
921 |
} // end try |
922 |
catch( java.io.IOException e ) { |
923 |
// Catch it and then throw it immediately so that |
924 |
// the finally{} block is called for cleanup. |
925 |
throw e; |
926 |
} // end catch |
927 |
finally { |
928 |
try{ gzos.close(); } catch( Exception e ){} |
929 |
try{ b64os.close(); } catch( Exception e ){} |
930 |
try{ baos.close(); } catch( Exception e ){} |
931 |
} // end finally |
932 |
|
933 |
return baos.toByteArray(); |
934 |
} // end if: compress |
935 |
|
936 |
// Else, don't compress. Better not to use streams at all then. |
937 |
else { |
938 |
boolean breakLines = (options & DO_BREAK_LINES) != 0; |
939 |
|
940 |
//int len43 = len * 4 / 3; |
941 |
//byte[] outBuff = new byte[ ( len43 ) // Main 4:3 |
942 |
// + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding |
943 |
// + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines |
944 |
// Try to determine more precisely how big the array needs to be. |
945 |
// If we get it right, we don't have to do an array copy, and |
946 |
// we save a bunch of memory. |
947 |
int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding |
948 |
if( breakLines ){ |
949 |
encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters |
950 |
} |
951 |
byte[] outBuff = new byte[ encLen ]; |
952 |
|
953 |
|
954 |
int d = 0; |
955 |
int e = 0; |
956 |
int len2 = len - 2; |
957 |
int lineLength = 0; |
958 |
for( ; d < len2; d+=3, e+=4 ) { |
959 |
encode3to4( source, d+off, 3, outBuff, e, options ); |
960 |
|
961 |
lineLength += 4; |
962 |
if( breakLines && lineLength >= MAX_LINE_LENGTH ) |
963 |
{ |
964 |
outBuff[e+4] = NEW_LINE; |
965 |
e++; |
966 |
lineLength = 0; |
967 |
} // end if: end of line |
968 |
} // en dfor: each piece of array |
969 |
|
970 |
if( d < len ) { |
971 |
encode3to4( source, d+off, len - d, outBuff, e, options ); |
972 |
e += 4; |
973 |
} // end if: some padding needed |
974 |
|
975 |
|
976 |
// Only resize array if we didn't guess it right. |
977 |
if( e <= outBuff.length - 1 ){ |
978 |
// If breaking lines and the last byte falls right at |
979 |
// the line length (76 bytes per line), there will be |
980 |
// one extra byte, and the array will need to be resized. |
981 |
// Not too bad of an estimate on array size, I'd say. |
982 |
byte[] finalOut = new byte[e]; |
983 |
System.arraycopy(outBuff,0, finalOut,0,e); |
984 |
//System.err.println("Having to resize array from " + outBuff.length + " to " + e ); |
985 |
return finalOut; |
986 |
} else { |
987 |
//System.err.println("No need to resize array."); |
988 |
return outBuff; |
989 |
} |
990 |
|
991 |
} // end else: don't compress |
992 |
|
993 |
} // end encodeBytesToBytes |
994 |
|
995 |
|
996 |
|
997 |
|
998 |
|
999 |
/* ******** D E C O D I N G M E T H O D S ******** */ |
1000 |
|
1001 |
|
1002 |
/** |
1003 |
* Decodes four bytes from array <var>source</var> |
1004 |
* and writes the resulting bytes (up to three of them) |
1005 |
* to <var>destination</var>. |
1006 |
* The source and destination arrays can be manipulated |
1007 |
* anywhere along their length by specifying |
1008 |
* <var>srcOffset</var> and <var>destOffset</var>. |
1009 |
* This method does not check to make sure your arrays |
1010 |
* are large enough to accomodate <var>srcOffset</var> + 4 for |
1011 |
* the <var>source</var> array or <var>destOffset</var> + 3 for |
1012 |
* the <var>destination</var> array. |
1013 |
* This method returns the actual number of bytes that |
1014 |
* were converted from the Base64 encoding. |
1015 |
* <p>This is the lowest level of the decoding methods with |
1016 |
* all possible parameters.</p> |
1017 |
* |
1018 |
* |
1019 |
* @param source the array to convert |
1020 |
* @param srcOffset the index where conversion begins |
1021 |
* @param destination the array to hold the conversion |
1022 |
* @param destOffset the index where output will be put |
1023 |
* @param options alphabet type is pulled from this (standard, url-safe, ordered) |
1024 |
* @return the number of decoded bytes converted |
1025 |
* @throws NullPointerException if source or destination arrays are null |
1026 |
* @throws IllegalArgumentException if srcOffset or destOffset are invalid |
1027 |
* or there is not enough room in the array. |
1028 |
* @since 1.3 |
1029 |
*/ |
1030 |
private static int decode4to3( |
1031 |
byte[] source, int srcOffset, |
1032 |
byte[] destination, int destOffset, int options ) { |
1033 |
|
1034 |
// Lots of error checking and exception throwing |
1035 |
if( source == null ){ |
1036 |
throw new NullPointerException( "Source array was null." ); |
1037 |
} // end if |
1038 |
if( destination == null ){ |
1039 |
throw new NullPointerException( "Destination array was null." ); |
1040 |
} // end if |
1041 |
if( srcOffset < 0 || srcOffset + 3 >= source.length ){ |
1042 |
throw new IllegalArgumentException( String.format( |
1043 |
"Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); |
1044 |
} // end if |
1045 |
if( destOffset < 0 || destOffset +2 >= destination.length ){ |
1046 |
throw new IllegalArgumentException( String.format( |
1047 |
"Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); |
1048 |
} // end if |
1049 |
|
1050 |
|
1051 |
byte[] DECODABET = getDecodabet( options ); |
1052 |
|
1053 |
// Example: Dk== |
1054 |
if( source[ srcOffset + 2] == EQUALS_SIGN ) { |
1055 |
// Two ways to do the same thing. Don't know which way I like best. |
1056 |
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) |
1057 |
// | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); |
1058 |
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) |
1059 |
| ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); |
1060 |
|
1061 |
destination[ destOffset ] = (byte)( outBuff >>> 16 ); |
1062 |
return 1; |
1063 |
} |
1064 |
|
1065 |
// Example: DkL= |
1066 |
else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { |
1067 |
// Two ways to do the same thing. Don't know which way I like best. |
1068 |
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) |
1069 |
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) |
1070 |
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); |
1071 |
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) |
1072 |
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) |
1073 |
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); |
1074 |
|
1075 |
destination[ destOffset ] = (byte)( outBuff >>> 16 ); |
1076 |
destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); |
1077 |
return 2; |
1078 |
} |
1079 |
|
1080 |
// Example: DkLE |
1081 |
else { |
1082 |
// Two ways to do the same thing. Don't know which way I like best. |
1083 |
//int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) |
1084 |
// | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) |
1085 |
// | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) |
1086 |
// | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); |
1087 |
int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) |
1088 |
| ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) |
1089 |
| ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) |
1090 |
| ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); |
1091 |
|
1092 |
|
1093 |
destination[ destOffset ] = (byte)( outBuff >> 16 ); |
1094 |
destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); |
1095 |
destination[ destOffset + 2 ] = (byte)( outBuff ); |
1096 |
|
1097 |
return 3; |
1098 |
} |
1099 |
} // end decodeToBytes |
1100 |
|
1101 |
|
1102 |
|
1103 |
|
1104 |
|
1105 |
/** |
1106 |
* Low-level access to decoding ASCII characters in |
1107 |
* the form of a byte array. <strong>Ignores GUNZIP option, if |
1108 |
* it's set.</strong> This is not generally a recommended method, |
1109 |
* although it is used internally as part of the decoding process. |
1110 |
* Special case: if len = 0, an empty array is returned. Still, |
1111 |
* if you need more speed and reduced memory footprint (and aren't |
1112 |
* gzipping), consider this method. |
1113 |
* |
1114 |
* @param source The Base64 encoded data |
1115 |
* @return decoded data |
1116 |
* @since 2.3.1 |
1117 |
*/ |
1118 |
public static byte[] decode( byte[] source ) |
1119 |
throws java.io.IOException { |
1120 |
byte[] decoded = null; |
1121 |
// try { |
1122 |
decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); |
1123 |
// } catch( java.io.IOException ex ) { |
1124 |
// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); |
1125 |
// } |
1126 |
return decoded; |
1127 |
} |
1128 |
|
1129 |
|
1130 |
|
1131 |
/** |
1132 |
* Low-level access to decoding ASCII characters in |
1133 |
* the form of a byte array. <strong>Ignores GUNZIP option, if |
1134 |
* it's set.</strong> This is not generally a recommended method, |
1135 |
* although it is used internally as part of the decoding process. |
1136 |
* Special case: if len = 0, an empty array is returned. Still, |
1137 |
* if you need more speed and reduced memory footprint (and aren't |
1138 |
* gzipping), consider this method. |
1139 |
* |
1140 |
* @param source The Base64 encoded data |
1141 |
* @param off The offset of where to begin decoding |
1142 |
* @param len The length of characters to decode |
1143 |
* @param options Can specify options such as alphabet type to use |
1144 |
* @return decoded data |
1145 |
* @throws java.io.IOException If bogus characters exist in source data |
1146 |
* @since 1.3 |
1147 |
*/ |
1148 |
public static byte[] decode( byte[] source, int off, int len, int options ) |
1149 |
throws java.io.IOException { |
1150 |
|
1151 |
// Lots of error checking and exception throwing |
1152 |
if( source == null ){ |
1153 |
throw new NullPointerException( "Cannot decode null source array." ); |
1154 |
} // end if |
1155 |
if( off < 0 || off + len > source.length ){ |
1156 |
throw new IllegalArgumentException( String.format( |
1157 |
"Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); |
1158 |
} // end if |
1159 |
|
1160 |
if( len == 0 ){ |
1161 |
return new byte[0]; |
1162 |
}else if( len < 4 ){ |
1163 |
throw new IllegalArgumentException( |
1164 |
"Base64-encoded string must have at least four characters, but length specified was " + len ); |
1165 |
} // end if |
1166 |
|
1167 |
byte[] DECODABET = getDecodabet( options ); |
1168 |
|
1169 |
int len34 = len * 3 / 4; // Estimate on array size |
1170 |
byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output |
1171 |
int outBuffPosn = 0; // Keep track of where we're writing |
1172 |
|
1173 |
byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space |
1174 |
int b4Posn = 0; // Keep track of four byte input buffer |
1175 |
int i = 0; // Source array counter |
1176 |
byte sbiDecode = 0; // Special value from DECODABET |
1177 |
|
1178 |
for( i = off; i < off+len; i++ ) { // Loop through source |
1179 |
|
1180 |
sbiDecode = DECODABET[ source[i]&0xFF ]; |
1181 |
|
1182 |
// White space, Equals sign, or legit Base64 character |
1183 |
// Note the values such as -5 and -9 in the |
1184 |
// DECODABETs at the top of the file. |
1185 |
if( sbiDecode >= WHITE_SPACE_ENC ) { |
1186 |
if( sbiDecode >= EQUALS_SIGN_ENC ) { |
1187 |
b4[ b4Posn++ ] = source[i]; // Save non-whitespace |
1188 |
if( b4Posn > 3 ) { // Time to decode? |
1189 |
outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); |
1190 |
b4Posn = 0; |
1191 |
|
1192 |
// If that was the equals sign, break out of 'for' loop |
1193 |
if( source[i] == EQUALS_SIGN ) { |
1194 |
break; |
1195 |
} // end if: equals sign |
1196 |
} // end if: quartet built |
1197 |
} // end if: equals sign or better |
1198 |
} // end if: white space, equals sign or better |
1199 |
else { |
1200 |
// There's a bad input character in the Base64 stream. |
1201 |
throw new java.io.IOException( String.format( |
1202 |
"Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); |
1203 |
} // end else: |
1204 |
} // each input character |
1205 |
|
1206 |
byte[] out = new byte[ outBuffPosn ]; |
1207 |
System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); |
1208 |
return out; |
1209 |
} // end decode |
1210 |
|
1211 |
|
1212 |
|
1213 |
|
1214 |
/** |
1215 |
* Decodes data from Base64 notation, automatically |
1216 |
* detecting gzip-compressed data and decompressing it. |
1217 |
* |
1218 |
* @param s the string to decode |
1219 |
* @return the decoded data |
1220 |
* @throws java.io.IOException If there is a problem |
1221 |
* @since 1.4 |
1222 |
*/ |
1223 |
public static byte[] decode( String s ) throws java.io.IOException { |
1224 |
return decode( s, NO_OPTIONS ); |
1225 |
} |
1226 |
|
1227 |
|
1228 |
|
1229 |
/** |
1230 |
* Decodes data from Base64 notation, automatically |
1231 |
* detecting gzip-compressed data and decompressing it. |
1232 |
* |
1233 |
* @param s the string to decode |
1234 |
* @param options encode options such as URL_SAFE |
1235 |
* @return the decoded data |
1236 |
* @throws java.io.IOException if there is an error |
1237 |
* @throws NullPointerException if <tt>s</tt> is null |
1238 |
* @since 1.4 |
1239 |
*/ |
1240 |
public static byte[] decode( String s, int options ) throws java.io.IOException { |
1241 |
|
1242 |
if( s == null ){ |
1243 |
throw new NullPointerException( "Input string was null." ); |
1244 |
} // end if |
1245 |
|
1246 |
byte[] bytes; |
1247 |
try { |
1248 |
bytes = s.getBytes( PREFERRED_ENCODING ); |
1249 |
} // end try |
1250 |
catch( java.io.UnsupportedEncodingException uee ) { |
1251 |
bytes = s.getBytes(); |
1252 |
} // end catch |
1253 |
//</change> |
1254 |
|
1255 |
// Decode |
1256 |
bytes = decode( bytes, 0, bytes.length, options ); |
1257 |
|
1258 |
// Check to see if it's gzip-compressed |
1259 |
// GZIP Magic Two-Byte Number: 0x8b1f (35615) |
1260 |
boolean dontGunzip = (options & DONT_GUNZIP) != 0; |
1261 |
if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { |
1262 |
|
1263 |
int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); |
1264 |
if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { |
1265 |
java.io.ByteArrayInputStream bais = null; |
1266 |
java.util.zip.GZIPInputStream gzis = null; |
1267 |
java.io.ByteArrayOutputStream baos = null; |
1268 |
byte[] buffer = new byte[2048]; |
1269 |
int length = 0; |
1270 |
|
1271 |
try { |
1272 |
baos = new java.io.ByteArrayOutputStream(); |
1273 |
bais = new java.io.ByteArrayInputStream( bytes ); |
1274 |
gzis = new java.util.zip.GZIPInputStream( bais ); |
1275 |
|
1276 |
while( ( length = gzis.read( buffer ) ) >= 0 ) { |
1277 |
baos.write(buffer,0,length); |
1278 |
} // end while: reading input |
1279 |
|
1280 |
// No error? Get new bytes. |
1281 |
bytes = baos.toByteArray(); |
1282 |
|
1283 |
} // end try |
1284 |
catch( java.io.IOException e ) { |
1285 |
e.printStackTrace(); |
1286 |
// Just return originally-decoded bytes |
1287 |
} // end catch |
1288 |
finally { |
1289 |
try{ baos.close(); } catch( Exception e ){} |
1290 |
try{ gzis.close(); } catch( Exception e ){} |
1291 |
try{ bais.close(); } catch( Exception e ){} |
1292 |
} // end finally |
1293 |
|
1294 |
} // end if: gzipped |
1295 |
} // end if: bytes.length >= 2 |
1296 |
|
1297 |
return bytes; |
1298 |
} // end decode |
1299 |
|
1300 |
|
1301 |
|
1302 |
/** |
1303 |
* Attempts to decode Base64 data and deserialize a Java |
1304 |
* Object within. Returns <tt>null</tt> if there was an error. |
1305 |
* |
1306 |
* @param encodedObject The Base64 data to decode |
1307 |
* @return The decoded and deserialized object |
1308 |
* @throws NullPointerException if encodedObject is null |
1309 |
* @throws java.io.IOException if there is a general error |
1310 |
* @throws ClassNotFoundException if the decoded object is of a |
1311 |
* class that cannot be found by the JVM |
1312 |
* @since 1.5 |
1313 |
*/ |
1314 |
public static Object decodeToObject( String encodedObject ) |
1315 |
throws java.io.IOException, java.lang.ClassNotFoundException { |
1316 |
return decodeToObject(encodedObject,NO_OPTIONS,null); |
1317 |
} |
1318 |
|
1319 |
|
1320 |
/** |
1321 |
* Attempts to decode Base64 data and deserialize a Java |
1322 |
* Object within. Returns <tt>null</tt> if there was an error. |
1323 |
* If <tt>loader</tt> is not null, it will be the class loader |
1324 |
* used when deserializing. |
1325 |
* |
1326 |
* @param encodedObject The Base64 data to decode |
1327 |
* @param options Various parameters related to decoding |
1328 |
* @param loader Optional class loader to use in deserializing classes. |
1329 |
* @return The decoded and deserialized object |
1330 |
* @throws NullPointerException if encodedObject is null |
1331 |
* @throws java.io.IOException if there is a general error |
1332 |
* @throws ClassNotFoundException if the decoded object is of a |
1333 |
* class that cannot be found by the JVM |
1334 |
* @since 2.3.4 |
1335 |
*/ |
1336 |
public static Object decodeToObject( |
1337 |
String encodedObject, int options, final ClassLoader loader ) |
1338 |
throws java.io.IOException, java.lang.ClassNotFoundException { |
1339 |
|
1340 |
// Decode and gunzip if necessary |
1341 |
byte[] objBytes = decode( encodedObject, options ); |
1342 |
|
1343 |
java.io.ByteArrayInputStream bais = null; |
1344 |
java.io.ObjectInputStream ois = null; |
1345 |
Object obj = null; |
1346 |
|
1347 |
try { |
1348 |
bais = new java.io.ByteArrayInputStream( objBytes ); |
1349 |
|
1350 |
// If no custom class loader is provided, use Java's builtin OIS. |
1351 |
if( loader == null ){ |
1352 |
ois = new java.io.ObjectInputStream( bais ); |
1353 |
} // end if: no loader provided |
1354 |
|
1355 |
// Else make a customized object input stream that uses |
1356 |
// the provided class loader. |
1357 |
else { |
1358 |
ois = new java.io.ObjectInputStream(bais){ |
1359 |
@Override |
1360 |
public Class<?> resolveClass(java.io.ObjectStreamClass streamClass) |
1361 |
throws java.io.IOException, ClassNotFoundException { |
1362 |
Class c = Class.forName(streamClass.getName(), false, loader); |
1363 |
if( c == null ){ |
1364 |
return super.resolveClass(streamClass); |
1365 |
} else { |
1366 |
return c; // Class loader knows of this class. |
1367 |
} // end else: not null |
1368 |
} // end resolveClass |
1369 |
}; // end ois |
1370 |
} // end else: no custom class loader |
1371 |
|
1372 |
obj = ois.readObject(); |
1373 |
} // end try |
1374 |
catch( java.io.IOException e ) { |
1375 |
throw e; // Catch and throw in order to execute finally{} |
1376 |
} // end catch |
1377 |
catch( java.lang.ClassNotFoundException e ) { |
1378 |
throw e; // Catch and throw in order to execute finally{} |
1379 |
} // end catch |
1380 |
finally { |
1381 |
try{ bais.close(); } catch( Exception e ){} |
1382 |
try{ ois.close(); } catch( Exception e ){} |
1383 |
} // end finally |
1384 |
|
1385 |
return obj; |
1386 |
} // end decodeObject |
1387 |
|
1388 |
|
1389 |
|
1390 |
/** |
1391 |
* Convenience method for encoding data to a file. |
1392 |
* |
1393 |
* <p>As of v 2.3, if there is a error, |
1394 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
1395 |
* In earlier versions, it just returned false, but |
1396 |
* in retrospect that's a pretty poor way to handle it.</p> |
1397 |
* |
1398 |
* @param dataToEncode byte array of data to encode in base64 form |
1399 |
* @param filename Filename for saving encoded data |
1400 |
* @throws java.io.IOException if there is an error |
1401 |
* @throws NullPointerException if dataToEncode is null |
1402 |
* @since 2.1 |
1403 |
*/ |
1404 |
public static void encodeToFile( byte[] dataToEncode, String filename ) |
1405 |
throws java.io.IOException { |
1406 |
|
1407 |
if( dataToEncode == null ){ |
1408 |
throw new NullPointerException( "Data to encode was null." ); |
1409 |
} // end iff |
1410 |
|
1411 |
Base64.OutputStream bos = null; |
1412 |
try { |
1413 |
bos = new Base64.OutputStream( |
1414 |
new java.io.FileOutputStream( filename ), Base64.ENCODE ); |
1415 |
bos.write( dataToEncode ); |
1416 |
} // end try |
1417 |
catch( java.io.IOException e ) { |
1418 |
throw e; // Catch and throw to execute finally{} block |
1419 |
} // end catch: java.io.IOException |
1420 |
finally { |
1421 |
try{ bos.close(); } catch( Exception e ){} |
1422 |
} // end finally |
1423 |
|
1424 |
} // end encodeToFile |
1425 |
|
1426 |
|
1427 |
/** |
1428 |
* Convenience method for decoding data to a file. |
1429 |
* |
1430 |
* <p>As of v 2.3, if there is a error, |
1431 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
1432 |
* In earlier versions, it just returned false, but |
1433 |
* in retrospect that's a pretty poor way to handle it.</p> |
1434 |
* |
1435 |
* @param dataToDecode Base64-encoded data as a string |
1436 |
* @param filename Filename for saving decoded data |
1437 |
* @throws java.io.IOException if there is an error |
1438 |
* @since 2.1 |
1439 |
*/ |
1440 |
public static void decodeToFile( String dataToDecode, String filename ) |
1441 |
throws java.io.IOException { |
1442 |
|
1443 |
Base64.OutputStream bos = null; |
1444 |
try{ |
1445 |
bos = new Base64.OutputStream( |
1446 |
new java.io.FileOutputStream( filename ), Base64.DECODE ); |
1447 |
bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); |
1448 |
} // end try |
1449 |
catch( java.io.IOException e ) { |
1450 |
throw e; // Catch and throw to execute finally{} block |
1451 |
} // end catch: java.io.IOException |
1452 |
finally { |
1453 |
try{ bos.close(); } catch( Exception e ){} |
1454 |
} // end finally |
1455 |
|
1456 |
} // end decodeToFile |
1457 |
|
1458 |
|
1459 |
|
1460 |
|
1461 |
/** |
1462 |
* Convenience method for reading a base64-encoded |
1463 |
* file and decoding it. |
1464 |
* |
1465 |
* <p>As of v 2.3, if there is a error, |
1466 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
1467 |
* In earlier versions, it just returned false, but |
1468 |
* in retrospect that's a pretty poor way to handle it.</p> |
1469 |
* |
1470 |
* @param filename Filename for reading encoded data |
1471 |
* @return decoded byte array |
1472 |
* @throws java.io.IOException if there is an error |
1473 |
* @since 2.1 |
1474 |
*/ |
1475 |
public static byte[] decodeFromFile( String filename ) |
1476 |
throws java.io.IOException { |
1477 |
|
1478 |
byte[] decodedData = null; |
1479 |
Base64.InputStream bis = null; |
1480 |
try |
1481 |
{ |
1482 |
// Set up some useful variables |
1483 |
java.io.File file = new java.io.File( filename ); |
1484 |
byte[] buffer = null; |
1485 |
int length = 0; |
1486 |
int numBytes = 0; |
1487 |
|
1488 |
// Check for size of file |
1489 |
if( file.length() > Integer.MAX_VALUE ) |
1490 |
{ |
1491 |
throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); |
1492 |
} // end if: file too big for int index |
1493 |
buffer = new byte[ (int)file.length() ]; |
1494 |
|
1495 |
// Open a stream |
1496 |
bis = new Base64.InputStream( |
1497 |
new java.io.BufferedInputStream( |
1498 |
new java.io.FileInputStream( file ) ), Base64.DECODE ); |
1499 |
|
1500 |
// Read until done |
1501 |
while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { |
1502 |
length += numBytes; |
1503 |
} // end while |
1504 |
|
1505 |
// Save in a variable to return |
1506 |
decodedData = new byte[ length ]; |
1507 |
System.arraycopy( buffer, 0, decodedData, 0, length ); |
1508 |
|
1509 |
} // end try |
1510 |
catch( java.io.IOException e ) { |
1511 |
throw e; // Catch and release to execute finally{} |
1512 |
} // end catch: java.io.IOException |
1513 |
finally { |
1514 |
try{ bis.close(); } catch( Exception e) {} |
1515 |
} // end finally |
1516 |
|
1517 |
return decodedData; |
1518 |
} // end decodeFromFile |
1519 |
|
1520 |
|
1521 |
|
1522 |
/** |
1523 |
* Convenience method for reading a binary file |
1524 |
* and base64-encoding it. |
1525 |
* |
1526 |
* <p>As of v 2.3, if there is a error, |
1527 |
* the method will throw an java.io.IOException. <b>This is new to v2.3!</b> |
1528 |
* In earlier versions, it just returned false, but |
1529 |
* in retrospect that's a pretty poor way to handle it.</p> |
1530 |
* |
1531 |
* @param filename Filename for reading binary data |
1532 |
* @return base64-encoded string |
1533 |
* @throws java.io.IOException if there is an error |
1534 |
* @since 2.1 |
1535 |
*/ |
1536 |
public static String encodeFromFile( String filename ) |
1537 |
throws java.io.IOException { |
1538 |
|
1539 |
String encodedData = null; |
1540 |
Base64.InputStream bis = null; |
1541 |
try |
1542 |
{ |
1543 |
// Set up some useful variables |
1544 |
java.io.File file = new java.io.File( filename ); |
1545 |
byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) |
1546 |
int length = 0; |
1547 |
int numBytes = 0; |
1548 |
|
1549 |
// Open a stream |
1550 |
bis = new Base64.InputStream( |
1551 |
new java.io.BufferedInputStream( |
1552 |
new java.io.FileInputStream( file ) ), Base64.ENCODE ); |
1553 |
|
1554 |
// Read until done |
1555 |
while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { |
1556 |
length += numBytes; |
1557 |
} // end while |
1558 |
|
1559 |
// Save in a variable to return |
1560 |
encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); |
1561 |
|
1562 |
} // end try |
1563 |
catch( java.io.IOException e ) { |
1564 |
throw e; // Catch and release to execute finally{} |
1565 |
} // end catch: java.io.IOException |
1566 |
finally { |
1567 |
try{ bis.close(); } catch( Exception e) {} |
1568 |
} // end finally |
1569 |
|
1570 |
return encodedData; |
1571 |
} // end encodeFromFile |
1572 |
|
1573 |
/** |
1574 |
* Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>. |
1575 |
* |
1576 |
* @param infile Input file |
1577 |
* @param outfile Output file |
1578 |
* @throws java.io.IOException if there is an error |
1579 |
* @since 2.2 |
1580 |
*/ |
1581 |
public static void encodeFileToFile( String infile, String outfile ) |
1582 |
throws java.io.IOException { |
1583 |
|
1584 |
String encoded = Base64.encodeFromFile( infile ); |
1585 |
java.io.OutputStream out = null; |
1586 |
try{ |
1587 |
out = new java.io.BufferedOutputStream( |
1588 |
new java.io.FileOutputStream( outfile ) ); |
1589 |
out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. |
1590 |
} // end try |
1591 |
catch( java.io.IOException e ) { |
1592 |
throw e; // Catch and release to execute finally{} |
1593 |
} // end catch |
1594 |
finally { |
1595 |
try { out.close(); } |
1596 |
catch( Exception ex ){} |
1597 |
} // end finally |
1598 |
} // end encodeFileToFile |
1599 |
|
1600 |
|
1601 |
/** |
1602 |
* Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>. |
1603 |
* |
1604 |
* @param infile Input file |
1605 |
* @param outfile Output file |
1606 |
* @throws java.io.IOException if there is an error |
1607 |
* @since 2.2 |
1608 |
*/ |
1609 |
public static void decodeFileToFile( String infile, String outfile ) |
1610 |
throws java.io.IOException { |
1611 |
|
1612 |
byte[] decoded = Base64.decodeFromFile( infile ); |
1613 |
java.io.OutputStream out = null; |
1614 |
try{ |
1615 |
out = new java.io.BufferedOutputStream( |
1616 |
new java.io.FileOutputStream( outfile ) ); |
1617 |
out.write( decoded ); |
1618 |
} // end try |
1619 |
catch( java.io.IOException e ) { |
1620 |
throw e; // Catch and release to execute finally{} |
1621 |
} // end catch |
1622 |
finally { |
1623 |
try { out.close(); } |
1624 |
catch( Exception ex ){} |
1625 |
} // end finally |
1626 |
} // end decodeFileToFile |
1627 |
|
1628 |
|
1629 |
/* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ |
1630 |
|
1631 |
|
1632 |
|
1633 |
/** |
1634 |
* A {@link Base64.InputStream} will read data from another |
1635 |
* <tt>java.io.InputStream</tt>, given in the constructor, |
1636 |
* and encode/decode to/from Base64 notation on the fly. |
1637 |
* |
1638 |
* @see Base64 |
1639 |
* @since 1.3 |
1640 |
*/ |
1641 |
public static class InputStream extends java.io.FilterInputStream { |
1642 |
|
1643 |
private boolean encode; // Encoding or decoding |
1644 |
private int position; // Current position in the buffer |
1645 |
private byte[] buffer; // Small buffer holding converted data |
1646 |
private int bufferLength; // Length of buffer (3 or 4) |
1647 |
private int numSigBytes; // Number of meaningful bytes in the buffer |
1648 |
private int lineLength; |
1649 |
private boolean breakLines; // Break lines at less than 80 characters |
1650 |
private int options; // Record options used to create the stream. |
1651 |
private byte[] decodabet; // Local copies to avoid extra method calls |
1652 |
|
1653 |
|
1654 |
/** |
1655 |
* Constructs a {@link Base64.InputStream} in DECODE mode. |
1656 |
* |
1657 |
* @param in the <tt>java.io.InputStream</tt> from which to read data. |
1658 |
* @since 1.3 |
1659 |
*/ |
1660 |
public InputStream( java.io.InputStream in ) { |
1661 |
this( in, DECODE ); |
1662 |
} // end constructor |
1663 |
|
1664 |
|
1665 |
/** |
1666 |
* Constructs a {@link Base64.InputStream} in |
1667 |
* either ENCODE or DECODE mode. |
1668 |
* <p> |
1669 |
* Valid options:<pre> |
1670 |
* ENCODE or DECODE: Encode or Decode as data is read. |
1671 |
* DO_BREAK_LINES: break lines at 76 characters |
1672 |
* (only meaningful when encoding)</i> |
1673 |
* </pre> |
1674 |
* <p> |
1675 |
* Example: <code>new Base64.InputStream( in, Base64.DECODE )</code> |
1676 |
* |
1677 |
* |
1678 |
* @param in the <tt>java.io.InputStream</tt> from which to read data. |
1679 |
* @param options Specified options |
1680 |
* @see Base64#ENCODE |
1681 |
* @see Base64#DECODE |
1682 |
* @see Base64#DO_BREAK_LINES |
1683 |
* @since 2.0 |
1684 |
*/ |
1685 |
public InputStream( java.io.InputStream in, int options ) { |
1686 |
|
1687 |
super( in ); |
1688 |
this.options = options; // Record for later |
1689 |
this.breakLines = (options & DO_BREAK_LINES) > 0; |
1690 |
this.encode = (options & ENCODE) > 0; |
1691 |
this.bufferLength = encode ? 4 : 3; |
1692 |
this.buffer = new byte[ bufferLength ]; |
1693 |
this.position = -1; |
1694 |
this.lineLength = 0; |
1695 |
this.decodabet = getDecodabet(options); |
1696 |
} // end constructor |
1697 |
|
1698 |
/** |
1699 |
* Reads enough of the input stream to convert |
1700 |
* to/from Base64 and returns the next byte. |
1701 |
* |
1702 |
* @return next byte |
1703 |
* @since 1.3 |
1704 |
*/ |
1705 |
@Override |
1706 |
public int read() throws java.io.IOException { |
1707 |
|
1708 |
// Do we need to get data? |
1709 |
if( position < 0 ) { |
1710 |
if( encode ) { |
1711 |
byte[] b3 = new byte[3]; |
1712 |
int numBinaryBytes = 0; |
1713 |
for( int i = 0; i < 3; i++ ) { |
1714 |
int b = in.read(); |
1715 |
|
1716 |
// If end of stream, b is -1. |
1717 |
if( b >= 0 ) { |
1718 |
b3[i] = (byte)b; |
1719 |
numBinaryBytes++; |
1720 |
} else { |
1721 |
break; // out of for loop |
1722 |
} // end else: end of stream |
1723 |
|
1724 |
} // end for: each needed input byte |
1725 |
|
1726 |
if( numBinaryBytes > 0 ) { |
1727 |
encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); |
1728 |
position = 0; |
1729 |
numSigBytes = 4; |
1730 |
} // end if: got data |
1731 |
else { |
1732 |
return -1; // Must be end of stream |
1733 |
} // end else |
1734 |
} // end if: encoding |
1735 |
|
1736 |
// Else decoding |
1737 |
else { |
1738 |
byte[] b4 = new byte[4]; |
1739 |
int i = 0; |
1740 |
for( i = 0; i < 4; i++ ) { |
1741 |
// Read four "meaningful" bytes: |
1742 |
int b = 0; |
1743 |
do{ b = in.read(); } |
1744 |
while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); |
1745 |
|
1746 |
if( b < 0 ) { |
1747 |
break; // Reads a -1 if end of stream |
1748 |
} // end if: end of stream |
1749 |
|
1750 |
b4[i] = (byte)b; |
1751 |
} // end for: each needed input byte |
1752 |
|
1753 |
if( i == 4 ) { |
1754 |
numSigBytes = decode4to3( b4, 0, buffer, 0, options ); |
1755 |
position = 0; |
1756 |
} // end if: got four characters |
1757 |
else if( i == 0 ){ |
1758 |
return -1; |
1759 |
} // end else if: also padded correctly |
1760 |
else { |
1761 |
// Must have broken out from above. |
1762 |
throw new java.io.IOException( "Improperly padded Base64 input." ); |
1763 |
} // end |
1764 |
|
1765 |
} // end else: decode |
1766 |
} // end else: get data |
1767 |
|
1768 |
// Got data? |
1769 |
if( position >= 0 ) { |
1770 |
// End of relevant data? |
1771 |
if( /*!encode &&*/ position >= numSigBytes ){ |
1772 |
return -1; |
1773 |
} // end if: got data |
1774 |
|
1775 |
if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { |
1776 |
lineLength = 0; |
1777 |
return '\n'; |
1778 |
} // end if |
1779 |
else { |
1780 |
lineLength++; // This isn't important when decoding |
1781 |
// but throwing an extra "if" seems |
1782 |
// just as wasteful. |
1783 |
|
1784 |
int b = buffer[ position++ ]; |
1785 |
|
1786 |
if( position >= bufferLength ) { |
1787 |
position = -1; |
1788 |
} // end if: end |
1789 |
|
1790 |
return b & 0xFF; // This is how you "cast" a byte that's |
1791 |
// intended to be unsigned. |
1792 |
} // end else |
1793 |
} // end if: position >= 0 |
1794 |
|
1795 |
// Else error |
1796 |
else { |
1797 |
throw new java.io.IOException( "Error in Base64 code reading stream." ); |
1798 |
} // end else |
1799 |
} // end read |
1800 |
|
1801 |
|
1802 |
/** |
1803 |
* Calls {@link #read()} repeatedly until the end of stream |
1804 |
* is reached or <var>len</var> bytes are read. |
1805 |
* Returns number of bytes read into array or -1 if |
1806 |
* end of stream is encountered. |
1807 |
* |
1808 |
* @param dest array to hold values |
1809 |
* @param off offset for array |
1810 |
* @param len max number of bytes to read into array |
1811 |
* @return bytes read into array or -1 if end of stream is encountered. |
1812 |
* @since 1.3 |
1813 |
*/ |
1814 |
@Override |
1815 |
public int read( byte[] dest, int off, int len ) |
1816 |
throws java.io.IOException { |
1817 |
int i; |
1818 |
int b; |
1819 |
for( i = 0; i < len; i++ ) { |
1820 |
b = read(); |
1821 |
|
1822 |
if( b >= 0 ) { |
1823 |
dest[off + i] = (byte) b; |
1824 |
} |
1825 |
else if( i == 0 ) { |
1826 |
return -1; |
1827 |
} |
1828 |
else { |
1829 |
break; // Out of 'for' loop |
1830 |
} // Out of 'for' loop |
1831 |
} // end for: each byte read |
1832 |
return i; |
1833 |
} // end read |
1834 |
|
1835 |
} // end inner class InputStream |
1836 |
|
1837 |
|
1838 |
|
1839 |
|
1840 |
|
1841 |
|
1842 |
/* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ |
1843 |
|
1844 |
|
1845 |
|
1846 |
/** |
1847 |
* A {@link Base64.OutputStream} will write data to another |
1848 |
* <tt>java.io.OutputStream</tt>, given in the constructor, |
1849 |
* and encode/decode to/from Base64 notation on the fly. |
1850 |
* |
1851 |
* @see Base64 |
1852 |
* @since 1.3 |
1853 |
*/ |
1854 |
public static class OutputStream extends java.io.FilterOutputStream { |
1855 |
|
1856 |
private boolean encode; |
1857 |
private int position; |
1858 |
private byte[] buffer; |
1859 |
private int bufferLength; |
1860 |
private int lineLength; |
1861 |
private boolean breakLines; |
1862 |
private byte[] b4; // Scratch used in a few places |
1863 |
private boolean suspendEncoding; |
1864 |
private int options; // Record for later |
1865 |
private byte[] decodabet; // Local copies to avoid extra method calls |
1866 |
|
1867 |
/** |
1868 |
* Constructs a {@link Base64.OutputStream} in ENCODE mode. |
1869 |
* |
1870 |
* @param out the <tt>java.io.OutputStream</tt> to which data will be written. |
1871 |
* @since 1.3 |
1872 |
*/ |
1873 |
public OutputStream( java.io.OutputStream out ) { |
1874 |
this( out, ENCODE ); |
1875 |
} // end constructor |
1876 |
|
1877 |
|
1878 |
/** |
1879 |
* Constructs a {@link Base64.OutputStream} in |
1880 |
* either ENCODE or DECODE mode. |
1881 |
* <p> |
1882 |
* Valid options:<pre> |
1883 |
* ENCODE or DECODE: Encode or Decode as data is read. |
1884 |
* DO_BREAK_LINES: don't break lines at 76 characters |
1885 |
* (only meaningful when encoding)</i> |
1886 |
* </pre> |
1887 |
* <p> |
1888 |
* Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code> |
1889 |
* |
1890 |
* @param out the <tt>java.io.OutputStream</tt> to which data will be written. |
1891 |
* @param options Specified options. |
1892 |
* @see Base64#ENCODE |
1893 |
* @see Base64#DECODE |
1894 |
* @see Base64#DO_BREAK_LINES |
1895 |
* @since 1.3 |
1896 |
*/ |
1897 |
public OutputStream( java.io.OutputStream out, int options ) { |
1898 |
super( out ); |
1899 |
this.breakLines = (options & DO_BREAK_LINES) != 0; |
1900 |
this.encode = (options & ENCODE) != 0; |
1901 |
this.bufferLength = encode ? 3 : 4; |
1902 |
this.buffer = new byte[ bufferLength ]; |
1903 |
this.position = 0; |
1904 |
this.lineLength = 0; |
1905 |
this.suspendEncoding = false; |
1906 |
this.b4 = new byte[4]; |
1907 |
this.options = options; |
1908 |
this.decodabet = getDecodabet(options); |
1909 |
} // end constructor |
1910 |
|
1911 |
|
1912 |
/** |
1913 |
* Writes the byte to the output stream after |
1914 |
* converting to/from Base64 notation. |
1915 |
* When encoding, bytes are buffered three |
1916 |
* at a time before the output stream actually |
1917 |
* gets a write() call. |
1918 |
* When decoding, bytes are buffered four |
1919 |
* at a time. |
1920 |
* |
1921 |
* @param theByte the byte to write |
1922 |
* @since 1.3 |
1923 |
*/ |
1924 |
@Override |
1925 |
public void write(int theByte) |
1926 |
throws java.io.IOException { |
1927 |
// Encoding suspended? |
1928 |
if( suspendEncoding ) { |
1929 |
this.out.write( theByte ); |
1930 |
return; |
1931 |
} // end if: supsended |
1932 |
|
1933 |
// Encode? |
1934 |
if( encode ) { |
1935 |
buffer[ position++ ] = (byte)theByte; |
1936 |
if( position >= bufferLength ) { // Enough to encode. |
1937 |
|
1938 |
this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); |
1939 |
|
1940 |
lineLength += 4; |
1941 |
if( breakLines && lineLength >= MAX_LINE_LENGTH ) { |
1942 |
this.out.write( NEW_LINE ); |
1943 |
lineLength = 0; |
1944 |
} // end if: end of line |
1945 |
|
1946 |
position = 0; |
1947 |
} // end if: enough to output |
1948 |
} // end if: encoding |
1949 |
|
1950 |
// Else, Decoding |
1951 |
else { |
1952 |
// Meaningful Base64 character? |
1953 |
if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { |
1954 |
buffer[ position++ ] = (byte)theByte; |
1955 |
if( position >= bufferLength ) { // Enough to output. |
1956 |
|
1957 |
int len = Base64.decode4to3( buffer, 0, b4, 0, options ); |
1958 |
out.write( b4, 0, len ); |
1959 |
position = 0; |
1960 |
} // end if: enough to output |
1961 |
} // end if: meaningful base64 character |
1962 |
else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { |
1963 |
throw new java.io.IOException( "Invalid character in Base64 data." ); |
1964 |
} // end else: not white space either |
1965 |
} // end else: decoding |
1966 |
} // end write |
1967 |
|
1968 |
|
1969 |
|
1970 |
/** |
1971 |
* Calls {@link #write(int)} repeatedly until <var>len</var> |
1972 |
* bytes are written. |
1973 |
* |
1974 |
* @param theBytes array from which to read bytes |
1975 |
* @param off offset for array |
1976 |
* @param len max number of bytes to read into array |
1977 |
* @since 1.3 |
1978 |
*/ |
1979 |
@Override |
1980 |
public void write( byte[] theBytes, int off, int len ) |
1981 |
throws java.io.IOException { |
1982 |
// Encoding suspended? |
1983 |
if( suspendEncoding ) { |
1984 |
this.out.write( theBytes, off, len ); |
1985 |
return; |
1986 |
} // end if: supsended |
1987 |
|
1988 |
for( int i = 0; i < len; i++ ) { |
1989 |
write( theBytes[ off + i ] ); |
1990 |
} // end for: each byte written |
1991 |
|
1992 |
} // end write |
1993 |
|
1994 |
|
1995 |
|
1996 |
/** |
1997 |
* Method added by PHIL. [Thanks, PHIL. -Rob] |
1998 |
* This pads the buffer without closing the stream. |
1999 |
* @throws java.io.IOException if there's an error. |
2000 |
*/ |
2001 |
public void flushBase64() throws java.io.IOException { |
2002 |
if( position > 0 ) { |
2003 |
if( encode ) { |
2004 |
out.write( encode3to4( b4, buffer, position, options ) ); |
2005 |
position = 0; |
2006 |
} // end if: encoding |
2007 |
else { |
2008 |
throw new java.io.IOException( "Base64 input not properly padded." ); |
2009 |
} // end else: decoding |
2010 |
} // end if: buffer partially full |
2011 |
|
2012 |
} // end flush |
2013 |
|
2014 |
|
2015 |
/** |
2016 |
* Flushes and closes (I think, in the superclass) the stream. |
2017 |
* |
2018 |
* @since 1.3 |
2019 |
*/ |
2020 |
@Override |
2021 |
public void close() throws java.io.IOException { |
2022 |
// 1. Ensure that pending characters are written |
2023 |
flushBase64(); |
2024 |
|
2025 |
// 2. Actually close the stream |
2026 |
// Base class both flushes and closes. |
2027 |
super.close(); |
2028 |
|
2029 |
buffer = null; |
2030 |
out = null; |
2031 |
} // end close |
2032 |
|
2033 |
|
2034 |
|
2035 |
/** |
2036 |
* Suspends encoding of the stream. |
2037 |
* May be helpful if you need to embed a piece of |
2038 |
* base64-encoded data in a stream. |
2039 |
* |
2040 |
* @throws java.io.IOException if there's an error flushing |
2041 |
* @since 1.5.1 |
2042 |
*/ |
2043 |
public void suspendEncoding() throws java.io.IOException { |
2044 |
flushBase64(); |
2045 |
this.suspendEncoding = true; |
2046 |
} // end suspendEncoding |
2047 |
|
2048 |
|
2049 |
/** |
2050 |
* Resumes encoding of the stream. |
2051 |
* May be helpful if you need to embed a piece of |
2052 |
* base64-encoded data in a stream. |
2053 |
* |
2054 |
* @since 1.5.1 |
2055 |
*/ |
2056 |
public void resumeEncoding() { |
2057 |
this.suspendEncoding = false; |
2058 |
} // end resumeEncoding |
2059 |
|
2060 |
|
2061 |
|
2062 |
} // end inner class OutputStream |
2063 |
|
2064 |
|
2065 |
} // end class Base64 |