package com.idega.core.ldap.client.cbutil;
import java.io.UnsupportedEncodingException;
/**
* Converts back and forth between binary and base 64 (rfc 1521). There are
* almost certainly java classes that already do this, but it will
* take longer to find them than to write this :-) <p>
*
* Not fully optimised for speed - might be made faster if necessary... <p>
*
* Maybe should rewrite sometime as a stream?
* @author Chris Betts
*/
public class CBBase64
{
protected static class Base64EncodingException extends Exception
{
public Base64EncodingException(String msg) { super(msg);}
}
/* if speeding up by using array, maybe mess around with this array...
static int binToChar[] =
{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
};
*/
/**
* Purely static class; hence no one should be able to instantiate it...
*/
private CBBase64 () {}
/**
* Takes a binary byte array and converts it to base64 mime
* encoded data.
* @param byteArray an array of 8 bit bytes to be converted
* @return the resultant encoded string
*/
public static String binaryToString(byte[] byteArray)
{
return binaryToString(byteArray, 0);
}
/**
* Takes a binary byte array and converts it to base64 mime
* encoded data.
* @param byteArray an array of 8 bit bytes to be converted
* @param offset The first line of the string may be offset by
* by this many characters for nice formatting (e.g. in
* an ldif file, the first line may include 'att value = ...'
* at the beginning of the base 64 encoded block).
* @return the resultant encoded string
*
*/
public static String binaryToString(byte[] byteArray, int offset)
{
if (byteArray == null) {
return null; // XXX correct behaviour?
}
//int arraySize = byteArray.length;
//int thirdSize = arraySize/3;
byte[] base64Data = encode(byteArray);
if (base64Data == null) {
return null; // Exception occurred.
}
return format(base64Data, offset);
}
/**
* This returns a formatted string representing the base64 data.
* @param base64Data a byte array of base64 encoded data.
* @param offset the string is formatted to fit inside column 76.
* The offset number provides for the first line to be
* foreshortened (basically for pretty formatting in ldif
* files).
*/
// XXX this is unusually sucky, even for me. Better would be too format on the fly...
public static String format(byte[] base64Data, int offset)
{
String data;
try
{
data = new String(base64Data, "US-ASCII");
}
catch (Exception e)
{
data = new String(base64Data); // should be equivalent to above
}
// some magic to make the columns line up nicely, and everythin,
// including leading space, to fit under column 76...
StringBuffer buffer = new StringBuffer(data);
int i = 76 - offset;
while (i < base64Data.length)
{
buffer.insert(i, "\r\n ");
i += 78;
}
return buffer.toString();
}
/**
* Encodes an arbitrary byte array into an array of base64 encoded bytes
* (i.e. bytes that have values corresponding to the ascii values of base 64
* data, and can be type cast to those chars).
* @param byteArray the raw data to encode
* @return the base64 encoded bytes (the length of this is ~ 4/3 the length of
* the raw data).
*/
public static byte[] encode(byte[] byteArray)
{
try
{
int arraySize = byteArray.length;
int outputSize = (arraySize%3 == 0)? (arraySize/3)*4 : ((arraySize/3)+1)*4;
byte[] output = new byte[outputSize];
// iterate through array, reading off byte triplets and converting to base64
int bufferLength = 0;
for (int i=0; i<=(arraySize-3); i+=3)
{
convertTriplet(byteArray[i], byteArray[i+1], byteArray[i+2], 3, output, bufferLength);
bufferLength += 4;
}
switch (arraySize%3)
{
case 0: break;
case 1: convertTriplet(byteArray[arraySize-1], (byte)0, (byte)0, 1, output, bufferLength);
break;
case 2: convertTriplet(byteArray[arraySize-2], byteArray[arraySize-1],(byte)0,2, output, bufferLength);
break;
}
return output;
}
catch (Base64EncodingException e)
{
return null;
}
}
/**
* Encode an arbitrary byte array into an array of base64 encoded bytes.
* That is, bytes that have values corresponding to the ascii values of base 64
* data, and can be type cast to those chars.
*
* @param byteArray the raw data to encode
* @param start the number of padding spaces to have at the start (must be less than colSize)
* @param colSize the length of each line of text (i.e. the right hand margin). Must
* be a multiple of 4.
* @return the base64 encoded bytes (the length of this is ~ 4/3 the length of
* the raw data).
*/
public static byte[] encodeFormatted(byte[] byteArray, int start, int colSize)
throws Base64EncodingException
{
try
{
if (colSize%4 != 0)
{
throw new Base64EncodingException("error in encodeFormatted - colSize not a multiple of 4.");
}
if (start >= colSize)
{
throw new Base64EncodingException("error in encodeFormatted - start is not less than colSize.");
}
int arraySize = byteArray.length;
int outputSize = start + ((arraySize%3 == 0)? (arraySize/3)*4 : ((arraySize/3)+1)*4);
outputSize += (outputSize/colSize) + 1; // allow for new lines!
byte[] output = new byte[outputSize];
// iterate through array, reading off byte triplets and converting to base64
for (int i=0; i<start; i++) {
output[i] = (byte) ' '; // pad to 'start' with spaces.
}
int bufferLength = start;
for (int i=0; i<=(arraySize-3); i+=3)
{
convertTriplet(byteArray[i], byteArray[i+1], byteArray[i+2], 3, output, bufferLength);
bufferLength += 4;
if (bufferLength%(colSize+1) == colSize) // check if we're at the end of a column
{
output[bufferLength++] = (byte) '\n';
}
}
switch (arraySize%3)
{
case 0: break;
case 1: convertTriplet(byteArray[arraySize-1], (byte)0, (byte)0, 1, output, bufferLength);
bufferLength += 4;
break;
case 2: convertTriplet(byteArray[arraySize-2], byteArray[arraySize-1],(byte)0,2, output, bufferLength);
bufferLength += 4;
break;
}
// final '\n'
if (bufferLength < outputSize) // it should be exactly one less!
{
output[bufferLength++] = (byte) '\n';
}
else
{
System.err.println("wierdness in formatted base 64 : bufferlength (" + bufferLength + ") != 1 + outputsize (" + outputSize + ")");
}
return output;
}
catch (Base64EncodingException e)
{
return null;
}
catch (Exception e2)
{
System.err.println("unexpected error in base 64 encoding");
e2.printStackTrace();
return null;
}
}
/**
* Takes a base 64 encoded string and converts it to a
* binary byte array.
* @param chars a string of base64 encoded characters to be converted
* @return the resultant binary array
*/
public static byte[] stringToBinary(String chars)
{
if (chars == null) {
return null;
}
byte charArray[];
try
{
charArray = chars.getBytes("US-ASCII");
}
catch (UnsupportedEncodingException e)
{
charArray = chars.getBytes();
}
return decode(charArray);
}
/**
* Decodes a byte array containing base64 encoded data.
* @param rawData a byte array, each byte of which is a seven-bit ASCII value.
* @return the raw binary data, each byte of which may have any value from
* 0 - 255. This value will be null if a decoding error occurred.
*/
public static byte[] decode(byte[] rawData)
{
try
{
int resultLength = (int)(rawData.length*.75); // set upper limit for binary array
byte result[] = new byte[resultLength];
int noBytesWritten = 0;
int validCharacters = 0;
byte c;
byte quad[] = new byte[4]; // temp store for a quad of base64 byte characters
int bytes; // temp store for a triplet of bytes
int numfound = 0; // number of chars found for quad
// iterate through, finding valid quads, and converting to byte triplets
for (int i=0; i<rawData.length; i++)
{
c = rawData[i];
{
if ((c >= (byte)'A' && c <= (byte)'Z') || (c>=(byte)'a' && c<=(byte)'z') || (c>='0' && c<='9') || ( c == '+') || (c == (byte)'/') || (c== (byte)'='))
{
quad[numfound++] = c;
validCharacters++;
}
else if (" \r\n\t\f".indexOf((char)c)==-1)
{
CBUtility.log("error... bad character (" + (char)c + ") read from base64 encoded string", 6);
return null;
}
}
// write the quad; paying special attention to possible 'filler'
// characters '=' or '==' at the end of the string (see rfc).
if (numfound == 4)
{
bytes = convertQuad(quad);
result[noBytesWritten++] = (byte) ((bytes & 0xFF0000)>>16);
if (c != '=')
{
result[noBytesWritten++] = (byte) ((bytes & 0xFF00)>>8);
result[noBytesWritten++] = (byte) (bytes & 0xFF);
}
else if (rawData[i-1] != '=')
{
result[noBytesWritten++] = (byte) ((bytes & 0xFF00)>>8);
if ((bytes & 0xFF)>0)
{
CBUtility.log("Warning: Corrupt base64 Encoded File - contains trailing bits after file end.", 6);
return null;
}
}
else if ((bytes & 0xFF00)>0)
{
CBUtility.log("Warning: Corrupt base64 Encoded File - contains trailing bits after file end.", 6);
return null;
}
numfound = 0;
}
}
// check that the number of real characters is correct - must be cleanly
// divisible by 4...
if (validCharacters%4!=0)
{
CBUtility.log("Warning: Corrupt base64 Encoded File - Length (" + validCharacters + ") of valid characters not divisible by 4.", 6);
return null;
}
byte finalResult[] = new byte[noBytesWritten];
System.arraycopy(result, 0, finalResult, 0, noBytesWritten);
return finalResult;
}
catch (Exception e)
{
CBUtility.log("unable to create final decoded byte array from base64 bytes: " + e, 6);
return null;
}
}
/**
* Converts three bytes to 4 base 64 values...
* a half hearted attempt has been made to make it go fast...
* @param a the first byte to convert
* @param b the second byte to convert
* @param c the third byte to convert
* @param Num the Number of 'real' bytes to convert - i.e. 1 (just a),
* 2 (a and b), or 3 (a,b and c).
* @param buffer the result buffer to put the final values in.
* @param bufpos the position to start filling the result buffer from.
* @return the current end position of data in the result buffer...
*/
private static void convertTriplet(byte a, byte b, byte c, int Num, byte[] buff, int buffpos)
throws Base64EncodingException
{
byte w,x,y,z; // the four 6 bit values extracted.
int trip = (a<<16) | ((b<<8) & 0xFF00) | ( c & 0xFF);
w = (byte) (( trip & 0xFC0000 ) >> 18);
x = (byte) (( trip & 0x03F000 ) >> 12);
y = (byte) (( trip & 0x000FC0 ) >> 6);
z = (byte) ( trip & 0x00003F );
buff[buffpos] = convertFrom6Bit(w);
buff[buffpos+1] = convertFrom6Bit(x);
if (Num == 1)
{
buff[buffpos+2] = (byte) '=';
buff[buffpos+3] = (byte) '=';
}
else
{
buff[buffpos+2] = convertFrom6Bit(y);
if (Num == 2)
{
buff[buffpos+3] = (byte)'=';
}
else
{
buff[buffpos+3] = convertFrom6Bit(z);
}
}
}
/**
* Use rfc 1521 specified character conversions
* (I wonder if a preset array would be faster?)
* @param b the 6 significant bit byte to be converted
* @return the converted base64 character
*/
// this might be sped up using an array index as provided above...
private static byte convertFrom6Bit(byte b)
throws Base64EncodingException
{
//byte c;
if (b < 26) {
return (byte)('A' + b); // 'A' -> 'Z'
}
else if (b < 52) {
return (byte)(('a' - 26 ) + b ); // 'a' -> 'z'
}
else if (b < 62) {
return (byte)(('0' - 52 ) + b ); // '0' -> '9'
}
else if (b == 62) {
return ((byte)'+');
}
else if (b == 63) {
return ((byte)'/');
}
else // error - should never happen
{
throw new Base64EncodingException ("erroroneous value " + (char)b + " passed in convertFrom6bit");
}
}
/**
* Use rfc 1521 specified character conversions
* (I wonder if a preset array would be faster?)
* @param c a byte representing a single base64 encoded character
* @return the corresponding raw 6 bit 'true' value.
*/
private static byte convertTo6Bit(byte c)
throws Base64EncodingException
{
if (c == (byte)'+') {
return 62;
}
else if (c == (byte)'/') {
return 63;
}
else if (c == (byte)'=') {
return 0;
}
else if (c <= (byte)'9') {
return (byte)( c - (byte)'0' + 52 );
}
else if (c <= (byte)'Z') {
return (byte)( c - (byte)'A');
}
else if (c <= (byte)'z') {
return (byte)( c - (byte)'a' + 26);
}
else // error - should never happen
{
throw new Base64EncodingException("erroroneous value " + (char)c + " passed in convertTo6bit");
}
}
/**
* Takes a triplet of base64 encoded characters, and returns
* a triplet of appropriate bytes
*
* @param quad four base 64 encoded characters
* @return the corresponding 'true' 24 bit value, as an int.
*/
private static int convertQuad(byte[] quad)
throws Base64EncodingException
{
byte a = convertTo6Bit(quad[0]);
byte b = convertTo6Bit(quad[1]);
byte c = convertTo6Bit(quad[2]);
byte d = convertTo6Bit(quad[3]);
int ret = (a << 18) + (b << 12) + (c << 6) + d;
return ret;
}
}