KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > commons > cbutil > CBBase64


1 package com.ca.commons.cbutil;
2
3 import java.io.UnsupportedEncodingException JavaDoc;
4
5 /**
6  * Converts back and forth between binary and base 64 (rfc 1521). There are
7  * almost certainly java classes that already do this, but it will
8  * take longer to find them than to write this :-) <p>
9  * <p/>
10  * Not fully optimised for speed - might be made faster if necessary... <p>
11  * <p/>
12  * Maybe should rewrite sometime as a stream?
13  *
14  * @author Chris Betts
15  */

16
17 public class CBBase64
18 {
19
20
21 /* if speeding up by using array, maybe mess around with this array...
22
23     static int binToChar[] =
24     {
25         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
26         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
27         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
28         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
29         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
30         0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
31         0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
32         0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
33         0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
34         0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
35         0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
36         0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
37         0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
38         0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
39         0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
40         0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
41     };
42 */

43
44     /**
45      * Purely static class; hence no one should be able to instantiate it...
46      */

47
48     private CBBase64()
49     {
50     }
51
52     /**
53      * Takes a binary byte array and converts it to base64 mime
54      * encoded data.
55      *
56      * @param byteArray an array of 8 bit bytes to be converted
57      * @return the resultant encoded string
58      */

59     public static String JavaDoc binaryToString(byte[] byteArray)
60     {
61         return binaryToString(byteArray, 0);
62     }
63
64     /**
65      * Takes a binary byte array and converts it to base64 mime
66      * encoded data.
67      *
68      * @param byteArray an array of 8 bit bytes to be converted
69      * @param offset The first line of the string may be offset by
70      * by this many characters for nice formatting (e.g. in
71      * an ldif file, the first line may include 'att value = ...'
72      * at the beginning of the base 64 encoded block).
73      * @return the resultant encoded string
74      */

75
76     public static String JavaDoc binaryToString(byte[] byteArray, int offset)
77     {
78         if (byteArray == null) return null; // XXX correct behaviour?
79

80         int arraySize = byteArray.length;
81         int thirdSize = arraySize / 3;
82
83         byte[] base64Data = encode(byteArray);
84
85         if (base64Data == null) return null; // Exception occurred.
86

87         return format(base64Data, offset);
88
89     }
90
91     /**
92      * This returns a formatted string representing the base64 data.
93      *
94      * @param base64Data a byte array of base64 encoded data.
95      * @param offset the string is formatted to fit inside column 76.
96      * The offset number provides for the first line to be
97      * foreshortened (basically for pretty formatting in ldif
98      * files).
99      */

100
101 // XXX this is unusually sucky, even for me. Better would be too format on the fly...
102

103     public static String JavaDoc format(byte[] base64Data, int offset)
104     {
105
106         String JavaDoc data;
107         try
108         {
109             data = new String JavaDoc(base64Data, "US-ASCII");
110         }
111         catch (Exception JavaDoc e)
112         {
113             data = new String JavaDoc(base64Data); // should be equivalent to above
114
}
115         
116         // some magic to make the columns line up nicely, and everythin,
117
// including leading space, to fit under column 76...
118

119         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(data);
120
121         int i = 76 - offset;
122         while (i < base64Data.length)
123         {
124             buffer.insert(i, "\r\n ");
125             i += 78;
126         }
127
128         return buffer.toString();
129     }
130
131     /**
132      * Encodes an arbitrary byte array into an array of base64 encoded bytes
133      * (i.e. bytes that have values corresponding to the ascii values of base 64
134      * data, and can be type cast to those chars).
135      *
136      * @param byteArray the raw data to encode
137      * @return the base64 encoded bytes (the length of this is ~ 4/3 the length of
138      * the raw data).
139      */

140
141     public static byte[] encode(byte[] byteArray)
142     {
143         try
144         {
145             int arraySize = byteArray.length;
146
147             int outputSize = (arraySize % 3 == 0) ? (arraySize / 3) * 4 : ((arraySize / 3) + 1) * 4;
148
149             byte[] output = new byte[outputSize];
150             
151             // iterate through array, reading off byte triplets and converting to base64
152

153             int bufferLength = 0;
154
155             for (int i = 0; i <= (arraySize - 3); i += 3)
156             {
157                 convertTriplet(byteArray[i], byteArray[i + 1], byteArray[i + 2], 3, output, bufferLength);
158                 bufferLength += 4;
159             }
160
161             switch (arraySize % 3)
162             {
163                 case 0:
164                     break;
165
166                 case 1:
167                     convertTriplet(byteArray[arraySize - 1], (byte) 0, (byte) 0, 1, output, bufferLength);
168                     break;
169
170                 case 2:
171                     convertTriplet(byteArray[arraySize - 2], byteArray[arraySize - 1], (byte) 0, 2, output, bufferLength);
172                     break;
173             }
174
175             return output;
176         }
177         catch (CBBase64EncodingException e)
178         {
179             return null;
180         }
181     }
182
183     /**
184      * Encode an arbitrary byte array into an array of base64 encoded bytes.
185      * That is, bytes that have values corresponding to the ascii values of base 64
186      * data, and can be type cast to those chars.
187      *
188      * @param byteArray the raw data to encode
189      * @param start the number of padding spaces to have at the start (must be less than colSize)
190      * @param colSize the length of each line of text (i.e. the right hand margin). Must
191      * be a multiple of 4.
192      * @return the base64 encoded bytes (the length of this is ~ 4/3 the length of
193      * the raw data).
194      */

195
196     public static byte[] encodeFormatted(byte[] byteArray, int start, int colSize)
197             throws CBBase64EncodingException
198     {
199         try
200         {
201             if (colSize % 4 != 0)
202             {
203                 throw new CBBase64EncodingException("error in encodeFormatted - colSize not a multiple of 4.");
204             }
205
206             if (start >= colSize)
207             {
208                 throw new CBBase64EncodingException("error in encodeFormatted - start is not less than colSize.");
209             }
210
211             int arraySize = byteArray.length;
212
213             int outputSize = start + ((arraySize % 3 == 0) ? (arraySize / 3) * 4 : ((arraySize / 3) + 1) * 4);
214
215             outputSize += (outputSize / colSize) + 1; // allow for new lines!
216

217             byte[] output = new byte[outputSize];
218             
219             // iterate through array, reading off byte triplets and converting to base64
220

221             for (int i = 0; i < start; i++)
222                 output[i] = (byte) ' '; // pad to 'start' with spaces.
223

224             int bufferLength = start;
225
226             for (int i = 0; i <= (arraySize - 3); i += 3)
227             {
228                 convertTriplet(byteArray[i], byteArray[i + 1], byteArray[i + 2], 3, output, bufferLength);
229                 bufferLength += 4;
230
231                 if (bufferLength % (colSize + 1) == colSize) // check if we're at the end of a column
232
{
233                     output[bufferLength++] = (byte) '\n';
234                 }
235             }
236
237             switch (arraySize % 3)
238             {
239                 case 0:
240                     break;
241
242                 case 1:
243                     convertTriplet(byteArray[arraySize - 1], (byte) 0, (byte) 0, 1, output, bufferLength);
244                     bufferLength += 4;
245                     break;
246
247                 case 2:
248                     convertTriplet(byteArray[arraySize - 2], byteArray[arraySize - 1], (byte) 0, 2, output, bufferLength);
249                     bufferLength += 4;
250                     break;
251             }
252         
253             // final '\n'
254
if (bufferLength < outputSize) // it should be exactly one less!
255
{
256                 output[bufferLength++] = (byte) '\n';
257             }
258             else
259             {
260                 System.err.println("wierdness in formatted base 64 : bufferlength (" + bufferLength + ") != 1 + outputsize (" + outputSize + ")");
261             }
262
263             return output;
264         }
265         catch (CBBase64EncodingException e)
266         {
267             // XXX shouldn't we do something here?
268
return null;
269         }
270         catch (Exception JavaDoc e2)
271         {
272             System.err.println("unexpected error in base 64 encoding");
273             e2.printStackTrace();
274             return null;
275         }
276     }
277
278
279     /**
280      * Takes a base 64 encoded string and converts it to a
281      * binary byte array.
282      *
283      * @param chars a string of base64 encoded characters to be converted
284      * @return the resultant binary array
285      */

286
287     public static byte[] stringToBinary(String JavaDoc chars)
288     {
289         if (chars == null) return null;
290
291         byte charArray[];
292
293         try
294         {
295             charArray = chars.getBytes("US-ASCII");
296         }
297         catch (UnsupportedEncodingException JavaDoc e)
298         {
299             charArray = chars.getBytes();
300         }
301
302         return decode(charArray);
303     }
304
305
306     /**
307      * Decodes a byte array containing base64 encoded data.
308      *
309      * @param rawData a byte array, each byte of which is a seven-bit ASCII value.
310      * @return the raw binary data, each byte of which may have any value from
311      * 0 - 255. This value will be null if a decoding error occurred.
312      */

313
314     public static byte[] decode(byte[] rawData)
315     {
316         try
317         {
318
319             int resultLength = (int) (rawData.length * .75); // set upper limit for binary array
320

321             byte result[] = new byte[resultLength];
322
323             int noBytesWritten = 0;
324             int validCharacters = 0;
325
326             byte c;
327             byte quad[] = new byte[4]; // temp store for a quad of base64 byte characters
328
int bytes; // temp store for a triplet of bytes
329
int numfound = 0; // number of chars found for quad
330

331              
332             // iterate through, finding valid quads, and converting to byte triplets
333

334             for (int i = 0; i < rawData.length; i++)
335             {
336                 c = rawData[i];
337                 if ((c >= (byte) 'A' && c <= (byte) 'Z') || (c >= (byte) 'a' && c <= (byte) 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == (byte) '/') || (c == (byte) '='))
338                 {
339                     quad[numfound++] = c;
340                     validCharacters++;
341                 }
342                 else if (" \r\n\t\f".indexOf((char) c) == -1)
343                 {
344                     //CBUtility.log("error... bad character (" + (char)c + ") read from base64 encoded string", 6);
345
return null;
346                 }
347
348                 // write the quad; paying special attention to possible 'filler'
349
// characters '=' or '==' at the end of the string (see rfc).
350

351                 if (numfound == 4)
352                 {
353                     bytes = convertQuad(quad);
354                     result[noBytesWritten++] = (byte) ((bytes & 0xFF0000) >> 16);
355                     if (c != '=')
356                     {
357                         result[noBytesWritten++] = (byte) ((bytes & 0xFF00) >> 8);
358                         result[noBytesWritten++] = (byte) (bytes & 0xFF);
359                     }
360                     else if (rawData[i - 1] != '=')
361                     {
362                         result[noBytesWritten++] = (byte) ((bytes & 0xFF00) >> 8);
363                         if ((bytes & 0xFF) > 0)
364                         {
365                             //CBUtility.log("Warning: Corrupt base64 Encoded File - contains trailing bits after file end.", 6);
366
return null;
367                         }
368                     }
369                     else if ((bytes & 0xFF00) > 0)
370                     {
371                         //CBUtility.log("Warning: Corrupt base64 Encoded File - contains trailing bits after file end.", 6);
372
return null;
373                     }
374
375                     numfound = 0;
376                 }
377             }
378              
379             // check that the number of real characters is correct - must be cleanly
380
// divisible by 4...
381

382             if (validCharacters % 4 != 0)
383             {
384                 //CBUtility.log("Warning: Corrupt base64 Encoded File - Length (" + validCharacters + ") of valid characters not divisible by 4.", 6);
385
return null;
386             }
387
388
389             byte finalResult[] = new byte[noBytesWritten];
390             System.arraycopy(result, 0, finalResult, 0, noBytesWritten);
391             return finalResult;
392         }
393         catch (Exception JavaDoc e)
394         {
395             //CBUtility.log("unable to create final decoded byte array from base64 bytes: " + e, 6);
396
return null;
397         }
398
399     }
400
401     /**
402      * Decodes a byte array containing base64 encoded data.
403      *
404      * @param chars a String, each character of which is a seven-bit ASCII value.
405      * @return the raw binary data, each byte of which may have any value from
406      * 0 - 255. This value will be null if a decoding error occurred.
407      */

408
409     public static byte[] decode(String JavaDoc chars)
410             throws CBBase64EncodingException
411     {
412
413         if (chars == null) return null;
414
415         byte rawData[];
416
417         try
418         {
419             rawData = chars.getBytes("US-ASCII");
420         }
421         catch (UnsupportedEncodingException JavaDoc e)
422         {
423             throw new CBBase64EncodingException("unable to convert base64 encoded data to bytes using US-ASCII encoding", e);
424         }
425
426         int resultLength = (int) (rawData.length * .75); // set upper limit for binary array
427

428         byte result[] = new byte[resultLength];
429
430         int noBytesWritten = 0;
431         int validCharacters = 0;
432
433         byte c;
434         byte quad[] = new byte[4]; // temp store for a quad of base64 byte characters
435
int bytes; // temp store for a triplet of bytes
436
int numfound = 0; // number of chars found for quad
437

438
439         // iterate through, finding valid quads, and converting to byte triplets
440

441         for (int i = 0; i < rawData.length; i++)
442         {
443             c = rawData[i];
444             if ((c >= (byte) 'A' && c <= (byte) 'Z') || (c >= (byte) 'a' && c <= (byte) 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == (byte) '/') || (c == (byte) '='))
445             {
446                 quad[numfound++] = c;
447                 validCharacters++;
448             }
449             else if (" \r\n\t\f".indexOf((char) c) == -1)
450             {
451                 throw new CBBase64EncodingException("error... bad character (" + (char) c + ") read from base64 encoded string");
452             }
453
454             // write the quad; paying special attention to possible 'filler'
455
// characters '=' or '==' at the end of the string (see rfc).
456

457             if (numfound == 4)
458             {
459                 bytes = convertQuad(quad);
460                 result[noBytesWritten++] = (byte) ((bytes & 0xFF0000) >> 16);
461                 if (c != '=')
462                 {
463                     result[noBytesWritten++] = (byte) ((bytes & 0xFF00) >> 8);
464                     result[noBytesWritten++] = (byte) (bytes & 0xFF);
465                 }
466                 else if (rawData[i - 1] != '=')
467                 {
468                     result[noBytesWritten++] = (byte) ((bytes & 0xFF00) >> 8);
469                     if ((bytes & 0xFF) > 0)
470                     {
471                         throw new CBBase64EncodingException("Warning: Corrupt base64 Encoded Data - contains trailing bits after end of base 64 data.");
472                     }
473                 }
474                 else if ((bytes & 0xFF00) > 0)
475                 {
476                     throw new CBBase64EncodingException("Warning: Corrupt base64 Encoded File - contains trailing bits after end of base64 data.");
477                 }
478
479                 numfound = 0;
480             }
481         }
482
483         // check that the number of real characters is correct - must be cleanly
484
// divisible by 4...
485

486         if (validCharacters % 4 != 0)
487         {
488             throw new CBBase64EncodingException("Warning: Corrupt base64 Encoded Data - Length (" + validCharacters + ") of valid characters not divisible by 4.");
489         }
490
491
492         byte finalResult[] = new byte[noBytesWritten];
493         System.arraycopy(result, 0, finalResult, 0, noBytesWritten);
494         return finalResult;
495
496     }
497
498
499     /**
500      * Converts three bytes to 4 base 64 values...
501      * a half hearted attempt has been made to make it go fast...
502      *
503      * @param a the first byte to convert
504      * @param b the second byte to convert
505      * @param c the third byte to convert
506      * @param Num the Number of 'real' bytes to convert - i.e. 1 (just a),
507      * 2 (a and b), or 3 (a,b and c).
508      * @param buff the result buffer to put the final values in.
509      * @param buffpos the position to start filling the result buffer from.
510      */

511
512     private static void convertTriplet(byte a, byte b, byte c, int Num, byte[] buff, int buffpos)
513             throws CBBase64EncodingException
514     {
515         byte w, x, y, z; // the four 6 bit values extracted.
516
int trip = (a << 16) | ((b << 8) & 0xFF00) | (c & 0xFF);
517
518         w = (byte) ((trip & 0xFC0000) >> 18);
519         x = (byte) ((trip & 0x03F000) >> 12);
520         y = (byte) ((trip & 0x000FC0) >> 6);
521         z = (byte) (trip & 0x00003F);
522
523         buff[buffpos] = convertFrom6Bit(w);
524         buff[buffpos + 1] = convertFrom6Bit(x);
525
526         if (Num == 1)
527         {
528             buff[buffpos + 2] = (byte) '=';
529             buff[buffpos + 3] = (byte) '=';
530         }
531         else
532         {
533             buff[buffpos + 2] = convertFrom6Bit(y);
534
535             if (Num == 2)
536             {
537                 buff[buffpos + 3] = (byte) '=';
538             }
539             else
540             {
541                 buff[buffpos + 3] = convertFrom6Bit(z);
542             }
543         }
544     }
545
546     /**
547      * Use rfc 1521 specified character conversions
548      * (I wonder if a preset array would be faster?)
549      *
550      * @param b the 6 significant bit byte to be converted
551      * @return the converted base64 character
552      */

553
554     // this might be sped up using an array index as provided above...
555

556     private static byte convertFrom6Bit(byte b)
557             throws CBBase64EncodingException
558     {
559         byte c;
560
561         if (b < 26)
562             return (byte) ('A' + b); // 'A' -> 'Z'
563
else if (b < 52)
564             return (byte) (('a' - 26) + b); // 'a' -> 'z'
565
else if (b < 62)
566             return (byte) (('0' - 52) + b); // '0' -> '9'
567
else if (b == 62)
568             return ((byte) '+');
569         else if (b == 63)
570             return ((byte) '/');
571         else // error - should never happen
572
{
573             throw new CBBase64EncodingException("erroroneous value " + (char) b + " passed in convertFrom6bit");
574         }
575     }
576
577     /**
578      * Use rfc 1521 specified character conversions
579      * (I wonder if a preset array would be faster?)
580      *
581      * @param c a byte representing a single base64 encoded character
582      * @return the corresponding raw 6 bit 'true' value.
583      */

584
585     private static byte convertTo6Bit(byte c)
586             throws CBBase64EncodingException
587     {
588         if (c == (byte) '+')
589             return 62;
590         else if (c == (byte) '/')
591             return 63;
592         else if (c == (byte) '=') // this result not actually used by calling program...
593
return 0;
594         else if (c <= (byte) '9')
595             return (byte) (c - (byte) '0' + 52);
596         else if (c <= (byte) 'Z')
597             return (byte) (c - (byte) 'A');
598         else if (c <= (byte) 'z')
599             return (byte) (c - (byte) 'a' + 26);
600         else // error - should never happen
601
{
602             throw new CBBase64EncodingException("erroroneous value " + (char) c + " passed in convertTo6bit");
603         }
604     }
605
606     /**
607      * Takes a triplet of base64 encoded characters, and returns
608      * a triplet of appropriate bytes
609      *
610      * @param quad four base 64 encoded characters
611      * @return the corresponding 'true' 24 bit value, as an int.
612      */

613
614     private static int convertQuad(byte[] quad)
615             throws CBBase64EncodingException
616     {
617         byte a = convertTo6Bit(quad[0]);
618         byte b = convertTo6Bit(quad[1]);
619         byte c = convertTo6Bit(quad[2]);
620         byte d = convertTo6Bit(quad[3]);
621
622         int ret = (a << 18) + (b << 12) + (c << 6) + d;
623
624         return ret;
625     }
626 }
Popular Tags