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");
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 public static String toBase64String(byte[] data) { return new String(toBase64(data)); }
135 /** Encode the input data producong a base 64 encoded byte array.
136 * @return A byte array containing the base 64 encoded data. */
137 public static byte[] toBase64(byte[] data) {
140 int modulus = data.length % 3;
142 bytes = new byte[4 * data.length / 3];
144 bytes = new byte[4 * ((data.length / 3) + 1)];
147 int dataLength = (data.length - modulus);
149 for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
151 a2 = data[i + 1] & 0xff;
152 a3 = data[i + 2] & 0xff;
154 bytes[j] = encB64[(a1 >>> 2) & 0x3f];
155 bytes[j + 1] = encB64[((a1 << 4) | (a2 >>> 4)) & 0x3f];
156 bytes[j + 2] = encB64[((a2 << 2) | (a3 >>> 6)) & 0x3f];
157 bytes[j + 3] = encB64[a3 & 0x3f];
163 case 0: /* nothing left to do */
166 d1 = data[data.length - 1] & 0xff;
167 b1 = (d1 >>> 2) & 0x3f;
168 b2 = (d1 << 4) & 0x3f;
170 bytes[bytes.length - 4] = encB64[b1];
171 bytes[bytes.length - 3] = encB64[b2];
172 bytes[bytes.length - 2] = (byte)'=';
173 bytes[bytes.length - 1] = (byte)'=';
176 d1 = data[data.length - 2] & 0xff;
177 d2 = data[data.length - 1] & 0xff;
179 b1 = (d1 >>> 2) & 0x3f;
180 b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
181 b3 = (d2 << 2) & 0x3f;
183 bytes[bytes.length - 4] = encB64[b1];
184 bytes[bytes.length - 3] = encB64[b2];
185 bytes[bytes.length - 2] = encB64[b3];
186 bytes[bytes.length - 1] = (byte)'=';
194 private static final byte[] decB64 = new byte[128];
196 for (int i = 'A'; i <= 'Z'; i++) decB64[i] = (byte)(i - 'A');
197 for (int i = 'a'; i <= 'z'; i++) decB64[i] = (byte)(i - 'a' + 26);
198 for (int i = '0'; i <= '9'; i++) decB64[i] = (byte)(i - '0' + 52);
203 /** Decode base 64 encoded input data.
204 * @return A byte array representing the decoded data. */
205 public static byte[] fromBase64(byte[] data) {
209 if (data[data.length - 2] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
210 else if (data[data.length - 1] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
211 else bytes = new byte[((data.length / 4) * 3)];
213 for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) {
214 b1 = decB64[data[i]];
215 b2 = decB64[data[i + 1]];
216 b3 = decB64[data[i + 2]];
217 b4 = decB64[data[i + 3]];
219 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
220 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
221 bytes[j + 2] = (byte)((b3 << 6) | b4);
224 if (data[data.length - 2] == '=') {
225 b1 = decB64[data[data.length - 4]];
226 b2 = decB64[data[data.length - 3]];
227 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
228 } else if (data[data.length - 1] == '=') {
229 b1 = decB64[data[data.length - 4]];
230 b2 = decB64[data[data.length - 3]];
231 b3 = decB64[data[data.length - 2]];
232 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
233 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
235 b1 = decB64[data[data.length - 4]];
236 b2 = decB64[data[data.length - 3]];
237 b3 = decB64[data[data.length - 2]];
238 b4 = decB64[data[data.length - 1]];
239 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
240 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
241 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
246 /** Decode a base 64 encoded String.
247 * @return A byte array representing the decoded data. */
248 public static byte[] fromBase64(String data) {
252 if (data.charAt(data.length() - 2) == '=')
253 bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
254 else if (data.charAt(data.length() - 1) == '=')
255 bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
257 bytes = new byte[((data.length() / 4) * 3)];
259 for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) {
260 b1 = decB64[data.charAt(i)];
261 b2 = decB64[data.charAt(i + 1)];
262 b3 = decB64[data.charAt(i + 2)];
263 b4 = decB64[data.charAt(i + 3)];
265 bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
266 bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
267 bytes[j + 2] = (byte)((b3 << 6) | b4);
270 if (data.charAt(data.length() - 2) == '=') {
271 b1 = decB64[data.charAt(data.length() - 4)];
272 b2 = decB64[data.charAt(data.length() - 3)];
273 bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
274 } else if (data.charAt(data.length() - 1) == '=') {
275 b1 = decB64[data.charAt(data.length() - 4)];
276 b2 = decB64[data.charAt(data.length() - 3)];
277 b3 = decB64[data.charAt(data.length() - 2)];
278 bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
279 bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
281 b1 = decB64[data.charAt(data.length() - 4)];
282 b2 = decB64[data.charAt(data.length() - 3)];
283 b3 = decB64[data.charAt(data.length() - 2)];
284 b4 = decB64[data.charAt(data.length() - 1)];
285 bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
286 bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
287 bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
293 /** Packs 8-bit bytes into a String of 7-bit chars.
294 * @throws IllegalArgumentException when <tt>len</tt> is not a multiple of 7.
295 * @return A String representing the processed bytes. */
296 public static String toStringFrom8bit(byte[] b, int off, int len) throws IllegalArgumentException {
297 if (len % 7 != 0) throw new IllegalArgumentException("len must be a multiple of 7");
298 StringBuffer ret = new StringBuffer();
299 for(int i=off; i<off+len; i += 7) {
301 for(int j=6; j>=0; j--) {
303 l |= (b[i + j] & 0xff);
305 for(int j=0; j<8; j++) {
306 ret.append((char)(l & 0x7f));
310 return ret.toString();
313 /** Packs a String of 7-bit chars into 8-bit bytes.
314 * @throws IllegalArgumentException when <tt>s.length()</tt> is not a multiple of 8.
315 * @return A byte array representing the processed String. */
316 public static byte[] fromStringTo8bit(String s) throws IllegalArgumentException {
317 if (s.length() % 8 != 0) throw new IllegalArgumentException("string length must be a multiple of 8");
318 byte[] ret = new byte[(s.length() / 8) * 7];
319 for(int i=0; i<s.length(); i += 8) {
321 for(int j=7; j>=0; j--) {
323 l |= (s.charAt(i + j) & 0x7fL);
325 for(int j=0; j<7; j++) {
326 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
333 public static class JavaSourceCode {
335 public static final int LINE_LENGTH = 80 / 4;
336 public static void main(String[] s) throws Exception { System.out.println(encode(s[0], s[1], System.in)); }
338 public static InputStream decode(String s) throws IOException {
339 return new GZIPInputStream(new StringInputStream(s)); }
341 private static class StringInputStream extends InputStream {
342 private final String s;
343 private final int length;
345 public StringInputStream(String s) { this.s = s; this.length = s.length(); }
347 byte[] b = new byte[1];
348 int numread = read(b, 0, 1);
349 if (numread == -1) return -1;
350 if (numread == 0) throw new Error();
353 public int read(byte[] b, int off, int len) {
354 for(int i=off; i<off+len; i++) {
355 if (pos>=length) return i-off;
356 //int out = s.charAt(pos++);
357 b[i] = (byte)s.charAt(pos++);//(byte)(out > 127 ? 127-out : out);
363 public static String encode(String packageName, String className, InputStream is) throws IOException {
365 // compress first, since the encoded form has more entropy
366 ByteArrayOutputStream baos;
367 OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
369 byte[] buf = new byte[1024];
371 int numread = is.read(buf, 0, buf.length);
372 if (numread == -1) break;
373 os.write(buf, 0, numread);
376 buf = baos.toByteArray();
378 StringBuffer ret = new StringBuffer();
379 ret.append("// generated by " + Encode.class.getName() + "\n\n");
380 ret.append("package " + packageName + ";\n\n");
381 ret.append("public class " + className + " {\n");
382 ret.append(" public static final String data = \n");
383 for(int pos = 0; pos<buf.length;) {
385 for(int i=0; i<LINE_LENGTH && pos<buf.length; i++) {
386 String cs = Integer.toOctalString(buf[pos++] & 0xff);
387 while(cs.length() < 3) cs = "0" + cs;
388 ret.append("\\" + cs);
390 ret.append("\" +\n");
392 ret.append(" \"\";\n");
394 return ret.toString();