Source code: com/cybertivity/powerjournal/Base64.java
1 package com.cybertivity.powerjournal;
2
3 /**
4 * Freeware from:
5 * Roedy Green
6 * Canadian Mind Products
7 * #208 - 525 Ninth Street
8 * New Westminster, BC Canada V3M 5T9
9 * tel: (604) 777-1804
10 * mailto:roedy@mindprod.com
11 */
12
13 /**
14 * Encode arbitrary binary into printable ASCII using BASE64 encoding.
15 * very loosely based on the Base64 Reader by
16 * Dr. Mark Thornton
17 * Optrak Distribution Software Ltd.
18 * http://www.optrak.co.uk
19 * and Kevin Kelley's http://www.ruralnet.net/~kelley/java/Base64.java
20 *
21 * Base64 is a way of encoding 8-bit characters using only ASCII printable
22 * characters. The spec is described in RFC 2045. Base64 is a scheme where
23 * 3 bytes are concatenated, then split to form 4 groups of 6-bits each; and
24 * each 6-bits gets translated to an encoded printable ASCII character, via a
25 * table lookup. An encoded string is therefore longer than the original by
26 * about 1/3. The "=" character is used to pad the end. Base64 is used,
27 * among other things, to encode the user:password string in an
28 * Authorization: header for HTTP. Don't confuse Base64 with
29 * x-www-form-urlencoded which is handled by
30 * Java.net.URLEncoder.encode/decode
31 * If you wanted to encode a giant file, you could do it in large chunks that
32 * are even multiples of 3 bytes, except for the last chunk, and append the outputs.
33 *
34 * version 1.1 1999 December 04 -- more symmetrical encoding algorithm.
35 * more accurate StringBuffer allocation size.
36 * version 1.0 1999 December 03 -- posted in comp.lang.java.programmer.
37 */
38
39 public class Base64
40 {
41
42 /**
43 * Encode an arbitrary array of bytes as Base64 printable ASCII.
44 * It will be broken into lines of 72 chars each. The last line is not
45 * terminated with a line separator.
46 */
47 public static String encode(byte[] b)
48 {
49 // Each group or partial group of 3 bytes becomes four chars
50 int outputLength = ((b.length + 2) / 3) * 4;
51
52 // account for embedded newlines
53 outputLength += (outputLength / lineLength) * lineSeparator.length();
54
55 // must be local for recursion to work.
56 StringBuffer sb = new StringBuffer( outputLength );
57
58 // must be local for recursion to work.
59 int linePos = 0;
60
61 // first deal with even multiples of 3 bytes.
62 int len = (b.length / 3) * 3;
63 int leftover = b.length - len;
64 for ( int i=0; i<len; i+=3 )
65 {
66
67
68 // Start a new line if next 4 chars won't fit on the current line
69 // We don't encapsulate so that linePos and sb will work recursively
70 {
71
72 linePos += 4;
73 if ( linePos > lineLength )
74 {
75 linePos = 0;
76 if ( lineLength != 0) {sb.append(lineSeparator);}
77 }
78 }
79
80 // get next three bytes in unsigned form lined up,
81 // in big-endian order
82 int combined = b[i+0] & 0xff;
83 combined <<= 8;
84 combined |= b[i+1] & 0xff;
85 combined <<= 8;
86 combined |= b[i+2] & 0xff;
87
88 // break those 24 bits into a 4 groups of 6 bits,
89 // working LSB to MSB.
90 int c3 = combined & 0x3f;
91 combined >>>= 6;
92 int c2 = combined & 0x3f;
93 combined >>>= 6;
94 int c1 = combined & 0x3f;
95 combined >>>= 6;
96 int c0 = combined & 0x3f;
97
98 // Translate into the equivalent alpha character
99 // emitting them in big-endian order.
100 sb.append( valueToChar[c0]);
101 sb.append( valueToChar[c1]);
102 sb.append( valueToChar[c2]);
103 sb.append( valueToChar[c3]);
104 }
105
106 // deal with leftover bytes
107 switch ( leftover )
108 {
109 case 0:
110 default:
111 // nothing to do
112 break;
113
114 case 1:
115 // One leftover byte generates xx==
116 // Start a new line if next 4 chars won't fit on the current line
117 // We don't encapsulate so that linePos and sb will work recursively
118 {
119
120 linePos += 4;
121 if ( linePos > lineLength )
122 {
123 linePos = 0;
124 if ( lineLength != 0) {sb.append(lineSeparator);}
125 }
126 }
127 // Handle this recursively with a faked complete triple.
128 // Throw away last two chars and replace with ==
129 sb.append(encode(new byte[] {b[len], 0, 0}
130 ).substring(0,2));
131 sb.append("==");
132 break;
133
134 case 2:
135 // Two leftover bytes generates xxx=
136 // Start a new line if next 4 chars won't fit on the current line
137 // We don't encapsulate so that linePos and sb will work recursively
138 {
139 linePos += 4;
140 if ( linePos > lineLength )
141 {
142 linePos = 0;
143 if ( lineLength != 0) {sb.append(lineSeparator);}
144 }
145 }
146 // Handle this recursively with a faked complete triple.
147 // Throw away last char and replace with =
148 sb.append(encode(new byte[] {b[len], b[len+1], 0}
149 ).substring(0,3));
150 sb.append("=");
151 break;
152
153 } // end switch;
154
155 if ( outputLength != sb.length() ) System.out.println("oops");
156
157 return sb.toString();
158 }// end encode
159
160
161 /**
162 * how we separate lines, e.g. \n, \r\n, \r etc.
163 */
164 static String lineSeparator = System.getProperty( "line.separator" );
165
166
167 /**
168 * decode a well-formed complete Base64 string back into an array of bytes.
169 */
170 public static byte[] decode( String s)
171 {
172
173 // estimate worst case size of output array, no embedded newlines.
174 byte[] b = new byte[(s.length() / 4) * 3];
175
176 // tracks where we are in a cycle of 4 input chars.
177 int cycle = 0;
178
179 // where we combine 4 groups of 6 bits and take apart as 3 groups of 8.
180 int combined = 0;
181
182 // how many bytes we have prepared.
183 int j = 0;
184 int len = s.length();
185 for ( int i=0; i<len; i++ )
186 {
187
188 int c = s.charAt(i);
189 int value = (c <= 255) ? charToValue[c] : IGNORE;
190 // there are two magic values PAD (=) and IGNORE.
191 switch ( value )
192 {
193 case IGNORE:
194 // e.g. \n, just ignore it.
195 break;
196
197 case PAD:
198 break;
199
200 default:
201 /* regular value character */
202 switch ( cycle )
203 {
204 case 0:
205 combined = value;
206 cycle = 1;
207 break;
208
209 case 1:
210 combined <<= 6;
211 combined |= value;
212 cycle = 2;
213 break;
214
215 case 2:
216 combined <<= 6;
217 combined |= value;
218 cycle = 3;
219 break;
220
221 case 3:
222 combined <<= 6;
223 combined |= value;
224 // we have just completed a cycle of 4 chars.
225 // the four 6-bit values are in combined in big-endian order
226 // peel them off 8 bits at a time working lsb to msb
227 // to get our original 3 8-bit bytes back
228
229 b[j+2] = (byte)combined;
230 combined >>>= 8;
231 b[j+1] = (byte)combined;
232 combined >>>= 8;
233 b[j+0] = (byte)combined;
234 j += 3;
235 cycle = 0;
236 break;
237 }
238 break;
239 }
240 }
241
242 // to come.
243 return b;
244
245 }// end decode
246
247 /**
248 * max chars per line. A multiple of 4.
249 */
250 private static int lineLength = 72;
251
252
253 /**
254 * determines how long the lines are that are generated by encode.
255 * Ignored by decode.
256 * @param length 0 means no newlines inserted.
257 */
258 public static void setLineLength(int length)
259 {
260 lineLength = length;
261 }
262
263 /**
264 * letter of the alphabet used to encode binary values 0..63
265 */
266 static final char[] valueToChar = new char[64];
267
268
269 /**
270 * binary value encoded by a given letter of the alphabet 0..63
271 */
272 static final int[] charToValue = new int[256];
273
274
275
276 /**
277 * Marker value for chars we just ignore, e.g. \n \r high ascii
278 */
279 static final int IGNORE = -1;
280
281 /**
282 * Marker for = trailing pad
283 */
284 static final int PAD = -2;
285
286
287 static
288 {
289 // build translate valueToChar table only once.
290 // 0..25 -> 'A'..'Z'
291 for ( int i=0; i<=25; i++ )
292 valueToChar[i] = (char)('A'+i);
293 // 26..51 -> 'a'..'z'
294 for ( int i=0; i<=25; i++ )
295 valueToChar[i+26] = (char)('a'+i);
296 // 52..61 -> '0'..'9'
297 for ( int i=0; i<=9; i++ )
298 valueToChar[i+52] = (char)('0'+i);
299 valueToChar[62] = '+';
300 valueToChar[63] = '/';
301
302 // build translate charToValue table only once.
303 for ( int i=0; i<256; i++ )
304 {
305 charToValue[i] = IGNORE; // default is to ignore
306 }
307
308 for ( int i=0; i<64; i++ )
309 {
310 charToValue[valueToChar[i]] = i;
311 }
312
313 charToValue['='] = PAD;
314 }
315
316 /**
317 * used to disable test driver
318 */
319 private static final boolean debug = true;
320
321 /**
322 * test driver
323 */
324 public static void main(String[] args)
325 {
326 if ( debug )
327 {
328 byte[] a = { (byte)0xfc, (byte)0x0f, (byte)0xc0};
329 byte[] b = { (byte)0x03, (byte)0xf0, (byte)0x3f};
330 byte[] c = { (byte)0x00, (byte)0x00, (byte)0x00};
331 byte[] d = { (byte)0xff, (byte)0xff, (byte)0xff};
332 byte[] e = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1};
333 byte[] f = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1, (byte)2};
334 byte[] g = { (byte)0xfc, (byte)0x0f, (byte)0xc0, (byte)1, (byte)2, (byte)3};
335
336 System.out.println(Base64.encode(a));
337 System.out.println(Base64.encode(b));
338 System.out.println(Base64.encode(c));
339 System.out.println(Base64.encode(d));
340 System.out.println(Base64.encode(e));
341 System.out.println(Base64.encode(f));
342 System.out.println(Base64.encode(g));
343
344 for ( int i=0; i<64; i++ )
345 {
346 c[2] = (byte)i;
347 System.out.println(Base64.encode(c));
348 }
349 }
350
351 }// end main
352
353 } // end Base64
354