checkpoint
[slipway.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) & 0xffffffff)); }
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     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
122                 this.radix = radix;
123                 this.reader = new InputStreamReader(is);
124             }
125             public int read() throws IOException {
126                 byte[] buf = new byte[1];
127                 while(true) {
128                     int numread = read(buf, 0, 1);
129                     if (numread<0) return -1;
130                     if (numread>0) return (buf[0] & 0xff);
131                 }
132             }
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);
137                 blen -= numskip;
138                 return numskip;
139             }                
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);
149                 blen -= numread;
150                 return numread;
151             }
152             public boolean fillc() throws IOException {
153                 int numread = reader.read(cbuf, clen, cbuf.length - clen);
154                 if (numread == -1) return false;
155                 int j = 0;
156                 for(int i=0; i<numread; i++) {
157                     if (!Character.isWhitespace(cbuf[clen+i]))
158                         cbuf[clen+(j++)] = cbuf[clen+i];
159                 }
160                 clen += j;
161                 return true;
162             }
163             public boolean fillb() throws IOException {
164                 int minChars;
165                 int bytesPerMinChars;
166                 switch(radix) {
167                     case 2: { minChars = 8; bytesPerMinChars = 1; break; }
168                     case 16: { minChars = 2; bytesPerMinChars = 1; break; }
169                     default: throw new Error("unsupported");
170                 }
171                 while(clen < minChars) if (!fillc()) return false;
172                 int pos = 0;
173                 while(pos <= clen - minChars) {
174                     bbuf[blen++] = (byte)Integer.parseInt(new String(cbuf, pos, minChars), radix);
175                     pos += minChars;
176                 }
177                 System.arraycopy(cbuf, pos, cbuf, 0, clen-pos);
178                 clen -= pos;
179                 return true;
180             }
181         }
182     }
183
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)'/'
195     };
196
197     // FIXME could be far more efficient
198     public static class Base64InputStream extends ByteArrayInputStream {
199         public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
200     }
201
202     public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
203
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) {
207         byte[]  bytes;
208                 
209         int modulus = data.length % 3;
210         if (modulus == 0) {
211             bytes = new byte[4 * data.length / 3];
212         } else {
213             bytes = new byte[4 * ((data.length / 3) + 1)];
214         }
215
216         int dataLength = (data.length - modulus);
217         int a1, a2, a3;
218         for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
219             a1 = data[i] & 0xff;
220             a2 = data[i + 1] & 0xff;
221             a3 = data[i + 2] & 0xff;
222             
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];
227         }
228
229         int b1, b2, b3;
230         int d1, d2;
231         switch (modulus) {
232                 case 0:         /* nothing left to do */
233                     break;
234                 case 1:
235                     d1 = data[data.length - 1] & 0xff;
236                     b1 = (d1 >>> 2) & 0x3f;
237                     b2 = (d1 << 4) & 0x3f;
238
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)'=';
243                     break;
244                 case 2:
245                     d1 = data[data.length - 2] & 0xff;
246                     d2 = data[data.length - 1] & 0xff;
247
248                     b1 = (d1 >>> 2) & 0x3f;
249                     b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
250                     b3 = (d2 << 2) & 0x3f;
251
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)'=';
256                     break;
257             }
258
259         return bytes;
260     }
261
262
263     private static final byte[] decB64 = new byte[128];
264     static {
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);
268         decB64['+'] = 62;
269         decB64['/'] = 63;
270     }
271
272     /** Decode base 64 encoded input data.
273      *  @return A byte array representing the decoded data. */
274     public static byte[] fromBase64(byte[] data) {
275         byte[]  bytes;
276         byte    b1, b2, b3, b4;
277
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)];
281
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]];
287             
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);
291         }
292
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));
303         } else {
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);
311         }
312         return bytes;
313     }
314
315     /** Decode a base 64 encoded String.
316      *  @return A byte array representing the decoded data. */
317     public static byte[] fromBase64(String data) {
318         byte[]  bytes;
319         byte    b1, b2, b3, b4;
320
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];
325         else
326             bytes = new byte[((data.length() / 4) * 3)];
327
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)];
333             
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);
337         }
338
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));
349         } else {
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);
357         }
358         return bytes;
359     }
360
361
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) {
369             long l = 0;
370             for(int j=6; j>=0; j--) {
371                 l <<= 8;
372                 l |= (b[i + j] & 0xff);
373             }
374             for(int j=0; j<8; j++) {
375                 ret.append((char)(l & 0x7f));
376                 l >>= 7;
377             }
378         }
379         return ret.toString();
380     }
381
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) {
389             long l = 0;
390             for(int j=7; j>=0; j--) {
391                 l <<= 7;
392                 l |= (s.charAt(i + j) & 0x7fL);
393             }
394             for(int j=0; j<7; j++) {
395                 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
396                 l >>= 8;
397             }
398         }
399         return ret;
400     }
401
402     public static class JavaSourceCode {
403
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)); }
406
407         public static InputStream decode(String s) throws IOException {
408             return new GZIPInputStream(new StringInputStream(s)); }
409         
410         private static class StringInputStream extends InputStream {
411             private final String s;
412             private final int length;
413             private int pos = 0;
414             public StringInputStream(String s) { this.s = s; this.length = s.length(); }
415             public int read() {
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();
420                 return b[0] & 0xff;
421             }
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);
427                 }
428                 return len;
429             }
430         }
431
432         public static String encode(String packageName, String className, InputStream is) throws IOException {
433
434             // compress first, since the encoded form has more entropy
435             ByteArrayOutputStream baos;
436             OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
437
438             byte[] buf = new byte[1024];
439             while(true) {
440                 int numread = is.read(buf, 0, buf.length);
441                 if (numread == -1) break;
442                 os.write(buf, 0, numread);
443             }
444             os.close();
445             buf = baos.toByteArray();
446             
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;) {
453                 ret.append("        \"");
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);
458                 }
459                 ret.append("\" +\n");
460             }
461             ret.append("    \"\";\n");
462             ret.append("}\n");
463             return ret.toString();
464         }
465
466     }
467
468 }
469