1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
8 import java.util.zip.GZIPInputStream;
9 import java.util.zip.GZIPOutputStream;
11 // FEATURE: add ASCII85Encoding (see pdf spec)
13 /** General <tt>String</tt> and <tt>byte[]</tt> processing functions,
14 * including Base64 and a safe filename transform.
16 * @author adam@ibex.org
18 public final class Encode {
20 public static class QuotedPrintable {
21 public static String decode(String s, boolean lax) {
23 // =XX -> hex representation, must be uppercase
24 // 9, 32, 33-60, 62-126 can be literal
25 // 9, 32 at end-of-line must get encoded
26 // trailing whitespace must be deleted when decoding
27 // =\n = soft line break
28 // lines cannot be more than 76 chars long
31 // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
37 public static class RFC2047 {
38 public static String decode(String s) {
40 try { while (s.indexOf("=?") != -1) {
41 String pre = s.substring(0, s.indexOf("=?"));
42 s = s.substring(s.indexOf("=?") + 2);
44 // MIME charset; FIXME use this
45 String charset = s.substring(0, s.indexOf('?')).toLowerCase();
46 s = s.substring(s.indexOf('?') + 1);
48 String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
49 s = s.substring(s.indexOf('?') + 1);
51 String encodedText = s.substring(0, s.indexOf("?="));
53 if (encoding.equals("b")) encodedText = new String(Base64.decode(encodedText));
55 // except that ANY char can be endoed (unlike real qp)
56 else if (encoding.equals("q")) encodedText = MIME.QuotedPrintable.decode(encodedText, true);
57 else Log.warn(MIME.class, "unknown RFC2047 encoding \""+encoding+"\"");
59 String post = s.substring(s.indexOf("?=") + 2);
60 s = pre + encodedText + post;
62 // FIXME re-encode when transmitting
64 } } catch (Exception e) {
65 Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
66 Log.warn(MIME.class, e);
74 public static long twoFloatsToLong(float a, float b) {
75 return ((Float.floatToIntBits(a) & 0xffffffffL) << 32) | (Float.floatToIntBits(b) & 0xffffffffL); }
76 public static float longToFloat1(long l) { return Float.intBitsToFloat((int)((l >> 32) & 0xffffffff)); }
77 public static float longToFloat2(long l) { return Float.intBitsToFloat((int)(l & 0xffffffff)); }
79 private static final char[] fn =
80 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
82 public static String toFilename(String s) {
83 StringBuffer sb = new StringBuffer();
85 byte[] b = s.getBytes("UTF-8");
86 for(int i=0; i<b.length; i++) {
87 char c = (char)(b[i] & 0xff);
88 if (c == File.separatorChar || c < 32 || c > 126 || c == '%' || (i == 0 && c == '.'))
89 sb.append("%" + fn[(b[i] & 0xf0) >> 8] + fn[b[i] & 0xf]);
93 } catch (UnsupportedEncodingException uee) {
94 throw new Error("this should never happen; Java spec mandates UTF-8 support");
98 public static String fromFilename(String s) {
99 StringBuffer sb = new StringBuffer();
100 byte[] b = new byte[s.length() * 2];
102 for(int i=0; i<s.length(); i++) {
103 char c = s.charAt(i);
104 if (c == '%') b[bytes++] = (byte)Integer.parseInt(("" + s.charAt(++i) + s.charAt(++i)), 16);
105 else b[bytes++] = (byte)c;
108 return new String(b, 0, bytes, "UTF-8");
109 } catch (UnsupportedEncodingException uee) {
110 throw new Error("this should never happen; Java spec mandates UTF-8 support");
115 private static final byte[] encB64 = {
116 (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
117 (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
118 (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
119 (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
120 (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
121 (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
122 (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
123 (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1',
124 (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
125 (byte)'9', (byte)'+', (byte)'/'
128 // FIXME could be far more efficient
129 public static class Base64InputStream extends ByteArrayInputStream {
130 public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
133 public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
135 public static String toBase64String(byte[] data) { return new String(toBase64(data)); }
137 /** Encode the input data producong a base 64 encoded byte array.
138 * @return A byte array containing the base 64 encoded data. */
139 public static byte[] toBase64(byte[] data) {
142 int modulus = data.length % 3;
144 bytes = new byte[4 * data.length / 3];
146 bytes = new byte[4 * ((data.length / 3) + 1)];
149 int dataLength = (data.length - modulus);
151 for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
153 a2 = data[i + 1] & 0xff;
154 a3 = data[i + 2] & 0xff;
156 bytes[j] = encB64[(a1 >>> 2) & 0x3f];
157 bytes[j + 1] = encB64[((a1 << 4) | (a2 >>> 4)) & 0x3f];
158 bytes[j + 2] = encB64[((a2 << 2) | (a3 >>> 6)) & 0x3f];
159 bytes[j + 3] = encB64[a3 & 0x3f];
165 case 0: /* nothing left to do */
168 d1 = data[data.length - 1] & 0xff;
169 b1 = (d1 >>> 2) & 0x3f;
170 b2 = (d1 << 4) & 0x3f;
172 bytes[bytes.length - 4] = encB64[b1];
173 bytes[bytes.length - 3] = encB64[b2];
174 bytes[bytes.length - 2] = (byte)'=';
175 bytes[bytes.length - 1] = (byte)'=';
178 d1 = data[data.length - 2] & 0xff;
179 d2 = data[data.length - 1] & 0xff;
181 b1 = (d1 >>> 2) & 0x3f;
182 b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
183 b3 = (d2 << 2) & 0x3f;
185 bytes[bytes.length - 4] = encB64[b1];
186 bytes[bytes.length - 3] = encB64[b2];
187 bytes[bytes.length - 2] = encB64[b3];
188 bytes[bytes.length - 1] = (byte)'=';
196 private static final byte[] decB64 = new byte[128];
198 for (int i = 'A'; i <= 'Z'; i++) decB64[i] = (byte)(i - 'A');
199 for (int i = 'a'; i <= 'z'; i++) decB64[i] = (byte)(i - 'a' + 26);
200 for (int i = '0'; i <= '9'; i++) decB64[i] = (byte)(i - '0' + 52);
205 /** Decode base 64 encoded input data.
206 * @return A byte array representing the decoded data. */
207 public static byte[] fromBase64(byte[] data) {
211 if (data[data.length - 2] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
212 else if (data[data.length - 1] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
213 else bytes = new byte[((data.length / 4) * 3)];
215 for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) {
216 b1 = decB64[data[i]];
217 b2 = decB64[data[i + 1]];
218 b3 = decB64[data[i + 2]];
219 b4 = decB64[data[i + 3]];
221 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
222 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
223 bytes[j + 2] = (byte)((b3 << 6) | b4);
226 if (data[data.length - 2] == '=') {
227 b1 = decB64[data[data.length - 4]];
228 b2 = decB64[data[data.length - 3]];
229 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
230 } else if (data[data.length - 1] == '=') {
231 b1 = decB64[data[data.length - 4]];
232 b2 = decB64[data[data.length - 3]];
233 b3 = decB64[data[data.length - 2]];
234 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
235 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
237 b1 = decB64[data[data.length - 4]];
238 b2 = decB64[data[data.length - 3]];
239 b3 = decB64[data[data.length - 2]];
240 b4 = decB64[data[data.length - 1]];
241 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
242 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
243 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
248 /** Decode a base 64 encoded String.
249 * @return A byte array representing the decoded data. */
250 public static byte[] fromBase64(String data) {
254 if (data.charAt(data.length() - 2) == '=')
255 bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
256 else if (data.charAt(data.length() - 1) == '=')
257 bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
259 bytes = new byte[((data.length() / 4) * 3)];
261 for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) {
262 b1 = decB64[data.charAt(i)];
263 b2 = decB64[data.charAt(i + 1)];
264 b3 = decB64[data.charAt(i + 2)];
265 b4 = decB64[data.charAt(i + 3)];
267 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
268 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
269 bytes[j + 2] = (byte)((b3 << 6) | b4);
272 if (data.charAt(data.length() - 2) == '=') {
273 b1 = decB64[data.charAt(data.length() - 4)];
274 b2 = decB64[data.charAt(data.length() - 3)];
275 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
276 } else if (data.charAt(data.length() - 1) == '=') {
277 b1 = decB64[data.charAt(data.length() - 4)];
278 b2 = decB64[data.charAt(data.length() - 3)];
279 b3 = decB64[data.charAt(data.length() - 2)];
280 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
281 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
283 b1 = decB64[data.charAt(data.length() - 4)];
284 b2 = decB64[data.charAt(data.length() - 3)];
285 b3 = decB64[data.charAt(data.length() - 2)];
286 b4 = decB64[data.charAt(data.length() - 1)];
287 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
288 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
289 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
295 /** Packs 8-bit bytes into a String of 7-bit chars.
296 * @throws IllegalArgumentException when <tt>len</tt> is not a multiple of 7.
297 * @return A String representing the processed bytes. */
298 public static String toStringFrom8bit(byte[] b, int off, int len) throws IllegalArgumentException {
299 if (len % 7 != 0) throw new IllegalArgumentException("len must be a multiple of 7");
300 StringBuffer ret = new StringBuffer();
301 for(int i=off; i<off+len; i += 7) {
303 for(int j=6; j>=0; j--) {
305 l |= (b[i + j] & 0xff);
307 for(int j=0; j<8; j++) {
308 ret.append((char)(l & 0x7f));
312 return ret.toString();
315 /** Packs a String of 7-bit chars into 8-bit bytes.
316 * @throws IllegalArgumentException when <tt>s.length()</tt> is not a multiple of 8.
317 * @return A byte array representing the processed String. */
318 public static byte[] fromStringTo8bit(String s) throws IllegalArgumentException {
319 if (s.length() % 8 != 0) throw new IllegalArgumentException("string length must be a multiple of 8");
320 byte[] ret = new byte[(s.length() / 8) * 7];
321 for(int i=0; i<s.length(); i += 8) {
323 for(int j=7; j>=0; j--) {
325 l |= (s.charAt(i + j) & 0x7fL);
327 for(int j=0; j<7; j++) {
328 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
335 public static class JavaSourceCode {
337 public static final int LINE_LENGTH = 80 / 4;
338 public static void main(String[] s) throws Exception { System.out.println(encode(s[0], s[1], System.in)); }
340 public static InputStream decode(String s) throws IOException {
341 return new GZIPInputStream(new StringInputStream(s)); }
343 private static class StringInputStream extends InputStream {
344 private final String s;
345 private final int length;
347 public StringInputStream(String s) { this.s = s; this.length = s.length(); }
349 byte[] b = new byte[1];
350 int numread = read(b, 0, 1);
351 if (numread == -1) return -1;
352 if (numread == 0) throw new Error();
355 public int read(byte[] b, int off, int len) {
356 for(int i=off; i<off+len; i++) {
357 if (pos>=length) return i-off;
358 //int out = s.charAt(pos++);
359 b[i] = (byte)s.charAt(pos++);//(byte)(out > 127 ? 127-out : out);
365 public static String encode(String packageName, String className, InputStream is) throws IOException {
367 // compress first, since the encoded form has more entropy
368 ByteArrayOutputStream baos;
369 OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
371 byte[] buf = new byte[1024];
373 int numread = is.read(buf, 0, buf.length);
374 if (numread == -1) break;
375 os.write(buf, 0, numread);
378 buf = baos.toByteArray();
380 StringBuffer ret = new StringBuffer();
381 ret.append("// generated by " + Encode.class.getName() + "\n\n");
382 ret.append("package " + packageName + ";\n\n");
383 ret.append("public class " + className + " {\n");
384 ret.append(" public static final String data = \n");
385 for(int pos = 0; pos<buf.length;) {
387 for(int i=0; i<LINE_LENGTH && pos<buf.length; i++) {
388 String cs = Integer.toOctalString(buf[pos++] & 0xff);
389 while(cs.length() < 3) cs = "0" + cs;
390 ret.append("\\" + cs);
392 ret.append("\" +\n");
394 ret.append(" \"\";\n");
396 return ret.toString();