1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.util.misc; |
|
27 |
|
28 import java.io.*; |
|
29 |
|
30 public class Base64Util { |
|
31 // |
|
32 // Inner classes |
|
33 // |
|
34 |
|
35 @SuppressWarnings({"serial"}) |
|
36 public static class Base64Exception extends IOException { |
|
37 public Base64Exception(String message) { |
|
38 super(message); |
|
39 } |
|
40 } |
|
41 |
|
42 /** |
|
43 * Utility class provided for callers who wish to enforce a maximum |
|
44 * line length for their Base64-encoded output. |
|
45 */ |
|
46 public static class WrappingWriter extends FilterWriter { |
|
47 private static final String NEWLINE = |
|
48 System.getProperty("line.separator"); |
|
49 |
|
50 private int column_ = 0; |
|
51 private int max_ = 76; |
|
52 |
|
53 /** |
|
54 * Creates a new WrappingWriter which writes line-wrapped |
|
55 * output to {@code out} using the default width (76 |
|
56 * characters). |
|
57 * |
|
58 * @param out the {@code Writer} to which output should be sent |
|
59 */ |
|
60 public WrappingWriter(Writer out) |
|
61 { |
|
62 super(out); |
|
63 } |
|
64 |
|
65 /** |
|
66 * Creates a new WrappingWriter which writes line-wrapped |
|
67 * output to {@code out} using the specified width. |
|
68 * |
|
69 * @param out the {@code Writer} to which output should be sent |
|
70 * @param max the maximum number of characters per line |
|
71 */ |
|
72 public WrappingWriter(Writer out, int max) |
|
73 { |
|
74 super(out); |
|
75 max_ = max; |
|
76 } |
|
77 |
|
78 @Override |
|
79 public void write(int c) throws IOException |
|
80 { |
|
81 if (++column_ > max_) { |
|
82 column_ = 1; |
|
83 out.write(NEWLINE); |
|
84 } |
|
85 |
|
86 out.write(c); |
|
87 } |
|
88 |
|
89 @Override |
|
90 public void flush() throws IOException |
|
91 { |
|
92 if (column_ != 0) |
|
93 out.write(NEWLINE); |
|
94 column_ = 0; |
|
95 |
|
96 out.flush(); |
|
97 } |
|
98 |
|
99 /* |
|
100 * We currently only use write(int), so we don't need to |
|
101 * redefine any other methods. |
|
102 */ |
|
103 } |
|
104 |
|
105 // |
|
106 // Static data |
|
107 // |
|
108 |
|
109 private static final char ENCODE[] = { |
|
110 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
|
111 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
|
112 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
|
113 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', |
|
114 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', |
|
115 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', |
|
116 'w', 'x', 'y', 'z', '0', '1', '2', '3', |
|
117 '4', '5', '6', '7', '8', '9', '+', '/' |
|
118 }; |
|
119 |
|
120 private static final byte DECODE[] = new byte[256]; |
|
121 |
|
122 static { |
|
123 for (int i = 0; i < DECODE.length; i++) { |
|
124 DECODE[i] = (byte)-1; |
|
125 } |
|
126 for (int i = 0; i < ENCODE.length; i++) { |
|
127 DECODE[ENCODE[i]] = (byte)i; |
|
128 } |
|
129 } |
|
130 |
|
131 private static final char CHAR_PAD = '='; |
|
132 |
|
133 // |
|
134 // Static methods |
|
135 // |
|
136 |
|
137 /** |
|
138 * Decodes the base64-encoded data on the given {@code InputStream} to the |
|
139 * given {@code OutputStream}. |
|
140 * |
|
141 * @exception Base64Exception |
|
142 * if the given {@code InputStream} has invalid or insufficient |
|
143 * data |
|
144 * |
|
145 * @exception IOException |
|
146 * if an I/O error ocurrs |
|
147 */ |
|
148 public static void decode(InputStream in, OutputStream out) |
|
149 throws IOException { |
|
150 |
|
151 // Total number of bytes read |
|
152 int count = 0; |
|
153 |
|
154 OUTER: while (true) { |
|
155 int pad = 0; |
|
156 int atom = 0; |
|
157 |
|
158 // Read in 4 characters (6 significant bits each) |
|
159 for (int i = 0; i < 4; i++) { |
|
160 int b = in.read(); |
|
161 |
|
162 if (b == -1) { |
|
163 if (i == 0) { |
|
164 break OUTER; |
|
165 } |
|
166 |
|
167 if (pad != 0) { |
|
168 throw new Base64Exception(String.format( |
|
169 "expected '%c' at position %d, found EOS", CHAR_PAD, |
|
170 count)); |
|
171 } else { |
|
172 throw new Base64Exception(String.format( |
|
173 "unexpected EOS at position %d", count)); |
|
174 } |
|
175 |
|
176 // Ignore whitespace |
|
177 } else if (Character.isWhitespace((char)b)) { |
|
178 i--; |
|
179 continue; |
|
180 } |
|
181 |
|
182 atom <<= 6; |
|
183 |
|
184 if (b == CHAR_PAD) { |
|
185 // Pad chars can only be the 3rd or 4th char in the set |
|
186 if (i < 2) { |
|
187 throw new Base64Exception(String.format( |
|
188 "invalid char at position %d: %s", count, |
|
189 getCharDescription((char)b))); |
|
190 } |
|
191 pad++; |
|
192 |
|
193 } else if (pad != 0) { |
|
194 throw new Base64Exception(String.format( |
|
195 "invalid char at position %d: %s (expected %s)", count, |
|
196 getCharDescription((char)b), |
|
197 getCharDescription(CHAR_PAD))); |
|
198 |
|
199 } else { |
|
200 // InputStream.read guarantees -1 <= b <= 255 |
|
201 int index = DECODE[b]; |
|
202 |
|
203 if (index < 0 || index > 63) { |
|
204 throw new Base64Exception(String.format( |
|
205 "invalid char at position %d: %s", count, |
|
206 getCharDescription((char)b))); |
|
207 } |
|
208 |
|
209 atom |= index; |
|
210 } |
|
211 |
|
212 count++; |
|
213 } |
|
214 |
|
215 // Output 3 8-bit bytes |
|
216 for (int i = 0; i < 3 - pad; i++) { |
|
217 out.write((byte)((atom & 0x00FF0000) >>> 16)); |
|
218 atom <<= 8; |
|
219 } |
|
220 } |
|
221 |
|
222 out.flush(); |
|
223 } |
|
224 |
|
225 public static byte[] decode(InputStream in) |
|
226 throws IOException { |
|
227 |
|
228 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
229 decode(in, out); |
|
230 |
|
231 return out.toByteArray(); |
|
232 } |
|
233 |
|
234 public static void decode(String in, OutputStream out) |
|
235 throws IOException { |
|
236 |
|
237 ByteArrayInputStream bin = |
|
238 new ByteArrayInputStream(in.getBytes()); |
|
239 |
|
240 decode(bin, out); |
|
241 } |
|
242 |
|
243 public static byte[] decode(String in) |
|
244 throws IOException { |
|
245 |
|
246 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
247 decode(in, out); |
|
248 |
|
249 return out.toByteArray(); |
|
250 } |
|
251 |
|
252 /** |
|
253 * Encodes the data on the given {@code InputStream} as base64 and writes it |
|
254 * to the given {@code Writer}. |
|
255 * |
|
256 * @exception IOException |
|
257 * if an I/O error ocurrs |
|
258 */ |
|
259 public static void encode(InputStream in, Writer out) |
|
260 throws IOException { |
|
261 |
|
262 OUTER: while (true) { |
|
263 int pad = 0; |
|
264 int atom = 0; |
|
265 |
|
266 // Read in 3 8-bit bytes |
|
267 for (int i = 0; i < 3; i++) { |
|
268 int b = in.read(); |
|
269 |
|
270 if (b == -1) { |
|
271 if (i == 0) { |
|
272 break OUTER; |
|
273 } |
|
274 pad++; |
|
275 } else { |
|
276 atom |= b; |
|
277 } |
|
278 atom <<= 8; |
|
279 } |
|
280 |
|
281 // Output 4 6-bit characters... |
|
282 for (int i = 0; i < 4 - pad; i++) { |
|
283 int index = (atom & 0xFC000000) >>> 26; |
|
284 out.write(ENCODE[index]); |
|
285 atom <<= 6; |
|
286 } |
|
287 |
|
288 // ...plus any padding if 3 bytes couldn't be read |
|
289 for (int i = 0; i < pad; i++) { |
|
290 out.write(CHAR_PAD); |
|
291 } |
|
292 } |
|
293 |
|
294 out.flush(); |
|
295 } |
|
296 |
|
297 public static void encode(byte[] buffer, int offset, int len, |
|
298 Writer out) throws IOException { |
|
299 |
|
300 ByteArrayInputStream in = |
|
301 new ByteArrayInputStream(buffer, offset, len); |
|
302 encode(in, out); |
|
303 } |
|
304 |
|
305 public static void encode(byte[] buffer, Writer out) |
|
306 throws IOException { |
|
307 encode(buffer, 0, buffer.length, out); |
|
308 } |
|
309 |
|
310 public static void encode(String s, Writer out) |
|
311 throws IOException { |
|
312 encode(s.getBytes(), out); |
|
313 } |
|
314 |
|
315 public static String encode(byte[] buffer, int offset, int len) { |
|
316 StringWriter out = new StringWriter(); |
|
317 try { |
|
318 encode(buffer, offset, len, out); |
|
319 return out.toString(); |
|
320 } catch (IOException e) { |
|
321 // Not likely -- output is from a byte array to a String |
|
322 } |
|
323 |
|
324 return null; |
|
325 } |
|
326 |
|
327 public static String encode(byte[] buffer) { |
|
328 return encode(buffer, 0, buffer.length); |
|
329 } |
|
330 |
|
331 public static String encode(String s) { |
|
332 return encode(s.getBytes()); |
|
333 } |
|
334 |
|
335 // |
|
336 // Private static methods |
|
337 // |
|
338 |
|
339 private static String getCharDescription(char c) { |
|
340 String desc = ""; |
|
341 |
|
342 if (TextUtil.isPrintable(c)) { |
|
343 desc = String.format("'%c',", c); |
|
344 } |
|
345 |
|
346 desc += String.format("%d,0x%02x", (int)c, (int)c); |
|
347 |
|
348 return desc; |
|
349 } |
|
350 } |
|