package com.hwlcn.ldap.util;
import com.hwlcn.core.annotation.ThreadSafety;
import java.io.IOException;
import java.text.ParseException;
import static com.hwlcn.ldap.util.UtilityMessages.*;
import static com.hwlcn.ldap.util.Validator.*;
/**
* This class provides methods for encoding and decoding data in base64 as
* defined in <A HREF="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</A>. It
* provides a relatively compact way of representing binary data using only
* printable characters. It uses a six-bit encoding mechanism in which every
* three bytes of raw data is converted to four bytes of base64-encoded data,
* which means that it only requires about a 33% increase in size (as compared
* with a hexadecimal representation, which requires a 100% increase in size).
* <BR><BR>
* Base64 encoding is used in LDIF processing as per
* <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A> to represent data
* that contains special characters or might otherwise be ambiguous. It is also
* used in a number of other areas (e.g., for the ASCII representation of
* certificates) where it is desirable to deal with a string containing only
* printable characters but the raw data may contain other characters outside of
* that range.
* <BR><BR>
* This class also provides support for the URL-safe variant (called base64url)
* as described in RFC 4648 section 5. This is nearly the same as base64,
* except that the '+' and '/' characters are replaced with '-' and '_',
* respectively. The padding may be omitted if the context makes the data size
* clear, but if padding is to be used then the URL-encoded "%3d" will be used
* instead of "=".
* <BR><BR>
* <H2>Example</H2>
* The following examples demonstrate the process for base64-encoding raw data,
* and for decoding a string containing base64-encoded data back to the raw
* data used to create it:
* <PRE>
* // Base64-encode some raw data:
* String base64String = Base64.encode(rawDataBytes);
* System.out.println("Base64 encoded representation of the raw data is " +
* base64String);
*
* // Decode a base64 string back to raw data:
* try
* {
* byte[] decodedRawDataBytes = Base64.decode(base64String);
* }
* catch (ParseException pe)
* {
* System.err.println("The string did not contain valid base64-encoded " +
* "data: " + pe.getMessage());
* }
* </PRE>
*/
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class Base64
{
private static final char[] BASE64_ALPHABET =
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
"0123456789+/").toCharArray();
private static final char[] BASE64URL_ALPHABET =
("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
"0123456789-_").toCharArray();
private Base64()
{
}
public static String encode(final String data)
{
ensureNotNull(data);
return encode(StaticUtils.getBytes(data));
}
public static String encode(final byte[] data)
{
ensureNotNull(data);
final StringBuilder buffer = new StringBuilder(4*data.length/3+1);
encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
return buffer.toString();
}
public static void encode(final String data, final StringBuilder buffer)
{
ensureNotNull(data);
encode(StaticUtils.getBytes(data), buffer);
}
public static void encode(final String data, final ByteStringBuffer buffer)
{
ensureNotNull(data);
encode(StaticUtils.getBytes(data), buffer);
}
public static void encode(final byte[] data, final StringBuilder buffer)
{
encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
}
public static void encode(final byte[] data, final int off, final int length,
final StringBuilder buffer)
{
encode(BASE64_ALPHABET, data, off, length, buffer, "=");
}
public static void encode(final byte[] data, final ByteStringBuffer buffer)
{
encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
}
public static void encode(final byte[] data, final int off, final int length,
final ByteStringBuffer buffer)
{
encode(BASE64_ALPHABET, data, off, length, buffer, "=");
}
public static String urlEncode(final String data, final boolean pad)
{
return urlEncode(StaticUtils.getBytes(data), pad);
}
public static void urlEncode(final String data, final StringBuilder buffer,
final boolean pad)
{
final byte[] dataBytes = StaticUtils.getBytes(data);
encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
(pad ? "%3d" : null));
}
public static void urlEncode(final String data, final ByteStringBuffer buffer,
final boolean pad)
{
final byte[] dataBytes = StaticUtils.getBytes(data);
encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
(pad ? "%3d" : null));
}
public static String urlEncode(final byte[] data, final boolean pad)
{
final StringBuilder buffer = new StringBuilder(4*data.length/3+6);
encode(BASE64URL_ALPHABET, data, 0, data.length, buffer,
(pad ? "%3d" : null));
return buffer.toString();
}
public static void urlEncode(final byte[] data, final int off,
final int length, final StringBuilder buffer,
final boolean pad)
{
encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
}
public static void urlEncode(final byte[] data, final int off,
final int length, final ByteStringBuffer buffer,
final boolean pad)
{
encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
}
private static void encode(final char[] alphabet, final byte[] data,
final int off, final int length,
final Appendable buffer, final String padStr)
{
ensureNotNull(data);
ensureTrue(data.length >= off);
ensureTrue(data.length >= (off+length));
if (length == 0)
{
return;
}
try
{
int pos = off;
for (int i=0; i < (length / 3); i++)
{
final int intValue = ((data[pos++] & 0xFF) << 16) |
((data[pos++] & 0xFF) << 8) |
(data[pos++] & 0xFF);
buffer.append(alphabet[(intValue >> 18) & 0x3F]);
buffer.append(alphabet[(intValue >> 12) & 0x3F]);
buffer.append(alphabet[(intValue >> 6) & 0x3F]);
buffer.append(alphabet[intValue & 0x3F]);
}
switch ((off+length) - pos)
{
case 1:
int intValue = (data[pos] & 0xFF) << 16;
buffer.append(alphabet[(intValue >> 18) & 0x3F]);
buffer.append(alphabet[(intValue >> 12) & 0x3F]);
if (padStr != null)
{
buffer.append(padStr);
buffer.append(padStr);
}
return;
case 2:
intValue = ((data[pos++] & 0xFF) << 16) | ((data[pos] & 0xFF) << 8);
buffer.append(alphabet[(intValue >> 18) & 0x3F]);
buffer.append(alphabet[(intValue >> 12) & 0x3F]);
buffer.append(alphabet[(intValue >> 6) & 0x3F]);
if (padStr != null)
{
buffer.append(padStr);
}
return;
}
}
catch (final IOException ioe)
{
Debug.debugException(ioe);
// This should never happen.
throw new RuntimeException(ioe.getMessage(), ioe);
}
}
public static byte[] decode(final String data)
throws ParseException
{
ensureNotNull(data);
final int length = data.length();
if (length == 0)
{
return new byte[0];
}
if ((length % 4) != 0)
{
throw new ParseException(ERR_BASE64_DECODE_INVALID_LENGTH.get(), length);
}
int numBytes = 3 * (length / 4);
if (data.charAt(length-2) == '=')
{
numBytes -= 2;
}
else if (data.charAt(length-1) == '=')
{
numBytes--;
}
final byte[] b = new byte[numBytes];
int stringPos = 0;
int arrayPos = 0;
while (stringPos < length)
{
int intValue = 0x00;
for (int i=0; i < 4; i++)
{
intValue <<= 6;
switch (data.charAt(stringPos++))
{
case 'A':
intValue |= 0x00;
break;
case 'B':
intValue |= 0x01;
break;
case 'C':
intValue |= 0x02;
break;
case 'D':
intValue |= 0x03;
break;
case 'E':
intValue |= 0x04;
break;
case 'F':
intValue |= 0x05;
break;
case 'G':
intValue |= 0x06;
break;
case 'H':
intValue |= 0x07;
break;
case 'I':
intValue |= 0x08;
break;
case 'J':
intValue |= 0x09;
break;
case 'K':
intValue |= 0x0A;
break;
case 'L':
intValue |= 0x0B;
break;
case 'M':
intValue |= 0x0C;
break;
case 'N':
intValue |= 0x0D;
break;
case 'O':
intValue |= 0x0E;
break;
case 'P':
intValue |= 0x0F;
break;
case 'Q':
intValue |= 0x10;
break;
case 'R':
intValue |= 0x11;
break;
case 'S':
intValue |= 0x12;
break;
case 'T':
intValue |= 0x13;
break;
case 'U':
intValue |= 0x14;
break;
case 'V':
intValue |= 0x15;
break;
case 'W':
intValue |= 0x16;
break;
case 'X':
intValue |= 0x17;
break;
case 'Y':
intValue |= 0x18;
break;
case 'Z':
intValue |= 0x19;
break;
case 'a':
intValue |= 0x1A;
break;
case 'b':
intValue |= 0x1B;
break;
case 'c':
intValue |= 0x1C;
break;
case 'd':
intValue |= 0x1D;
break;
case 'e':
intValue |= 0x1E;
break;
case 'f':
intValue |= 0x1F;
break;
case 'g':
intValue |= 0x20;
break;
case 'h':
intValue |= 0x21;
break;
case 'i':
intValue |= 0x22;
break;
case 'j':
intValue |= 0x23;
break;
case 'k':
intValue |= 0x24;
break;
case 'l':
intValue |= 0x25;
break;
case 'm':
intValue |= 0x26;
break;
case 'n':
intValue |= 0x27;
break;
case 'o':
intValue |= 0x28;
break;
case 'p':
intValue |= 0x29;
break;
case 'q':
intValue |= 0x2A;
break;
case 'r':
intValue |= 0x2B;
break;
case 's':
intValue |= 0x2C;
break;
case 't':
intValue |= 0x2D;
break;
case 'u':
intValue |= 0x2E;
break;
case 'v':
intValue |= 0x2F;
break;
case 'w':
intValue |= 0x30;
break;
case 'x':
intValue |= 0x31;
break;
case 'y':
intValue |= 0x32;
break;
case 'z':
intValue |= 0x33;
break;
case '0':
intValue |= 0x34;
break;
case '1':
intValue |= 0x35;
break;
case '2':
intValue |= 0x36;
break;
case '3':
intValue |= 0x37;
break;
case '4':
intValue |= 0x38;
break;
case '5':
intValue |= 0x39;
break;
case '6':
intValue |= 0x3A;
break;
case '7':
intValue |= 0x3B;
break;
case '8':
intValue |= 0x3C;
break;
case '9':
intValue |= 0x3D;
break;
case '+':
intValue |= 0x3E;
break;
case '/':
intValue |= 0x3F;
break;
case '=':
switch (length - stringPos)
{
case 0:
intValue >>= 8;
b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
b[arrayPos] = (byte) (intValue & 0xFF);
return b;
case 1:
intValue >>= 10;
b[arrayPos] = (byte) (intValue & 0xFF);
return b;
default:
throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_EQUAL.get(
(stringPos-1)),
(stringPos-1));
}
default:
throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
data.charAt(stringPos-1)),
(stringPos-1));
}
}
b[arrayPos++] = (byte) ((intValue >> 16) & 0xFF);
b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
b[arrayPos++] = (byte) (intValue & 0xFF);
}
return b;
}
public static String decodeToString(final String data)
throws ParseException
{
ensureNotNull(data);
final byte[] decodedBytes = decode(data);
return StaticUtils.toUTF8String(decodedBytes);
}
public static byte[] urlDecode(final String data)
throws ParseException
{
ensureNotNull(data);
final int length = data.length();
if (length == 0)
{
return new byte[0];
}
int stringPos = 0;
final ByteStringBuffer buffer = new ByteStringBuffer(length);
decodeLoop:
while (stringPos < length)
{
int intValue = 0x00;
for (int i=0; i < 4; i++)
{
final char c;
if (stringPos >= length)
{
c = '=';
stringPos++;
}
else
{
c = data.charAt(stringPos++);
}
intValue <<= 6;
switch (c)
{
case 'A':
intValue |= 0x00;
break;
case 'B':
intValue |= 0x01;
break;
case 'C':
intValue |= 0x02;
break;
case 'D':
intValue |= 0x03;
break;
case 'E':
intValue |= 0x04;
break;
case 'F':
intValue |= 0x05;
break;
case 'G':
intValue |= 0x06;
break;
case 'H':
intValue |= 0x07;
break;
case 'I':
intValue |= 0x08;
break;
case 'J':
intValue |= 0x09;
break;
case 'K':
intValue |= 0x0A;
break;
case 'L':
intValue |= 0x0B;
break;
case 'M':
intValue |= 0x0C;
break;
case 'N':
intValue |= 0x0D;
break;
case 'O':
intValue |= 0x0E;
break;
case 'P':
intValue |= 0x0F;
break;
case 'Q':
intValue |= 0x10;
break;
case 'R':
intValue |= 0x11;
break;
case 'S':
intValue |= 0x12;
break;
case 'T':
intValue |= 0x13;
break;
case 'U':
intValue |= 0x14;
break;
case 'V':
intValue |= 0x15;
break;
case 'W':
intValue |= 0x16;
break;
case 'X':
intValue |= 0x17;
break;
case 'Y':
intValue |= 0x18;
break;
case 'Z':
intValue |= 0x19;
break;
case 'a':
intValue |= 0x1A;
break;
case 'b':
intValue |= 0x1B;
break;
case 'c':
intValue |= 0x1C;
break;
case 'd':
intValue |= 0x1D;
break;
case 'e':
intValue |= 0x1E;
break;
case 'f':
intValue |= 0x1F;
break;
case 'g':
intValue |= 0x20;
break;
case 'h':
intValue |= 0x21;
break;
case 'i':
intValue |= 0x22;
break;
case 'j':
intValue |= 0x23;
break;
case 'k':
intValue |= 0x24;
break;
case 'l':
intValue |= 0x25;
break;
case 'm':
intValue |= 0x26;
break;
case 'n':
intValue |= 0x27;
break;
case 'o':
intValue |= 0x28;
break;
case 'p':
intValue |= 0x29;
break;
case 'q':
intValue |= 0x2A;
break;
case 'r':
intValue |= 0x2B;
break;
case 's':
intValue |= 0x2C;
break;
case 't':
intValue |= 0x2D;
break;
case 'u':
intValue |= 0x2E;
break;
case 'v':
intValue |= 0x2F;
break;
case 'w':
intValue |= 0x30;
break;
case 'x':
intValue |= 0x31;
break;
case 'y':
intValue |= 0x32;
break;
case 'z':
intValue |= 0x33;
break;
case '0':
intValue |= 0x34;
break;
case '1':
intValue |= 0x35;
break;
case '2':
intValue |= 0x36;
break;
case '3':
intValue |= 0x37;
break;
case '4':
intValue |= 0x38;
break;
case '5':
intValue |= 0x39;
break;
case '6':
intValue |= 0x3A;
break;
case '7':
intValue |= 0x3B;
break;
case '8':
intValue |= 0x3C;
break;
case '9':
intValue |= 0x3D;
break;
case '-':
intValue |= 0x3E;
break;
case '_':
intValue |= 0x3F;
break;
case '=':
case '%':
switch ((stringPos-1) % 4)
{
case 2:
intValue >>= 10;
buffer.append((byte) (intValue & 0xFF));
break decodeLoop;
case 3:
intValue >>= 8;
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
break decodeLoop;
}
throw new ParseException(ERR_BASE64_URLDECODE_INVALID_LENGTH.get(),
(stringPos-1));
default:
throw new ParseException(
ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
data.charAt(stringPos-1)),
(stringPos-1));
}
}
buffer.append((byte) ((intValue >> 16) & 0xFF));
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
}
return buffer.toByteArray();
}
public static String urlDecodeToString(final String data)
throws ParseException
{
ensureNotNull(data);
final byte[] decodedBytes = urlDecode(data);
return StaticUtils.toUTF8String(decodedBytes);
}
}