add toBase64String stub
[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) & 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
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     public static String toBase64String(byte[] data) { return new String(toBase64(data)); }
134
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) {
138         byte[]  bytes;
139                 
140         int modulus = data.length % 3;
141         if (modulus == 0) {
142             bytes = new byte[4 * data.length / 3];
143         } else {
144             bytes = new byte[4 * ((data.length / 3) + 1)];
145         }
146
147         int dataLength = (data.length - modulus);
148         int a1, a2, a3;
149         for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
150             a1 = data[i] & 0xff;
151             a2 = data[i + 1] & 0xff;
152             a3 = data[i + 2] & 0xff;
153             
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];
158         }
159
160         int b1, b2, b3;
161         int d1, d2;
162         switch (modulus) {
163                 case 0:         /* nothing left to do */
164                     break;
165                 case 1:
166                     d1 = data[data.length - 1] & 0xff;
167                     b1 = (d1 >>> 2) & 0x3f;
168                     b2 = (d1 << 4) & 0x3f;
169
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)'=';
174                     break;
175                 case 2:
176                     d1 = data[data.length - 2] & 0xff;
177                     d2 = data[data.length - 1] & 0xff;
178
179                     b1 = (d1 >>> 2) & 0x3f;
180                     b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
181                     b3 = (d2 << 2) & 0x3f;
182
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)'=';
187                     break;
188             }
189
190         return bytes;
191     }
192
193
194     private static final byte[] decB64 = new byte[128];
195     static {
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);
199         decB64['+'] = 62;
200         decB64['/'] = 63;
201     }
202
203     /** Decode base 64 encoded input data.
204      *  @return A byte array representing the decoded data. */
205     public static byte[] fromBase64(byte[] data) {
206         byte[]  bytes;
207         byte    b1, b2, b3, b4;
208
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)];
212
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]];
218             
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);
222         }
223
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));
234         } else {
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);
242         }
243         return bytes;
244     }
245
246     /** Decode a base 64 encoded String.
247      *  @return A byte array representing the decoded data. */
248     public static byte[] fromBase64(String data) {
249         byte[]  bytes;
250         byte    b1, b2, b3, b4;
251
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];
256         else
257             bytes = new byte[((data.length() / 4) * 3)];
258
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)];
264             
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);
268         }
269
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));
280         } else {
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);
288         }
289         return bytes;
290     }
291
292
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) {
300             long l = 0;
301             for(int j=6; j>=0; j--) {
302                 l <<= 8;
303                 l |= (b[i + j] & 0xff);
304             }
305             for(int j=0; j<8; j++) {
306                 ret.append((char)(l & 0x7f));
307                 l >>= 7;
308             }
309         }
310         return ret.toString();
311     }
312
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) {
320             long l = 0;
321             for(int j=7; j>=0; j--) {
322                 l <<= 7;
323                 l |= (s.charAt(i + j) & 0x7fL);
324             }
325             for(int j=0; j<7; j++) {
326                 ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
327                 l >>= 8;
328             }
329         }
330         return ret;
331     }
332
333     public static class JavaSourceCode {
334
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)); }
337
338         public static InputStream decode(String s) throws IOException {
339             return new GZIPInputStream(new StringInputStream(s)); }
340         
341         private static class StringInputStream extends InputStream {
342             private final String s;
343             private final int length;
344             private int pos = 0;
345             public StringInputStream(String s) { this.s = s; this.length = s.length(); }
346             public int read() {
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();
351                 return b[0] & 0xff;
352             }
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);
358                 }
359                 return len;
360             }
361         }
362
363         public static String encode(String packageName, String className, InputStream is) throws IOException {
364
365             // compress first, since the encoded form has more entropy
366             ByteArrayOutputStream baos;
367             OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
368
369             byte[] buf = new byte[1024];
370             while(true) {
371                 int numread = is.read(buf, 0, buf.length);
372                 if (numread == -1) break;
373                 os.write(buf, 0, numread);
374             }
375             os.close();
376             buf = baos.toByteArray();
377             
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;) {
384                 ret.append("        \"");
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);
389                 }
390                 ret.append("\" +\n");
391             }
392             ret.append("    \"\";\n");
393             ret.append("}\n");
394             return ret.toString();
395         }
396
397     }
398
399 }
400