note to self
[org.ibex.util.git] / src / org / ibex / util / Encode.java
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.
4
5 package org.ibex.util;
6
7 import java.io.*;
8 import java.util.zip.GZIPInputStream;
9 import java.util.zip.GZIPOutputStream;
10
11 // FEATURE: add ASCII85Encoding (see pdf spec)
12
13 /** General <tt>String</tt> and <tt>byte[]</tt> processing functions,
14  *  including Base64 and a safe filename transform.
15  *
16  *  @author adam@ibex.org
17  */
18 public final class Encode {
19
20     public static class QuotedPrintable {
21         public static String decode(String s, boolean lax) {
22         //
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
29         //
30
31             // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
32             return s;
33         }
34     }
35
36
37     public static class RFC2047 {
38         public static String decode(String s) {
39             /*
40             try { while (s.indexOf("=?") != -1) {
41                 String pre = s.substring(0, s.indexOf("=?"));
42                 s = s.substring(s.indexOf("=?") + 2);
43
44                 // MIME charset; FIXME use this
45                 String charset = s.substring(0, s.indexOf('?')).toLowerCase();
46                 s = s.substring(s.indexOf('?') + 1);
47
48                 String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
49                 s = s.substring(s.indexOf('?') + 1);
50
51                 String encodedText = s.substring(0, s.indexOf("?="));
52
53                 if (encoding.equals("b"))      encodedText = new String(Base64.decode(encodedText));
54
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+"\"");
58
59                 String post = s.substring(s.indexOf("?=") + 2);
60                 s = pre + encodedText + post;
61
62                 // FIXME re-encode when transmitting
63
64             } } catch (Exception e) {
65                 Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
66                 Log.warn(MIME.class, e);
67             }
68             */
69             return s;
70         }
71     }
72
73
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)); }
78
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' };
81
82     public static String toFilename(String s) {
83         StringBuffer sb = new StringBuffer();
84         try {
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]);
90                 else sb.append(c);
91             }
92             return sb.toString();
93         } catch (UnsupportedEncodingException uee) {
94             throw new Error("this should never happen; Java spec mandates UTF-8 support");
95         }
96     }
97
98     public static String fromFilename(String s) {
99         StringBuffer sb = new StringBuffer();
100         byte[] b = new byte[s.length() * 2];
101         int bytes = 0;
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;
106         }
107         try {
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");
111         }
112     }
113
114
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)'/'
126     };
127
128     // FIXME could be far more efficient
129     public static class Base64InputStream extends ByteArrayInputStream {
130         public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
131     }
132
133     public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
134
135     public static String toBase64String(byte[] data) { return new String(toBase64(data)); }
136
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) {
140         byte[]  bytes;
141                 
142         int modulus = data.length % 3;
143         if (modulus == 0) {
144             bytes = new byte[4 * data.length / 3];
145         } else {
146             bytes = new byte[4 * ((data.length / 3) + 1)];
147         }
148
149         int dataLength = (data.length - modulus);
150         int a1, a2, a3;
151         for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
152             a1 = data[i] & 0xff;
153             a2 = data[i + 1] & 0xff;
154             a3 = data[i + 2] & 0xff;
155             
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];
160         }
161
162         int b1, b2, b3;
163         int d1, d2;
164         switch (modulus) {
165                 case 0:         /* nothing left to do */
166                     break;
167                 case 1:
168                     d1 = data[data.length - 1] & 0xff;
169                     b1 = (d1 >>> 2) & 0x3f;
170                     b2 = (d1 << 4) & 0x3f;
171
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)'=';
176                     break;
177                 case 2:
178                     d1 = data[data.length - 2] & 0xff;
179                     d2 = data[data.length - 1] & 0xff;
180
181                     b1 = (d1 >>> 2) & 0x3f;
182                     b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
183                     b3 = (d2 << 2) & 0x3f;
184
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)'=';
189                     break;
190             }
191
192         return bytes;
193     }
194
195
196     private static final byte[] decB64 = new byte[128];
197     static {
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);
201         decB64['+'] = 62;
202         decB64['/'] = 63;
203     }
204
205     /** Decode base 64 encoded input data.
206      *  @return A byte array representing the decoded data. */
207     public static byte[] fromBase64(byte[] data) {
208         byte[]  bytes;
209         byte    b1, b2, b3, b4;
210
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)];
214
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]];
220             
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);
224         }
225
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));
236         } else {
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);
244         }
245         return bytes;
246     }
247
248     /** Decode a base 64 encoded String.
249      *  @return A byte array representing the decoded data. */
250     public static byte[] fromBase64(String data) {
251         byte[]  bytes;
252         byte    b1, b2, b3, b4;
253
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];
258         else
259             bytes = new byte[((data.length() / 4) * 3)];
260
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)];
266             
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);
270         }
271
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));
282         } else {
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);
290         }
291         return bytes;
292     }
293
294
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) {
302             long l = 0;
303             for(int j=6; j>=0; j--) {
304                 l <<= 8;
305                 l |= (b[i + j] & 0xff);
306             }
307             for(int j=0; j<8; j++) {
308                 ret.append((char)(l & 0x7f));
309                 l >>= 7;
310             }
311         }
312         return ret.toString();
313     }
314
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) {
322             long l = 0;
323             for(int j=7; j>=0; j--) {
324                 l <<= 7;
325                 l |= (s.charAt(i + j) & 0x7fL);
326             }
327             for(int j=0; j<7; j++) {
328                 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
329                 l >>= 8;
330             }
331         }
332         return ret;
333     }
334
335     public static class JavaSourceCode {
336
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)); }
339
340         public static InputStream decode(String s) throws IOException {
341             return new GZIPInputStream(new StringInputStream(s)); }
342         
343         private static class StringInputStream extends InputStream {
344             private final String s;
345             private final int length;
346             private int pos = 0;
347             public StringInputStream(String s) { this.s = s; this.length = s.length(); }
348             public int read() {
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();
353                 return b[0] & 0xff;
354             }
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);
360                 }
361                 return len;
362             }
363         }
364
365         public static String encode(String packageName, String className, InputStream is) throws IOException {
366
367             // compress first, since the encoded form has more entropy
368             ByteArrayOutputStream baos;
369             OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
370
371             byte[] buf = new byte[1024];
372             while(true) {
373                 int numread = is.read(buf, 0, buf.length);
374                 if (numread == -1) break;
375                 os.write(buf, 0, numread);
376             }
377             os.close();
378             buf = baos.toByteArray();
379             
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;) {
386                 ret.append("        \"");
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);
391                 }
392                 ret.append("\" +\n");
393             }
394             ret.append("    \"\";\n");
395             ret.append("}\n");
396             return ret.toString();
397         }
398
399     }
400
401 }
402