You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2009/07/13 22:37:32 UTC
svn commit: r793704 - in /commons/proper/codec/trunk/src:
java/org/apache/commons/codec/binary/Base64.java
test/org/apache/commons/codec/binary/Base64Test.java
Author: ggregory
Date: Mon Jul 13 20:37:32 2009
New Revision: 793704
URL: http://svn.apache.org/viewvc?rev=793704&view=rev
Log:
[#CODEC-75] Make Base64 URL-safe. Applied patch (https://issues.apache.org/jira/secure/attachment/12408898/codec75.patch) and make other clean ups.
Modified:
commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java
commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64Test.java
Modified: commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java?rev=793704&r1=793703&r2=793704&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java (original)
+++ commons/proper/codec/trunk/src/java/org/apache/commons/codec/binary/Base64.java Mon Jul 13 20:37:32 2009
@@ -66,7 +66,7 @@
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
- private static final byte[] intToBase64 = {
+ private static final byte[] STANDARD_ENCODE_TABLE = {
'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',
@@ -75,27 +75,44 @@
};
/**
+ * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and /
+ * changed to - and _ to make the encoded Base64 results more URL-SAFE.
+ * This table is only used when the Base64's mode is set to URL-SAFE.
+ */
+ private static final byte[] URL_SAFE_ENCODE_TABLE = {
+ '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', '-', '_'
+ };
+
+ /**
* Byte used to pad output.
*/
private static final byte PAD = '=';
/**
- * This array is a lookup table that translates unicode characters
+ * This array is a lookup table that translates Unicode characters
* drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
* into their 6-bit positive integer equivalents. Characters that
* are not in the Base64 alphabet but fall within the bounds of the
* array are translated to -1.
*
+ * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63.
+ * This means decoder seamlessly handles both URL_SAFE and STANDARD base64.
+ * (The encoder, on the other hand, needs to know ahead of time what to emit).
+ *
* Thanks to "commons" project in ws.apache.org for this code.
* http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
*/
- private static final byte[] base64ToInt = {
+ private static final byte[] DECODE_TABLE = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
- 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
@@ -109,6 +126,12 @@
// The private member fields below are used with the new streaming approach, which requires
// some state be preserved between calls of encode() and decode().
+ /**
+ * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static
+ * because it is able to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member
+ * variable so we can switch between the two modes.
+ */
+ private final byte[] encodeTable;
/**
* Line length for encoding. Not used when decoding. A value of zero or less implies
@@ -175,27 +198,44 @@
private int x;
/**
- * Default constructor: lineLength is 76, and the lineSeparator is CRLF
- * when encoding, and all forms can be decoded.
+ * Sets state for decoding and encoding.
+ * <p>
+ * When encoding the line length is 76, the line separator is CRLF, and we use the STANDARD_ENCODE_TABLE.
+ * </p>
+ *
+ * <p>
+ * When decoding all variants can be decoded.
+ * </p>
*/
public Base64() {
- this(CHUNK_SIZE, CHUNK_SEPARATOR);
+ this(false);
+ }
+
+ /**
+ * Same as default constructor (line length is 76, line separator is CRLF), but URL-SAFE mode for encoding is
+ * supplied.
+ *
+ * When decoding: all variants can be decoded.
+ *
+ * @param urlSafe
+ * true if URL-SAFE encoding should be performed. In most situations this should be set to false.
+ */
+ public Base64(boolean urlSafe) {
+ this(CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe);
}
/**
* <p>
- * Consumer can use this constructor to choose a different lineLength
- * when encoding (lineSeparator is still CRLF). All forms of data can
- * be decoded.
- * </p><p>
- * Note: lineLengths that aren't multiples of 4 will still essentially
- * end up being multiples of 4 in the encoded data.
+ * Sets the line length when encoding (line separator is still CRLF). All forms of data can be decoded.
* </p>
- *
- * @param lineLength each line of encoded data will be at most this long
- * (rounded up to nearest multiple of 4).
- * If lineLength <= 0, then the output will not be divided into lines (chunks).
- * Ignored when decoding.
+ * <p>
+ * Note: line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded
+ * data.
+ * </p>
+ *
+ * @param lineLength
+ * each line of encoded data will be at most this long (rounded up to nearest multiple of 4). If
+ * lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when decoding.
*/
public Base64(int lineLength) {
this(lineLength, CHUNK_SEPARATOR);
@@ -203,9 +243,30 @@
/**
* <p>
- * Consumer can use this constructor to choose a different lineLength
- * and lineSeparator when encoding. All forms of data can
- * be decoded.
+ * Sets the line length and line separator when encoding. All forms of data can be decoded.
+ * </p>
+ * <p>
+ * Note: line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded
+ * data.
+ * </p>
+ *
+ * @param lineLength
+ * Each line of encoded data will be at most this long (rounded up to nearest multiple of 4). Ignored
+ * when decoding. If <= 0, then output will not be divided into lines (chunks).
+ * @param lineSeparator
+ * Each line of encoded data will end with this sequence of bytes.
+ * @throws IllegalArgumentException
+ * The provided lineSeparator included some base64 characters. That's not going to work!
+ */
+ public Base64(int lineLength, byte[] lineSeparator) {
+ this(lineLength, lineSeparator, false);
+ }
+
+ /**
+ * <p>
+ * Consumer can use this constructor to choose a different lineLength,
+ * lineSeparator, and whether to use URL-SAFE mode when encoding.
+ * All forms of data can be decoded.
* </p><p>
* Note: lineLengths that aren't multiples of 4 will still essentially
* end up being multiples of 4 in the encoded data.
@@ -216,10 +277,14 @@
* @param lineSeparator Each line of encoded data will end with this
* sequence of bytes.
* If lineLength <= 0, then the lineSeparator is not used.
+ * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively.
+ * urlSafe is only applied to "encode" operations. Decoding seamlessly
+ * handles both modes.
+ *
* @throws IllegalArgumentException The provided lineSeparator included
* some base64 characters. That's not going to work!
*/
- public Base64(int lineLength, byte[] lineSeparator) {
+ public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) {
this.lineLength = lineLength;
this.lineSeparator = new byte[lineSeparator.length];
System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length);
@@ -228,7 +293,7 @@
} else {
this.encodeSize = 4;
}
- this.decodeSize = encodeSize - 1;
+ this.decodeSize = this.encodeSize - 1;
if (containsBase64Byte(lineSeparator)) {
String sep;
try {
@@ -238,21 +303,35 @@
}
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
}
+ this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
+ }
+
+ /**
+ * Returns our current encode mode. True if we're URL-SAFE, false otherwise.
+ *
+ * @return true if we're in URL-SAFE mode, false otherwise.
+ */
+ public boolean isUrlSafe() {
+ return this.encodeTable == URL_SAFE_ENCODE_TABLE;
}
/**
* Returns true if this Base64 object has buffered data for reading.
- *
+ *
* @return true if there is Base64 object still available for reading.
*/
- boolean hasData() { return buf != null; }
+ boolean hasData() {
+ return this.buf != null;
+ }
/**
* Returns the amount of buffered data available for reading.
- *
+ *
* @return The amount of buffered data available for reading.
*/
- int avail() { return buf != null ? pos - readPos : 0; }
+ int avail() {
+ return buf != null ? pos - readPos : 0;
+ }
/** Doubles our buffer. */
private void resizeBuf() {
@@ -294,9 +373,8 @@
buf = null;
}
return len;
- } else {
- return eof ? -1 : 0;
}
+ return eof ? -1 : 0;
}
/**
@@ -337,7 +415,6 @@
if (eof) {
return;
}
-
// inAvail < 0 is how we're informed of EOF in the underlying data we're
// encoding.
if (inAvail < 0) {
@@ -347,17 +424,23 @@
}
switch (modulus) {
case 1:
- buf[pos++] = intToBase64[(x >> 2) & MASK_6BITS];
- buf[pos++] = intToBase64[(x << 4) & MASK_6BITS];
- buf[pos++] = PAD;
- buf[pos++] = PAD;
+ buf[pos++] = encodeTable[(x >> 2) & MASK_6BITS];
+ buf[pos++] = encodeTable[(x << 4) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buf[pos++] = PAD;
+ buf[pos++] = PAD;
+ }
break;
case 2:
- buf[pos++] = intToBase64[(x >> 10) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 4) & MASK_6BITS];
- buf[pos++] = intToBase64[(x << 2) & MASK_6BITS];
- buf[pos++] = PAD;
+ buf[pos++] = encodeTable[(x >> 10) & MASK_6BITS];
+ buf[pos++] = encodeTable[(x >> 4) & MASK_6BITS];
+ buf[pos++] = encodeTable[(x << 2) & MASK_6BITS];
+ // URL-SAFE skips the padding to further reduce size.
+ if (encodeTable == STANDARD_ENCODE_TABLE) {
+ buf[pos++] = PAD;
+ }
break;
}
if (lineLength > 0) {
@@ -374,10 +457,10 @@
if (b < 0) { b += 256; }
x = (x << 8) + b;
if (0 == modulus) {
- buf[pos++] = intToBase64[(x >> 18) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 12) & MASK_6BITS];
- buf[pos++] = intToBase64[(x >> 6) & MASK_6BITS];
- buf[pos++] = intToBase64[x & MASK_6BITS];
+ buf[pos++] = encodeTable[(x >> 18) & MASK_6BITS];
+ buf[pos++] = encodeTable[(x >> 12) & MASK_6BITS];
+ buf[pos++] = encodeTable[(x >> 6) & MASK_6BITS];
+ buf[pos++] = encodeTable[x & MASK_6BITS];
currentLinePos += 4;
if (lineLength > 0 && lineLength <= currentLinePos) {
System.arraycopy(lineSeparator, 0, buf, pos, lineSeparator.length);
@@ -423,23 +506,12 @@
}
byte b = in[inPos++];
if (b == PAD) {
- x = x << 6;
- switch (modulus) {
- case 2:
- x = x << 6;
- buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
- break;
- case 3:
- buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
- buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
- break;
- }
// WE'RE DONE!!!!
eof = true;
- return;
+ break;
} else {
- if (b >= 0 && b < base64ToInt.length) {
- int result = base64ToInt[b];
+ if (b >= 0 && b < DECODE_TABLE.length) {
+ int result = DECODE_TABLE[b];
if (result >= 0) {
modulus = (++modulus) % 4;
x = (x << 6) + result;
@@ -452,6 +524,23 @@
}
}
}
+
+ // Two forms of EOF as far as base64 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (eof && modulus != 0) {
+ x = x << 6;
+ switch (modulus) {
+ case 2:
+ x = x << 6;
+ buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
+ break;
+ case 3:
+ buf[pos++] = (byte) ((x >> 16) & MASK_8BITS);
+ buf[pos++] = (byte) ((x >> 8) & MASK_8BITS);
+ break;
+ }
+ }
}
/**
@@ -462,7 +551,7 @@
* @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
*/
public static boolean isBase64(byte octet) {
- return octet == PAD || (octet >= 0 && octet < base64ToInt.length && base64ToInt[octet] != -1);
+ return octet == PAD || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
}
/**
@@ -483,7 +572,7 @@
return true;
}
- /*
+ /**
* Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
*
* @param arrayOctet
@@ -511,6 +600,19 @@
}
/**
+ * Encodes binary data using a url-safe variation of the base64 algorithm but does not chunk the output.
+ * The url-safe variation emits - and _ instead of + and / characters.
+ *
+ * @param binaryData
+ * binary data to encode
+ * @return Base64 characters
+ */
+ public static byte[] encodeBase64URLSafe(byte[] binaryData) {
+ return encodeBase64(binaryData, false, true);
+ }
+
+
+ /**
* Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
*
* @param binaryData
@@ -561,11 +663,27 @@
* Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
*/
public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) {
+ return encodeBase64(binaryData, isChunked, false);
+ }
+
+ /**
+ * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
+ *
+ * @param binaryData
+ * Array containing binary data to encode.
+ * @param isChunked
+ * if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
+ * @param urlSafe
+ * if <code>true</code> this encoder will emit - and _ instead of the usual + and / characters.
+ * @return Base64-encoded data.
+ * @throws IllegalArgumentException
+ * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
+ */
+ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe) {
if (binaryData == null || binaryData.length == 0) {
return binaryData;
}
- Base64 b64 = isChunked ? new Base64() : new Base64(0);
-
+ Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe);
long len = (binaryData.length * 4) / 3;
long mod = len % 4;
if (mod != 0) {
@@ -574,7 +692,6 @@
if (isChunked) {
len += (1 + (len / CHUNK_SIZE)) * CHUNK_SEPARATOR.length;
}
-
if (len > Integer.MAX_VALUE) {
throw new IllegalArgumentException(
"Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
@@ -583,11 +700,17 @@
b64.setInitialBuffer(buf, 0, buf.length);
b64.encode(binaryData, 0, binaryData.length);
b64.encode(binaryData, 0, -1); // Notify encoder of EOF.
-
// Encoder might have resized, even though it was unnecessary.
if (b64.buf != buf) {
b64.readResults(buf, 0, buf.length);
}
+ // In URL-SAFE mode we skip the padding characters, so sometimes our
+ // final length is a bit smaller.
+ if (urlSafe && b64.pos < buf.length) {
+ byte[] smallerBuf = new byte[b64.pos];
+ System.arraycopy(buf, 0, smallerBuf, 0, b64.pos);
+ buf = smallerBuf;
+ }
return buf;
}
@@ -602,13 +725,11 @@
return base64Data;
}
Base64 b64 = new Base64();
-
long len = (base64Data.length * 3) / 4;
byte[] buf = new byte[(int) len];
b64.setInitialBuffer(buf, 0, buf.length);
b64.decode(base64Data, 0, base64Data.length);
b64.decode(base64Data, 0, -1); // Notify decoder of EOF.
-
// We have no idea what the line-length was, so we
// cannot know how much of our array wasn't used.
byte[] result = new byte[b64.pos];
@@ -627,7 +748,6 @@
static byte[] discardWhitespace(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
-
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case ' ' :
@@ -639,30 +759,28 @@
groomedData[bytesCopied++] = data[i];
}
}
-
byte packedData[] = new byte[bytesCopied];
-
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
return packedData;
}
/**
- * Check if a byte value is whitespace or not.
+ * Checks if a byte value is whitespace or not.
*
- * @param byteToCheck the byte to check
+ * @param byteToCheck
+ * the byte to check
* @return true if byte is whitespace, false otherwise
*/
- private static boolean isWhiteSpace(byte byteToCheck){
+ private static boolean isWhiteSpace(byte byteToCheck) {
switch (byteToCheck) {
- case ' ' :
- case '\n' :
- case '\r' :
- case '\t' :
- return true;
- default :
- return false;
+ case ' ' :
+ case '\n' :
+ case '\r' :
+ case '\t' :
+ return true;
+ default :
+ return false;
}
}
@@ -677,17 +795,13 @@
static byte[] discardNonBase64(byte[] data) {
byte groomedData[] = new byte[data.length];
int bytesCopied = 0;
-
for (int i = 0; i < data.length; i++) {
if (isBase64(data[i])) {
groomedData[bytesCopied++] = data[i];
}
}
-
byte packedData[] = new byte[bytesCopied];
-
System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
return packedData;
}
@@ -718,12 +832,12 @@
* @return A byte array containing only Base64 character data
*/
public byte[] encode(byte[] pArray) {
- return encodeBase64(pArray, false);
+ return encodeBase64(pArray, false, isUrlSafe());
}
// Implementation of integer encoding used for crypto
/**
- * Decode a byte64-encoded integer according to crypto
+ * Decodes a byte64-encoded integer according to crypto
* standards such as W3C's XML-Signature
*
* @param pArray a byte array containing base64 character data
@@ -734,7 +848,7 @@
}
/**
- * Encode to a byte64-encoded integer according to crypto
+ * Encodes to a byte64-encoded integer according to crypto
* standards such as W3C's XML-Signature
*
* @param bigInt a BigInteger
@@ -745,7 +859,6 @@
if(bigInt == null) {
throw new NullPointerException("encodeInteger called with null parameter");
}
-
return encodeBase64(toIntegerBytes(bigInt), false);
}
@@ -766,7 +879,6 @@
(((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
return bigBytes;
}
-
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
@@ -776,12 +888,9 @@
startSrc = 1;
len--;
}
-
int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
byte[] resizedBytes = new byte[bitlen / 8];
-
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
-
return resizedBytes;
}
}
Modified: commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64Test.java
URL: http://svn.apache.org/viewvc/commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64Test.java?rev=793704&r1=793703&r2=793704&view=diff
==============================================================================
--- commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64Test.java (original)
+++ commons/proper/codec/trunk/src/test/org/apache/commons/codec/binary/Base64Test.java Mon Jul 13 20:37:32 2009
@@ -18,6 +18,7 @@
package org.apache.commons.codec.binary;
+import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Random;
import java.math.BigInteger;
@@ -746,6 +747,89 @@
}
base64 = new Base64(64,new byte[]{' ','$','\n','\r','\t'}); // OK
}
+
+ /**
+ * Base64 encoding of UUID's is a common use-case, especially in URL-SAFE
+ * mode. This test case ends up being the "URL-SAFE" JUnit's.
+ *
+ * @throws DecoderException if Hex.decode() fails - a serious problem since
+ * Hex comes from our own commons-codec!
+ *
+ * @throws UnsupportedEncodingException if "UTF-8" character set is not
+ * available. Unlikely.
+ */
+ public void testUUID() throws DecoderException, UnsupportedEncodingException {
+ // The 4 UUID's below contains mixtures of + and / to help us test the
+ // URL-SAFE encoding mode.
+ byte[][] ids = new byte[4][];
+
+ // ids[0] was chosen so that it encodes with at least one +.
+ ids[0] = Hex.decodeHex("94ed8d0319e4493399560fb67404d370".toCharArray());
+
+ // ids[1] was chosen so that it encodes with both / and +.
+ ids[1] = Hex.decodeHex("2bf7cc2701fe4397b49ebeed5acc7090".toCharArray());
+
+ // ids[2] was chosen so that it encodes with at least one /.
+ ids[2] = Hex.decodeHex("64be154b6ffa40258d1a01288e7c31ca".toCharArray());
+
+ // ids[3] was chosen so that it encodes with both / and +, with /
+ // right at the beginning.
+ ids[3] = Hex.decodeHex("ff7f8fc01cdb471a8c8b5a9306183fe8".toCharArray());
+
+ byte[][] standard = new byte[4][];
+ standard[0] = "lO2NAxnkSTOZVg+2dATTcA==".getBytes("UTF-8");
+ standard[1] = "K/fMJwH+Q5e0nr7tWsxwkA==".getBytes("UTF-8");
+ standard[2] = "ZL4VS2/6QCWNGgEojnwxyg==".getBytes("UTF-8");
+ standard[3] = "/3+PwBzbRxqMi1qTBhg/6A==".getBytes("UTF-8");
+
+ byte[][] urlSafe1 = new byte[4][];
+ // regular padding (two '==' signs).
+ urlSafe1[0] = "lO2NAxnkSTOZVg-2dATTcA==".getBytes("UTF-8");
+ urlSafe1[1] = "K_fMJwH-Q5e0nr7tWsxwkA==".getBytes("UTF-8");
+ urlSafe1[2] = "ZL4VS2_6QCWNGgEojnwxyg==".getBytes("UTF-8");
+ urlSafe1[3] = "_3-PwBzbRxqMi1qTBhg_6A==".getBytes("UTF-8");
+
+ byte[][] urlSafe2 = new byte[4][];
+ // single padding (only one '=' sign).
+ urlSafe2[0] = "lO2NAxnkSTOZVg-2dATTcA=".getBytes("UTF-8");
+ urlSafe2[1] = "K_fMJwH-Q5e0nr7tWsxwkA=".getBytes("UTF-8");
+ urlSafe2[2] = "ZL4VS2_6QCWNGgEojnwxyg=".getBytes("UTF-8");
+ urlSafe2[3] = "_3-PwBzbRxqMi1qTBhg_6A=".getBytes("UTF-8");
+
+ byte[][] urlSafe3 = new byte[4][];
+ // no padding (no '=' signs).
+ urlSafe3[0] = "lO2NAxnkSTOZVg-2dATTcA".getBytes("UTF-8");
+ urlSafe3[1] = "K_fMJwH-Q5e0nr7tWsxwkA".getBytes("UTF-8");
+ urlSafe3[2] = "ZL4VS2_6QCWNGgEojnwxyg".getBytes("UTF-8");
+ urlSafe3[3] = "_3-PwBzbRxqMi1qTBhg_6A".getBytes("UTF-8");
+
+ for (int i = 0; i < 4; i++) {
+ byte[] encodedStandard = Base64.encodeBase64(ids[i]);
+ byte[] encodedUrlSafe = Base64.encodeBase64URLSafe(ids[i]);
+ byte[] decodedStandard = Base64.decodeBase64(standard[i]);
+ byte[] decodedUrlSafe1 = Base64.decodeBase64(urlSafe1[i]);
+ byte[] decodedUrlSafe2 =Base64.decodeBase64(urlSafe2[i]);
+ byte[] decodedUrlSafe3 =Base64.decodeBase64(urlSafe3[i]);
+
+ // Very important debugging output should anyone
+ // ever need to delve closely into this stuff.
+ if (false) {
+ System.out.println("reference: [" + new String(Hex.encodeHex(ids[i])) + "]");
+ System.out.println("standard: [" + new String(Hex.encodeHex(decodedStandard)) + "] From: [" + new String(standard[i], "UTF-8") + "]");
+ System.out.println("safe1: [" + new String(Hex.encodeHex(decodedUrlSafe1)) + "] From: [" + new String(urlSafe1[i], "UTF-8") + "]");
+ System.out.println("safe2: [" + new String(Hex.encodeHex(decodedUrlSafe2)) + "] From: [" + new String(urlSafe2[i], "UTF-8") + "]");
+ System.out.println("safe3: [" + new String(Hex.encodeHex(decodedUrlSafe3)) + "] From: [" + new String(urlSafe3[i], "UTF-8") + "]");
+ }
+
+ assertTrue("standard encode uuid", Arrays.equals(encodedStandard, standard[i]));
+ assertTrue("url-safe encode uuid", Arrays.equals(encodedUrlSafe, urlSafe3[i]));
+ assertTrue("standard decode uuid", Arrays.equals(decodedStandard, ids[i]));
+ assertTrue("url-safe1 decode uuid", Arrays.equals(decodedUrlSafe1, ids[i]));
+ assertTrue("url-safe2 decode uuid", Arrays.equals(decodedUrlSafe2, ids[i]));
+ assertTrue("url-safe3 decode uuid", Arrays.equals(decodedUrlSafe3, ids[i]));
+ }
+ }
+
// -------------------------------------------------------- Private Methods
private String toString(byte[] data) {