/****************************************************************************************
* Copyright 2012 IBM Corp. *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); *
* you may not use this file except in compliance with the License. *
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
****************************************************************************************/
package com.ibm.sbt.security.authentication.oauth.consumer;
public final class Base64Url {
// private static final String CLASS_NAME = Base64Url.class.getName();
// Tokens used in Base64-encoded data, indexed by their corresponding byte value.
private static final char[] TOKENS = { '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', '-', '_' };
// The minimum token character value considered for decoding.
private static final int TOKEN_MIN = 0;
// The maximum token character value considered for decoding.
private static final int TOKEN_MAX = Byte.MAX_VALUE;
// Value associated with characters that are not valid Base64 token.
private static final byte INVALID_TOKEN_VALUE = -1;
// Array indexed by token character and containing decoded values.
// INVALID_TOKEN_VALUE is used for characters that are not valid Base64
// tokens.
private static final byte[] TOKEN_VALUES = new byte[TOKEN_MAX - TOKEN_MIN + 1];
static {
// Initialize the token values array to the invalid token value.
for (int i = 0; i < TOKEN_VALUES.length; i++) {
TOKEN_VALUES[i] = INVALID_TOKEN_VALUE;
}
// Populate the valid token entries in the token values array.
for (int i = 0; i < TOKENS.length; i++) {
TOKEN_VALUES[TOKENS[i]] = (byte) i;
}
}
// Private constructor to prevent instantiation.
private Base64Url() {
// Never called.
}
/**
* Encode a byte array into a Base64-encoded string. The string is not
* broken into 72 character lines.
*
* @param data
* Byte data to be encoded.
* @return Base64-encoded string.
*/
public static String encode(byte[] data) {
return encode(data, 0, data.length);
}
/**
* Encode a byte array into a Base64-encoded String. The String is not
* broken into 72 character lines.
*
* @param data
* Byte data to be encoded
* @param offset
* Starting index within the byte data to be encoded
* @param length
* Number of bytes to encode
* @return Base64-encoded string.
*/
public static String encode(byte[] data, int offset, int length) {
// overestimating the resultSize is ok
final int remainder = length % 3;
final int resultSize = ((remainder == 0 ? length : length + 3 - remainder) / 3 * 4);
final StringBuilder resultBuff = new StringBuilder(resultSize);
// Indicates the current position within the three byte encoding input group.
int groupIndex = 0;
byte previousByte = 0;
byte currentByte;
// Iterate through and encode all the input data.
for (int i = offset; i < length+offset; i++) {
currentByte = data[i];
switch (groupIndex) {
case 0: {
// Encode the first 6-bit output token using the first byte of
// the input group.
resultBuff.append(TOKENS[(currentByte & 0xfc) >> 2]);
groupIndex = 1;
break;
}
case 1: {
// Encode the second 6-bit output token using the first and
// second byte of the input group.
resultBuff.append(TOKENS[((previousByte & 0x3) << 4)
| ((currentByte & 0xf0) >> 4)]);
groupIndex = 2;
break;
}
case 2: {
// Encode the third and fourth 6-bit output token using the
// second and third byte of the input group.
resultBuff.append(TOKENS[((previousByte & 0xf) << 2)
| ((currentByte & 0xc0) >> 6)]);
resultBuff.append(TOKENS[currentByte & 0x3f]);
groupIndex = 0;
break;
}
}
// Keep hold of the current byte as it may contain data for the next
// 6-bit output.
previousByte = currentByte;
}
// Encode any remaining data using zero as the next byte
switch (groupIndex) {
case 1: {
resultBuff.append(TOKENS[(previousByte & 0x3) << 4]);
break;
}
case 2: {
resultBuff.append(TOKENS[(previousByte & 0xf) << 2]);
break;
}
}
return resultBuff.toString();
}
/**
* Decode a Base64-encoded string
*
* @param data
* Base64-encoded String
* @return Decoded data
* @throws IllegalArgumentException
* Thrown if the supplied string is not a valid Base64-encoded
* string.
*/
public static byte[] decode(String data) throws IllegalArgumentException {
return decode(data.toCharArray(), 0, data.length());
}
/**
* Decode the portion of the char array of Base64-encoded data identified by
* offset and length
*
* @param data
* Base64-encoded char array
* @param offset
* The starting point in the char array of the data to be decoded
* @param length
* The length of the data in the char array to be decoded
* @return Decoded data
* @throws IllegalArgumentException
* Thrown if the supplied char array does not contain valid
* Base64-encoded data.
*/
public static byte[] decode(char[] data, int offset, int length) {
int numberOfCompleteGroups = length/4;
int incompleteGroupLength = length%4;
int resultLength = numberOfCompleteGroups*3;
switch (incompleteGroupLength) {
case 1: {
throw new IllegalArgumentException();
}
case 2: {
resultLength += 1;
break;
}
case 3: {
resultLength += 2;
break;
}
}
final byte[] result = new byte[resultLength];
int index = 0;
// Current index in the four character input group.
int groupIndex = 0;
int previousTokenValue = 0;
int currentTokenValue;
// Read and decode all the input data.
for (int i = offset; i < length+offset; i++) {
char token = data[i];
// Ignore any tokens that are not valid Base64 characters.
if (token < TOKEN_MIN
|| token > TOKEN_MAX
|| (currentTokenValue = TOKEN_VALUES[token]) == INVALID_TOKEN_VALUE) {
continue;
}
switch (groupIndex) {
case 0: {
groupIndex = 1;
break;
}
case 1: {
// Calculate the first byte of the output group from the first
// and second characters of the input.
result[index++] = (byte) ((previousTokenValue << 2) | ((currentTokenValue & 0x30) >> 4));
groupIndex = 2;
break;
}
case 2: {
// Calculate the second byte of the output group from the second
// and third characters of the input.
result[index++] = (byte) (((previousTokenValue & 0xf) << 4) | ((currentTokenValue & 0x3c) >> 2));
groupIndex = 3;
break;
}
case 3: {
// Calculate the third byte of the output group from the third
// and fourth characters of the input.
result[index++] = (byte) (((previousTokenValue & 0x3) << 6) | currentTokenValue);
groupIndex = 0;
break;
}
}
previousTokenValue = currentTokenValue;
}
final byte[] decoded;
if (index < result.length) { // must have ignored some non Base64 characters
throw new IllegalArgumentException();
} else {
decoded = result;
}
return decoded;
}
public static void main(String[] args) {
byte[] toEncode;
String encoded, toEncodeStr, decodedStr;
byte[] decoded;
java.util.Random rand = new java.util.Random();
for (int i=1; i<100; i++) {
toEncode = new byte[i];
rand.nextBytes(toEncode);
toEncodeStr = new java.math.BigInteger(toEncode).toString();
encoded = Base64Url.encode(toEncode);
decoded = Base64Url.decode(encoded);
decodedStr = new java.math.BigInteger(decoded).toString();
// System.out.println(encoded + "," + toEncodeStr + "," + decodedStr);
for (int j=0; j<i; j++) {
if (toEncode[j] != decoded[j]) {
// System.out.println("failed at length=" + i + " toEncode=" + toEncode + " decoded=" + decoded);
}
}
}
}
}