package freenet.support; import java.nio.charset.Charset; /** * 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 { static final Charset UTF8 = Charset.forName("UTF-8"); private static char[] base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~-".toCharArray(); private static char[] base64StandardAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); /** * A reverse lookup table to convert base64 letters back into the * a 6-bit sequence. */ private static byte[] base64Reverse; private static byte[] base64StandardReverse; // Populate the base64Reverse lookup table from the base64Alphabet table. static { base64Reverse = new byte[128]; base64StandardReverse = new byte[base64Reverse.length]; // 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; base64StandardReverse[i] = (byte) 0xFF; } for (int i = 0; i < base64Alphabet.length; i++) { base64Reverse[base64Alphabet[i]] = (byte) i; base64StandardReverse[base64StandardAlphabet[i]] = (byte) i; } } /** * Encode to our shortened (non-standards-compliant) format. */ public static String encode(byte[] in) { return encode(in, false); } /* FIXME: Figure out where this function is used and maybe remove it if its not * used. Its old javadoc which has been here for a while fools the user into believing * that the format is standard compliant */ /** * Caller should specify equalsPad=true if they want a standards compliant padding, * but not standard compliant encoding. */ public static String encode(byte[] in, boolean equalsPad) { return encode(in, equalsPad, base64Alphabet); } /** * Convenience method to encode a string, in our shortened format. * * Please use this to encode a string, rather than trying to encode the string * yourself using the 0-arg String.getBytes() which is not deterministic. */ public static String encodeUTF8(String in) { return encodeUTF8(in, false); } /** * Convenience method to encode a string. * * Please use this to encode a string, rather than trying to encode the string * yourself using the 0-arg String.getBytes() which is not deterministic. */ public static String encodeUTF8(String in, boolean equalsPad) { return encode(in.getBytes(UTF8), equalsPad, base64Alphabet); } /** * Convenience method to encode a string. * * Please use this to encode a string, rather than trying to encode the string * yourself using the 0-arg String.getBytes() which is not deterministic. */ public static String encodeStandardUTF8(String in) { return encodeStandard(in.getBytes(UTF8)); } /** * Standard compliant encoding. */ public static String encodeStandard(byte[] in) { return encode(in, true, base64StandardAlphabet); } /** * Caller should specify equalsPad=true if they want a standards compliant encoding. */ private static String encode(byte[] in, boolean equalsPad, char[] alphabet) { 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++] = alphabet[(val>>18) & 0x3F]; out[o++] = alphabet[(val>>12) & 0x3F]; out[o++] = alphabet[(val>>6) & 0x3F]; out[o++] = alphabet[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 padding (padded with '=' signs) as well as our * shortened form. * @throws IllegalBase64Exception */ public static byte[] decode(String inStr) throws IllegalBase64Exception { return decode(inStr, base64Reverse); } /** * Convenience method to decode into a string, in our shortened format. * * Please use this to decode into a string, rather than trying to decode the * string yourself using new String(bytes[]) which is not deterministic. */ public static String decodeUTF8(String inStr) throws IllegalBase64Exception { return new String(decode(inStr), UTF8); } /** * Handles the standards-compliant base64 encoding. */ public static byte[] decodeStandard(String inStr) throws IllegalBase64Exception { return decode(inStr, base64StandardReverse); } /** * Handles the standards-compliant (padded with '=' signs) as well as our * shortened form. */ private static byte[] decode(String inStr, byte[] reverseAlphabet) 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 = reverseAlphabet[in[i]]; int in2 = reverseAlphabet[in[i+1]]; int in3 = reverseAlphabet[in[i+2]]; int in4 = reverseAlphabet[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 = reverseAlphabet[in[i]]; int in2 = reverseAlphabet[in[i+1]]; orValue = in1|in2; int outVal = (in1 << 18) | (in2 << 12); out[o] = (byte) (outVal>>16); } break; case 3: { int in1 = reverseAlphabet[in[i]]; int in2 = reverseAlphabet[in[i+1]]; int in3 = reverseAlphabet[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 reverseAlphabet. catch (ArrayIndexOutOfBoundsException e) { throw new IllegalBase64Exception("illegal Base64 character"); } } }