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 /** General <tt>String</tt> and <tt>byte[]</tt> processing functions,
12 * including Base64 and a safe filename transform.
14 * @author adam@ibex.org
16 public final class Encode {
18 public static class QuotedPrintable {
19 public static String decode(String s, boolean lax) {
21 // =XX -> hex representation, must be uppercase
22 // 9, 32, 33-60, 62-126 can be literal
23 // 9, 32 at end-of-line must get encoded
24 // trailing whitespace must be deleted when decoding
25 // =\n = soft line break
26 // lines cannot be more than 76 chars long
29 // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
35 public static class RFC2047 {
36 public static String decode(String s) {
38 try { while (s.indexOf("=?") != -1) {
39 String pre = s.substring(0, s.indexOf("=?"));
40 s = s.substring(s.indexOf("=?") + 2);
42 // MIME charset; FIXME use this
43 String charset = s.substring(0, s.indexOf('?')).toLowerCase();
44 s = s.substring(s.indexOf('?') + 1);
46 String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
47 s = s.substring(s.indexOf('?') + 1);
49 String encodedText = s.substring(0, s.indexOf("?="));
51 if (encoding.equals("b")) encodedText = new String(Base64.decode(encodedText));
53 // except that ANY char can be endoed (unlike real qp)
54 else if (encoding.equals("q")) encodedText = MIME.QuotedPrintable.decode(encodedText, true);
55 else Log.warn(MIME.class, "unknown RFC2047 encoding \""+encoding+"\"");
57 String post = s.substring(s.indexOf("?=") + 2);
58 s = pre + encodedText + post;
60 // FIXME re-encode when transmitting
62 } } catch (Exception e) {
63 Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
64 Log.warn(MIME.class, e);
72 public static long twoFloatsToLong(float a, float b) {
73 return ((Float.floatToIntBits(a) & 0xffffffffL) << 32) | (Float.floatToIntBits(b) & 0xffffffffL); }
74 public static float longToFloat1(long l) { return Float.intBitsToFloat((int)((l >> 32) & 0xffffffff)); }
75 public static float longToFloat2(long l) { return Float.intBitsToFloat((int)(l & 0xffffffff)); }
77 private static final char[] fn =
78 new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
80 public static String toFilename(String s) {
81 StringBuffer sb = new StringBuffer();
83 byte[] b = s.getBytes("UTF-8");
84 for(int i=0; i<b.length; i++) {
85 char c = (char)(b[i] & 0xff);
86 if (c == File.separatorChar || c < 32 || c > 126 || c == '%' || (i == 0 && c == '.'))
87 sb.append("%" + fn[(b[i] & 0xf0) >> 8] + fn[b[i] & 0xf]);
91 } catch (UnsupportedEncodingException uee) {
92 throw new Error("this should never happen; Java spec mandates UTF-8 support");
96 public static String fromFilename(String s) {
97 StringBuffer sb = new StringBuffer();
98 byte[] b = new byte[s.length() * 2];
100 for(int i=0; i<s.length(); i++) {
101 char c = s.charAt(i);
102 if (c == '%') b[bytes++] = (byte)Integer.parseInt(("" + s.charAt(++i) + s.charAt(++i)), 16);
103 else b[bytes++] = (byte)c;
106 return new String(b, 0, bytes, "UTF-8");
107 } catch (UnsupportedEncodingException uee) {
108 throw new Error("this should never happen; Java spec mandates UTF-8 support");
112 public static class Ascii {
113 public static class In extends InputStream {
114 public final int radix;
115 private final Reader reader;
116 private int blen = 0;
117 private byte[] bbuf = new byte[1024 * 16];
118 private int clen = 0;
119 private char[] cbuf = new char[1024 * 16];
120 public In(int radix, InputStream is) {
121 // FIXME: radix must be a power of 2
123 this.reader = new InputStreamReader(is);
125 public int read() throws IOException {
126 byte[] buf = new byte[1];
128 int numread = read(buf, 0, 1);
129 if (numread<0) return -1;
130 if (numread>0) return (buf[0] & 0xff);
133 public long skip(long n) throws IOException {
134 while(blen<=0) if (!fillb()) return -1;
135 int numskip = Math.min((int)n, blen);
136 if (blen > numskip) System.arraycopy(bbuf, numskip, bbuf, 0, blen-numskip);
140 public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
141 public int available() { return blen; }
142 public boolean markSupported() { return false; }
143 public void close() throws IOException { reader.close(); }
144 public int read(byte[] buf, int off, int len) throws IOException {
145 while(blen<=0) if (!fillb()) return -1;
146 int numread = Math.min(len, blen);
147 System.arraycopy(bbuf, 0, buf, off, numread);
148 if (numread < blen) System.arraycopy(bbuf, numread, bbuf, 0, blen-numread);
152 public boolean fillc() throws IOException {
153 int numread = reader.read(cbuf, clen, cbuf.length - clen);
154 if (numread == -1) return false;
156 for(int i=0; i<numread; i++) {
157 if (!Character.isWhitespace(cbuf[clen+i]))
158 cbuf[clen+(j++)] = cbuf[clen+i];
163 public boolean fillb() throws IOException {
165 int bytesPerMinChars;
167 case 2: { minChars = 8; bytesPerMinChars = 1; break; }
168 case 16: { minChars = 2; bytesPerMinChars = 1; break; }
169 default: throw new Error("unsupported");
171 while(clen < minChars) if (!fillc()) return false;
173 while(pos <= clen - minChars) {
174 bbuf[blen++] = (byte)Integer.parseInt(new String(cbuf, pos, minChars), radix);
177 System.arraycopy(cbuf, pos, cbuf, 0, clen-pos);
184 private static final byte[] encB64 = {
185 (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
186 (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
187 (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
188 (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
189 (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
190 (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
191 (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
192 (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1',
193 (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
194 (byte)'9', (byte)'+', (byte)'/'
197 // FIXME could be far more efficient
198 public static class Base64InputStream extends ByteArrayInputStream {
199 public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
202 public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
204 /** Encode the input data producong a base 64 encoded byte array.
205 * @return A byte array containing the base 64 encoded data. */
206 public static byte[] toBase64(byte[] data) {
209 int modulus = data.length % 3;
211 bytes = new byte[4 * data.length / 3];
213 bytes = new byte[4 * ((data.length / 3) + 1)];
216 int dataLength = (data.length - modulus);
218 for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
220 a2 = data[i + 1] & 0xff;
221 a3 = data[i + 2] & 0xff;
223 bytes[j] = encB64[(a1 >>> 2) & 0x3f];
224 bytes[j + 1] = encB64[((a1 << 4) | (a2 >>> 4)) & 0x3f];
225 bytes[j + 2] = encB64[((a2 << 2) | (a3 >>> 6)) & 0x3f];
226 bytes[j + 3] = encB64[a3 & 0x3f];
232 case 0: /* nothing left to do */
235 d1 = data[data.length - 1] & 0xff;
236 b1 = (d1 >>> 2) & 0x3f;
237 b2 = (d1 << 4) & 0x3f;
239 bytes[bytes.length - 4] = encB64[b1];
240 bytes[bytes.length - 3] = encB64[b2];
241 bytes[bytes.length - 2] = (byte)'=';
242 bytes[bytes.length - 1] = (byte)'=';
245 d1 = data[data.length - 2] & 0xff;
246 d2 = data[data.length - 1] & 0xff;
248 b1 = (d1 >>> 2) & 0x3f;
249 b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
250 b3 = (d2 << 2) & 0x3f;
252 bytes[bytes.length - 4] = encB64[b1];
253 bytes[bytes.length - 3] = encB64[b2];
254 bytes[bytes.length - 2] = encB64[b3];
255 bytes[bytes.length - 1] = (byte)'=';
263 private static final byte[] decB64 = new byte[128];
265 for (int i = 'A'; i <= 'Z'; i++) decB64[i] = (byte)(i - 'A');
266 for (int i = 'a'; i <= 'z'; i++) decB64[i] = (byte)(i - 'a' + 26);
267 for (int i = '0'; i <= '9'; i++) decB64[i] = (byte)(i - '0' + 52);
272 /** Decode base 64 encoded input data.
273 * @return A byte array representing the decoded data. */
274 public static byte[] fromBase64(byte[] data) {
278 if (data[data.length - 2] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
279 else if (data[data.length - 1] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
280 else bytes = new byte[((data.length / 4) * 3)];
282 for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) {
283 b1 = decB64[data[i]];
284 b2 = decB64[data[i + 1]];
285 b3 = decB64[data[i + 2]];
286 b4 = decB64[data[i + 3]];
288 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
289 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
290 bytes[j + 2] = (byte)((b3 << 6) | b4);
293 if (data[data.length - 2] == '=') {
294 b1 = decB64[data[data.length - 4]];
295 b2 = decB64[data[data.length - 3]];
296 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
297 } else if (data[data.length - 1] == '=') {
298 b1 = decB64[data[data.length - 4]];
299 b2 = decB64[data[data.length - 3]];
300 b3 = decB64[data[data.length - 2]];
301 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
302 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
304 b1 = decB64[data[data.length - 4]];
305 b2 = decB64[data[data.length - 3]];
306 b3 = decB64[data[data.length - 2]];
307 b4 = decB64[data[data.length - 1]];
308 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
309 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
310 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
315 /** Decode a base 64 encoded String.
316 * @return A byte array representing the decoded data. */
317 public static byte[] fromBase64(String data) {
321 if (data.charAt(data.length() - 2) == '=')
322 bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
323 else if (data.charAt(data.length() - 1) == '=')
324 bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
326 bytes = new byte[((data.length() / 4) * 3)];
328 for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) {
329 b1 = decB64[data.charAt(i)];
330 b2 = decB64[data.charAt(i + 1)];
331 b3 = decB64[data.charAt(i + 2)];
332 b4 = decB64[data.charAt(i + 3)];
334 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
335 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
336 bytes[j + 2] = (byte)((b3 << 6) | b4);
339 if (data.charAt(data.length() - 2) == '=') {
340 b1 = decB64[data.charAt(data.length() - 4)];
341 b2 = decB64[data.charAt(data.length() - 3)];
342 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
343 } else if (data.charAt(data.length() - 1) == '=') {
344 b1 = decB64[data.charAt(data.length() - 4)];
345 b2 = decB64[data.charAt(data.length() - 3)];
346 b3 = decB64[data.charAt(data.length() - 2)];
347 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
348 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
350 b1 = decB64[data.charAt(data.length() - 4)];
351 b2 = decB64[data.charAt(data.length() - 3)];
352 b3 = decB64[data.charAt(data.length() - 2)];
353 b4 = decB64[data.charAt(data.length() - 1)];
354 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
355 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
356 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
362 /** Packs 8-bit bytes into a String of 7-bit chars.
363 * @throws IllegalArgumentException when <tt>len</tt> is not a multiple of 7.
364 * @return A String representing the processed bytes. */
365 public static String toStringFrom8bit(byte[] b, int off, int len) throws IllegalArgumentException {
366 if (len % 7 != 0) throw new IllegalArgumentException("len must be a multiple of 7");
367 StringBuffer ret = new StringBuffer();
368 for(int i=off; i<off+len; i += 7) {
370 for(int j=6; j>=0; j--) {
372 l |= (b[i + j] & 0xff);
374 for(int j=0; j<8; j++) {
375 ret.append((char)(l & 0x7f));
379 return ret.toString();
382 /** Packs a String of 7-bit chars into 8-bit bytes.
383 * @throws IllegalArgumentException when <tt>s.length()</tt> is not a multiple of 8.
384 * @return A byte array representing the processed String. */
385 public static byte[] fromStringTo8bit(String s) throws IllegalArgumentException {
386 if (s.length() % 8 != 0) throw new IllegalArgumentException("string length must be a multiple of 8");
387 byte[] ret = new byte[(s.length() / 8) * 7];
388 for(int i=0; i<s.length(); i += 8) {
390 for(int j=7; j>=0; j--) {
392 l |= (s.charAt(i + j) & 0x7fL);
394 for(int j=0; j<7; j++) {
395 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
402 public static class JavaSourceCode {
404 public static final int LINE_LENGTH = 80 / 4;
405 public static void main(String[] s) throws Exception { System.out.println(encode(s[0], s[1], System.in)); }
407 public static InputStream decode(String s) throws IOException {
408 return new GZIPInputStream(new StringInputStream(s)); }
410 private static class StringInputStream extends InputStream {
411 private final String s;
412 private final int length;
414 public StringInputStream(String s) { this.s = s; this.length = s.length(); }
416 byte[] b = new byte[1];
417 int numread = read(b, 0, 1);
418 if (numread == -1) return -1;
419 if (numread == 0) throw new Error();
422 public int read(byte[] b, int off, int len) {
423 for(int i=off; i<off+len; i++) {
424 if (pos>=length) return i-off;
425 //int out = s.charAt(pos++);
426 b[i] = (byte)s.charAt(pos++);//(byte)(out > 127 ? 127-out : out);
432 public static String encode(String packageName, String className, InputStream is) throws IOException {
434 // compress first, since the encoded form has more entropy
435 ByteArrayOutputStream baos;
436 OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
438 byte[] buf = new byte[1024];
440 int numread = is.read(buf, 0, buf.length);
441 if (numread == -1) break;
442 os.write(buf, 0, numread);
445 buf = baos.toByteArray();
447 StringBuffer ret = new StringBuffer();
448 ret.append("// generated by " + Encode.class.getName() + "\n\n");
449 ret.append("package " + packageName + ";\n\n");
450 ret.append("public class " + className + " {\n");
451 ret.append(" public static final String data = \n");
452 for(int pos = 0; pos<buf.length;) {
454 for(int i=0; i<LINE_LENGTH && pos<buf.length; i++) {
455 String cs = Integer.toOctalString(buf[pos++] & 0xff);
456 while(cs.length() < 3) cs = "0" + cs;
457 ret.append("\\" + cs);
459 ret.append("\" +\n");
461 ret.append(" \"\";\n");
463 return ret.toString();