c8f20ef8260bea651752845cfc2b536b488dc1f1
[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 /** General <tt>String</tt> and <tt>byte[]</tt> processing functions,
12  *  including Base64 and a safe filename transform.
13  *
14  *  @author adam@ibex.org
15  */
16 public final class Encode {
17
18     public static class QuotedPrintable {
19         public static String decode(String s, boolean lax) {
20         //
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
27         //
28
29             // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
30             return s;
31         }
32     }
33
34
35     public static class RFC2047 {
36         public static String decode(String s) {
37             /*
38             try { while (s.indexOf("=?") != -1) {
39                 String pre = s.substring(0, s.indexOf("=?"));
40                 s = s.substring(s.indexOf("=?") + 2);
41
42                 // MIME charset; FIXME use this
43                 String charset = s.substring(0, s.indexOf('?')).toLowerCase();
44                 s = s.substring(s.indexOf('?') + 1);
45
46                 String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
47                 s = s.substring(s.indexOf('?') + 1);
48
49                 String encodedText = s.substring(0, s.indexOf("?="));
50
51                 if (encoding.equals("b"))      encodedText = new String(Base64.decode(encodedText));
52
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+"\"");
56
57                 String post = s.substring(s.indexOf("?=") + 2);
58                 s = pre + encodedText + post;
59
60                 // FIXME re-encode when transmitting
61
62             } } catch (Exception e) {
63                 Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
64                 Log.warn(MIME.class, e);
65             }
66             */
67             return s;
68         }
69     }
70
71
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)); }
76
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' };
79
80     public static String toFilename(String s) {
81         StringBuffer sb = new StringBuffer();
82         try {
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]);
88                 else sb.append(c);
89             }
90             return sb.toString();
91         } catch (UnsupportedEncodingException uee) {
92             throw new Error("this should never happen; Java spec mandates UTF-8 support");
93         }
94     }
95
96     public static String fromFilename(String s) {
97         StringBuffer sb = new StringBuffer();
98         byte[] b = new byte[s.length() * 2];
99         int bytes = 0;
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;
104         }
105         try {
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");
109         }
110     }
111
112
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)'/'
124     };
125
126     // FIXME could be far more efficient
127     public static class Base64InputStream extends ByteArrayInputStream {
128         public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
129     }
130
131     public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
132
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) {
136         byte[]  bytes;
137                 
138         int modulus = data.length % 3;
139         if (modulus == 0) {
140             bytes = new byte[4 * data.length / 3];
141         } else {
142             bytes = new byte[4 * ((data.length / 3) + 1)];
143         }
144
145         int dataLength = (data.length - modulus);
146         int a1, a2, a3;
147         for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
148             a1 = data[i] & 0xff;
149             a2 = data[i + 1] & 0xff;
150             a3 = data[i + 2] & 0xff;
151             
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];
156         }
157
158         int b1, b2, b3;
159         int d1, d2;
160         switch (modulus) {
161                 case 0:         /* nothing left to do */
162                     break;
163                 case 1:
164                     d1 = data[data.length - 1] & 0xff;
165                     b1 = (d1 >>> 2) & 0x3f;
166                     b2 = (d1 << 4) & 0x3f;
167
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)'=';
172                     break;
173                 case 2:
174                     d1 = data[data.length - 2] & 0xff;
175                     d2 = data[data.length - 1] & 0xff;
176
177                     b1 = (d1 >>> 2) & 0x3f;
178                     b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
179                     b3 = (d2 << 2) & 0x3f;
180
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)'=';
185                     break;
186             }
187
188         return bytes;
189     }
190
191
192     private static final byte[] decB64 = new byte[128];
193     static {
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);
197         decB64['+'] = 62;
198         decB64['/'] = 63;
199     }
200
201     /** Decode base 64 encoded input data.
202      *  @return A byte array representing the decoded data. */
203     public static byte[] fromBase64(byte[] data) {
204         byte[]  bytes;
205         byte    b1, b2, b3, b4;
206
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)];
210
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]];
216             
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);
220         }
221
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));
232         } else {
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);
240         }
241         return bytes;
242     }
243
244     /** Decode a base 64 encoded String.
245      *  @return A byte array representing the decoded data. */
246     public static byte[] fromBase64(String data) {
247         byte[]  bytes;
248         byte    b1, b2, b3, b4;
249
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];
254         else
255             bytes = new byte[((data.length() / 4) * 3)];
256
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)];
262             
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);
266         }
267
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));
278         } else {
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);
286         }
287         return bytes;
288     }
289
290
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) {
298             long l = 0;
299             for(int j=6; j>=0; j--) {
300                 l <<= 8;
301                 l |= (b[i + j] & 0xff);
302             }
303             for(int j=0; j<8; j++) {
304                 ret.append((char)(l & 0x7f));
305                 l >>= 7;
306             }
307         }
308         return ret.toString();
309     }
310
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) {
318             long l = 0;
319             for(int j=7; j>=0; j--) {
320                 l <<= 7;
321                 l |= (s.charAt(i + j) & 0x7fL);
322             }
323             for(int j=0; j<7; j++) {
324                 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
325                 l >>= 8;
326             }
327         }
328         return ret;
329     }
330
331     public static class JavaSourceCode {
332
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)); }
335
336         public static InputStream decode(String s) throws IOException {
337             return new GZIPInputStream(new StringInputStream(s)); }
338         
339         private static class StringInputStream extends InputStream {
340             private final String s;
341             private final int length;
342             private int pos = 0;
343             public StringInputStream(String s) { this.s = s; this.length = s.length(); }
344             public int read() {
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();
349                 return b[0] & 0xff;
350             }
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);
356                 }
357                 return len;
358             }
359         }
360
361         public static String encode(String packageName, String className, InputStream is) throws IOException {
362
363             // compress first, since the encoded form has more entropy
364             ByteArrayOutputStream baos;
365             OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
366
367             byte[] buf = new byte[1024];
368             while(true) {
369                 int numread = is.read(buf, 0, buf.length);
370                 if (numread == -1) break;
371                 os.write(buf, 0, numread);
372             }
373             os.close();
374             buf = baos.toByteArray();
375             
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;) {
382                 ret.append("        \"");
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);
387                 }
388                 ret.append("\" +\n");
389             }
390             ret.append("    \"\";\n");
391             ret.append("}\n");
392             return ret.toString();
393         }
394
395     }
396
397 }
398