package freenet.support;
import java.util.Random;
/**
* This class provides encoding of byte arrays into Base64-encoded strings,
* and decoding the other way.
*
* <P>NOTE! This is modified Base64 with slightly different characters than
* usual, so it won't require escaping when used in URLs.
*
* <P>NOTE! This class only does the padding that's normal in Base64
* if the 'true' flag is given to the encode() method. This is because
* Base64 requires that the length of the encoded text be a multiple
* of four characters, padded with '='. Without the 'true' flag, we don't
* add these '=' characters.
*
* @author Stephen Blackheath
*/
public class Base64
{
// Unit test
public static void main(String[] args)
throws IllegalBase64Exception
{
int iter;
Random r = new Random();
for (iter = 0; iter < 1000; iter++) {
byte[] b = new byte[r.nextInt(64)];
for (int i = 0; i < b.length; i++)
b[i] = (byte) (r.nextInt(256));
String encoded = encode(b);
System.out.println(encoded);
byte[] decoded = decode(encoded);
if (decoded.length != b.length) {
System.out.println("length mismatch");
return;
}
for (int i = 0; i < b.length; i++)
if (b[i] != decoded[i]) {
System.out.println("data mismatch: index "+i+" of "+b.length+" should be 0x"+Integer.toHexString(b[i] & 0xFF)+
" was 0x"+Integer.toHexString(decoded[i] & 0xFF));
return;
}
}
System.out.println("passed "+iter+" tests");
}
private static char[] base64Alphabet = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '~', '-'};
/**
* A reverse lookup table to convert base64 letters back into the
* a 6-bit sequence.
*/
private static byte[] base64Reverse;
// Populate the base64Reverse lookup table from the base64Alphabet table.
static {
base64Reverse = new byte[128];
// Set all entries to 0xFF, which means that that particular letter
// is not a legal base64 letter.
for (int i = 0; i < base64Reverse.length; i++)
base64Reverse[i] = (byte) 0xFF;
for (int i = 0; i < base64Alphabet.length; i++)
base64Reverse[base64Alphabet[i]] = (byte) i;
}
/**
* Encode to our shortened (non-standards-compliant) format.
*/
public static String encode(byte[] in)
{
return encode(in, false);
}
/**
* Caller should specify equalsPad=true if they want a standards compliant encoding.
*/
public static String encode(byte[] in, boolean equalsPad)
{
char[] out = new char[((in.length+2)/3)*4];
int rem = in.length%3;
int o = 0;
for (int i = 0; i < in.length;) {
int val = (in[i++] & 0xFF) << 16;
if (i < in.length)
val |= (in[i++] & 0xFF) << 8;
if (i < in.length)
val |= (in[i++] & 0xFF);
out[o++] = base64Alphabet[(val>>18) & 0x3F];
out[o++] = base64Alphabet[(val>>12) & 0x3F];
out[o++] = base64Alphabet[(val>>6) & 0x3F];
out[o++] = base64Alphabet[val & 0x3F];
}
int outLen = out.length;
switch (rem) {
case 1: outLen -= 2; break;
case 2: outLen -= 1; break;
}
// Pad with '=' signs up to a multiple of four if requested.
if (equalsPad)
while (outLen < out.length)
out[outLen++] = '=';
return new String(out, 0, outLen);
}
/**
* Handles the standards-compliant (padded with '=' signs) as well as our
* shortened form.
*/
public static byte[] decode(String inStr)
throws IllegalBase64Exception
{
try {
char[] in = inStr.toCharArray();
int inLength = in.length;
// Strip trailing equals signs.
while ((inLength > 0) && (in[inLength-1] == '='))
inLength--;
int blocks = inLength/4;
int remainder = inLength & 3;
// wholeInLen and wholeOutLen are the the length of the input and output
// sequences respectively, not including any partial block at the end.
int wholeInLen = blocks*4;
int wholeOutLen = blocks*3;
int outLen = wholeOutLen;
switch (remainder) {
case 1: throw new IllegalBase64Exception("illegal Base64 length");
case 2: outLen = wholeOutLen+1; break;
case 3: outLen = wholeOutLen+2; break;
default: outLen = wholeOutLen;
}
byte[] out = new byte[outLen];
int o = 0;
int i;
for (i = 0; i < wholeInLen;) {
int in1 = base64Reverse[in[i]];
int in2 = base64Reverse[in[i+1]];
int in3 = base64Reverse[in[i+2]];
int in4 = base64Reverse[in[i+3]];
int orValue = in1|in2|in3|in4;
if ((orValue & 0x80) != 0)
throw new IllegalBase64Exception("illegal Base64 character");
int outVal = (in1 << 18) | (in2 << 12) | (in3 << 6) | in4;
out[o] = (byte) (outVal>>16);
out[o+1] = (byte) (outVal>>8);
out[o+2] = (byte) outVal;
i += 4;
o += 3;
}
int orValue;
switch (remainder) {
case 2:
{
int in1 = base64Reverse[in[i]];
int in2 = base64Reverse[in[i+1]];
orValue = in1|in2;
int outVal = (in1 << 18) | (in2 << 12);
out[o] = (byte) (outVal>>16);
}
break;
case 3:
{
int in1 = base64Reverse[in[i]];
int in2 = base64Reverse[in[i+1]];
int in3 = base64Reverse[in[i+2]];
orValue = in1|in2|in3;
int outVal = (in1 << 18) | (in2 << 12) | (in3 << 6);
out[o] = (byte) (outVal>>16);
out[o+1] = (byte) (outVal>>8);
}
break;
default:
// Keep compiler happy
orValue = 0;
}
if ((orValue & 0x80) != 0)
throw new IllegalBase64Exception("illegal Base64 character");
return out;
}
// Illegal characters can cause an ArrayIndexOutOfBoundsException when
// looking up base64Reverse.
catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalBase64Exception("illegal Base64 character");
}
}
}