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)); }
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");
113 private static final byte[] encB64 = {
114 (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
115 (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
116 (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
117 (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
118 (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
119 (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
120 (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
121 (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1',
122 (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
123 (byte)'9', (byte)'+', (byte)'/'
126 // FIXME could be far more efficient
127 public static class Base64InputStream extends ByteArrayInputStream {
128 public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
131 public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
133 /** Encode the input data producong a base 64 encoded byte array.
134 * @return A byte array containing the base 64 encoded data. */
135 public static byte[] toBase64(byte[] data) {
138 int modulus = data.length % 3;
140 bytes = new byte[4 * data.length / 3];
142 bytes = new byte[4 * ((data.length / 3) + 1)];
145 int dataLength = (data.length - modulus);
147 for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
149 a2 = data[i + 1] & 0xff;
150 a3 = data[i + 2] & 0xff;
152 bytes[j] = encB64[(a1 >>> 2) & 0x3f];
153 bytes[j + 1] = encB64[((a1 << 4) | (a2 >>> 4)) & 0x3f];
154 bytes[j + 2] = encB64[((a2 << 2) | (a3 >>> 6)) & 0x3f];
155 bytes[j + 3] = encB64[a3 & 0x3f];
161 case 0: /* nothing left to do */
164 d1 = data[data.length - 1] & 0xff;
165 b1 = (d1 >>> 2) & 0x3f;
166 b2 = (d1 << 4) & 0x3f;
168 bytes[bytes.length - 4] = encB64[b1];
169 bytes[bytes.length - 3] = encB64[b2];
170 bytes[bytes.length - 2] = (byte)'=';
171 bytes[bytes.length - 1] = (byte)'=';
174 d1 = data[data.length - 2] & 0xff;
175 d2 = data[data.length - 1] & 0xff;
177 b1 = (d1 >>> 2) & 0x3f;
178 b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
179 b3 = (d2 << 2) & 0x3f;
181 bytes[bytes.length - 4] = encB64[b1];
182 bytes[bytes.length - 3] = encB64[b2];
183 bytes[bytes.length - 2] = encB64[b3];
184 bytes[bytes.length - 1] = (byte)'=';
192 private static final byte[] decB64 = new byte[128];
194 for (int i = 'A'; i <= 'Z'; i++) decB64[i] = (byte)(i - 'A');
195 for (int i = 'a'; i <= 'z'; i++) decB64[i] = (byte)(i - 'a' + 26);
196 for (int i = '0'; i <= '9'; i++) decB64[i] = (byte)(i - '0' + 52);
201 /** Decode base 64 encoded input data.
202 * @return A byte array representing the decoded data. */
203 public static byte[] fromBase64(byte[] data) {
207 if (data[data.length - 2] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
208 else if (data[data.length - 1] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
209 else bytes = new byte[((data.length / 4) * 3)];
211 for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) {
212 b1 = decB64[data[i]];
213 b2 = decB64[data[i + 1]];
214 b3 = decB64[data[i + 2]];
215 b4 = decB64[data[i + 3]];
217 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
218 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
219 bytes[j + 2] = (byte)((b3 << 6) | b4);
222 if (data[data.length - 2] == '=') {
223 b1 = decB64[data[data.length - 4]];
224 b2 = decB64[data[data.length - 3]];
225 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
226 } else if (data[data.length - 1] == '=') {
227 b1 = decB64[data[data.length - 4]];
228 b2 = decB64[data[data.length - 3]];
229 b3 = decB64[data[data.length - 2]];
230 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
231 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
233 b1 = decB64[data[data.length - 4]];
234 b2 = decB64[data[data.length - 3]];
235 b3 = decB64[data[data.length - 2]];
236 b4 = decB64[data[data.length - 1]];
237 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
238 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
239 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
244 /** Decode a base 64 encoded String.
245 * @return A byte array representing the decoded data. */
246 public static byte[] fromBase64(String data) {
250 if (data.charAt(data.length() - 2) == '=')
251 bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
252 else if (data.charAt(data.length() - 1) == '=')
253 bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
255 bytes = new byte[((data.length() / 4) * 3)];
257 for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) {
258 b1 = decB64[data.charAt(i)];
259 b2 = decB64[data.charAt(i + 1)];
260 b3 = decB64[data.charAt(i + 2)];
261 b4 = decB64[data.charAt(i + 3)];
263 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
264 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
265 bytes[j + 2] = (byte)((b3 << 6) | b4);
268 if (data.charAt(data.length() - 2) == '=') {
269 b1 = decB64[data.charAt(data.length() - 4)];
270 b2 = decB64[data.charAt(data.length() - 3)];
271 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
272 } else if (data.charAt(data.length() - 1) == '=') {
273 b1 = decB64[data.charAt(data.length() - 4)];
274 b2 = decB64[data.charAt(data.length() - 3)];
275 b3 = decB64[data.charAt(data.length() - 2)];
276 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
277 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
279 b1 = decB64[data.charAt(data.length() - 4)];
280 b2 = decB64[data.charAt(data.length() - 3)];
281 b3 = decB64[data.charAt(data.length() - 2)];
282 b4 = decB64[data.charAt(data.length() - 1)];
283 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
284 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
285 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
291 /** Packs 8-bit bytes into a String of 7-bit chars.
292 * @throws IllegalArgumentException when <tt>len</tt> is not a multiple of 7.
293 * @return A String representing the processed bytes. */
294 public static String toStringFrom8bit(byte[] b, int off, int len) throws IllegalArgumentException {
295 if (len % 7 != 0) throw new IllegalArgumentException("len must be a multiple of 7");
296 StringBuffer ret = new StringBuffer();
297 for(int i=off; i<off+len; i += 7) {
299 for(int j=6; j>=0; j--) {
301 l |= (b[i + j] & 0xff);
303 for(int j=0; j<8; j++) {
304 ret.append((char)(l & 0x7f));
308 return ret.toString();
311 /** Packs a String of 7-bit chars into 8-bit bytes.
312 * @throws IllegalArgumentException when <tt>s.length()</tt> is not a multiple of 8.
313 * @return A byte array representing the processed String. */
314 public static byte[] fromStringTo8bit(String s) throws IllegalArgumentException {
315 if (s.length() % 8 != 0) throw new IllegalArgumentException("string length must be a multiple of 8");
316 byte[] ret = new byte[(s.length() / 8) * 7];
317 for(int i=0; i<s.length(); i += 8) {
319 for(int j=7; j>=0; j--) {
321 l |= (s.charAt(i + j) & 0x7fL);
323 for(int j=0; j<7; j++) {
324 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
331 public static class JavaSourceCode {
333 public static final int LINE_LENGTH = 80 / 4;
334 public static void main(String[] s) throws Exception { System.out.println(encode(s[0], s[1], System.in)); }
336 public static InputStream decode(String s) throws IOException {
337 return new GZIPInputStream(new StringInputStream(s)); }
339 private static class StringInputStream extends InputStream {
340 private final String s;
341 private final int length;
343 public StringInputStream(String s) { this.s = s; this.length = s.length(); }
345 byte[] b = new byte[1];
346 int numread = read(b, 0, 1);
347 if (numread == -1) return -1;
348 if (numread == 0) throw new Error();
351 public int read(byte[] b, int off, int len) {
352 for(int i=off; i<off+len; i++) {
353 if (pos>=length) return i-off;
354 //int out = s.charAt(pos++);
355 b[i] = (byte)s.charAt(pos++);//(byte)(out > 127 ? 127-out : out);
361 public static String encode(String packageName, String className, InputStream is) throws IOException {
363 // compress first, since the encoded form has more entropy
364 ByteArrayOutputStream baos;
365 OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
367 byte[] buf = new byte[1024];
369 int numread = is.read(buf, 0, buf.length);
370 if (numread == -1) break;
371 os.write(buf, 0, numread);
374 buf = baos.toByteArray();
376 StringBuffer ret = new StringBuffer();
377 ret.append("// generated by " + Encode.class.getName() + "\n\n");
378 ret.append("package " + packageName + ";\n\n");
379 ret.append("public class " + className + " {\n");
380 ret.append(" public static final String data = \n");
381 for(int pos = 0; pos<buf.length;) {
383 for(int i=0; i<LINE_LENGTH && pos<buf.length; i++) {
384 String cs = Integer.toOctalString(buf[pos++] & 0xff);
385 while(cs.length() < 3) cs = "0" + cs;
386 ret.append("\\" + cs);
388 ret.append("\" +\n");
390 ret.append(" \"\";\n");
392 return ret.toString();