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) {


Reply via email to