You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by ve...@apache.org on 2015/05/02 21:33:21 UTC

svn commit: r1677340 - in /webservices/axiom/trunk/modules/axiom-api/src: main/java/org/apache/axiom/util/base64/ test/java/org/apache/axiom/util/base64/

Author: veithen
Date: Sat May  2 19:33:21 2015
New Revision: 1677340

URL: http://svn.apache.org/r1677340
Log:
Rewrite the Base64Utils#decode(String) method to minimize the number of array allocations and to throw an exception for data that is not correctly encoded (AXIOM-434).

Modified:
    webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/AbstractBase64DecodingWriter.java
    webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Constants.java
    webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Utils.java
    webservices/axiom/trunk/modules/axiom-api/src/test/java/org/apache/axiom/util/base64/Base64UtilsTest.java

Modified: webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/AbstractBase64DecodingWriter.java
URL: http://svn.apache.org/viewvc/webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/AbstractBase64DecodingWriter.java?rev=1677340&r1=1677339&r2=1677340&view=diff
==============================================================================
--- webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/AbstractBase64DecodingWriter.java (original)
+++ webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/AbstractBase64DecodingWriter.java Sat May  2 19:33:21 2015
@@ -65,7 +65,7 @@ public abstract class AbstractBase64Deco
             return -1;
         } else if (c < Base64Constants.S_DECODETABLE.length) {
             int result = Base64Constants.S_DECODETABLE[c];
-            if (result != Byte.MAX_VALUE) {
+            if (result >= 0) {
                 return result;
             }
         }

Modified: webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Constants.java
URL: http://svn.apache.org/viewvc/webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Constants.java?rev=1677340&r1=1677339&r2=1677340&view=diff
==============================================================================
--- webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Constants.java (original)
+++ webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Constants.java Sat May  2 19:33:21 2015
@@ -30,15 +30,36 @@ class Base64Constants {
 
     static final byte S_BASE64PAD = '=';
 
+    /**
+     * Used in {@link #S_DECODETABLE} to indicate that a character is the padding character.
+     */
+    static final byte PADDING = -1;
+    
+    /**
+     * Used in {@link #S_DECODETABLE} to indicate that a character is white space.
+     */
+    static final byte WHITE_SPACE = -2;
+    
+    /**
+     * Used in {@link #S_DECODETABLE} to indicate that a character is invalid.
+     */
+    static final byte INVALID = -3;
+
     static final byte[] S_DECODETABLE = new byte[128];
     
     static {
         for (int i = 0; i < S_DECODETABLE.length; i++) {
-            S_DECODETABLE[i] = Byte.MAX_VALUE; // 127
+            S_DECODETABLE[i] = INVALID;
         }
         for (int i = 0; i < S_BASE64CHAR.length; i++) {
             // 0 to 63
             S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;
         }
+        S_DECODETABLE[S_BASE64PAD] = PADDING;
+        // See http://www.w3.org/TR/2008/REC-xml-20081126/#white
+        S_DECODETABLE[' '] = WHITE_SPACE;
+        S_DECODETABLE['\t'] = WHITE_SPACE;
+        S_DECODETABLE['\r'] = WHITE_SPACE;
+        S_DECODETABLE['\n'] = WHITE_SPACE;
     }
 }

Modified: webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Utils.java
URL: http://svn.apache.org/viewvc/webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Utils.java?rev=1677340&r1=1677339&r2=1677340&view=diff
==============================================================================
--- webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Utils.java (original)
+++ webservices/axiom/trunk/modules/axiom-api/src/main/java/org/apache/axiom/util/base64/Base64Utils.java Sat May  2 19:33:21 2015
@@ -120,7 +120,7 @@ public class Base64Utils {
         for (int i = off; i < off + len; i++) {
             char ch = data[i];
             if (ch == Base64Constants.S_BASE64PAD || ch < Base64Constants.S_DECODETABLE.length
-                    && Base64Constants.S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                    && Base64Constants.S_DECODETABLE[ch] >= 0) {
                 ibuf[ibufcount++] = ch;
                 if (ibufcount == ibuf.length) {
                     ibufcount = 0;
@@ -136,29 +136,69 @@ public class Base64Utils {
     }
 
     /**
-     *
+     * Decodes a base64 encoded string into a byte array. This method is designed to conform to the
+     * <a href="http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#base64Binary">XML Schema</a>
+     * specification. It can be used to decode the text content of an element (or the value of an
+     * attribute) of type <code>base64Binary</code>.
+     * 
+     * @param data
+     *            the base64 encoded data
+     * @return the decoded data
      */
     public static byte[] decode(String data) {
-        char[] ibuf = new char[4];
-        int ibufcount = 0;
-        byte[] obuf = new byte[data.length() / 4 * 3 + 3];
-        int obufcount = 0;
+        int symbols = 0;
+        int padding = 0;
         for (int i = 0; i < data.length(); i++) {
-            char ch = data.charAt(i);
-            if (ch == Base64Constants.S_BASE64PAD || ch < Base64Constants.S_DECODETABLE.length
-                    && Base64Constants.S_DECODETABLE[ch] != Byte.MAX_VALUE) {
-                ibuf[ibufcount++] = ch;
-                if (ibufcount == ibuf.length) {
-                    ibufcount = 0;
-                    obufcount += decode0(ibuf, obuf, obufcount);
-                }
+            switch (Base64Constants.S_DECODETABLE[data.charAt(i)]) {
+                case Base64Constants.PADDING:
+                    if (padding == 2) {
+                        throw new IllegalArgumentException("Too much padding");
+                    }
+                    padding++;
+                    break;
+                case Base64Constants.WHITE_SPACE:
+                    break;
+                case Base64Constants.INVALID:
+                    throw new IllegalArgumentException("Invalid character encountered");
+                default:
+                    // Padding can only occur at the end
+                    if (padding > 0) {
+                        throw new IllegalArgumentException("Unexpected padding character");
+                    }
+                    symbols++;
             }
         }
-        if (obufcount == obuf.length)
-            return obuf;
-        byte[] ret = new byte[obufcount];
-        System.arraycopy(obuf, 0, ret, 0, obufcount);
-        return ret;
+        if ((symbols + padding) % 4 != 0) {
+            throw new IllegalArgumentException("Missing padding");
+        }
+        byte[] result = new byte[(symbols + padding) / 4 * 3 - padding];
+        int pos = 0;
+        int resultPos = 0;
+        byte accumulator = 0;
+        int bits = 0;
+        while (symbols > 0) {
+            byte b = Base64Constants.S_DECODETABLE[data.charAt(pos++)];
+            if (b == Base64Constants.WHITE_SPACE) {
+                continue;
+            }
+            if (bits == 0) {
+                accumulator = (byte)(b << 2);
+                bits = 6;
+            } else {
+                accumulator |= b >> (bits - 2);
+                result[resultPos++] = accumulator;
+                accumulator = (byte)(b << (10 - bits));
+                bits -= 2;
+            }
+            symbols--;
+        }
+        if (accumulator != 0) {
+            throw new IllegalArgumentException("Invalid base64 value");
+        }
+        if (resultPos != result.length) {
+            throw new Error("Oops. This is a bug.");
+        }
+        return result;
     }
 
     /**
@@ -169,7 +209,7 @@ public class Base64Utils {
             char ch = data.charAt(i);
 
             if (ch == Base64Constants.S_BASE64PAD || ch < Base64Constants.S_DECODETABLE.length
-                    && Base64Constants.S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                    && Base64Constants.S_DECODETABLE[ch] >= 0) {
                 //valid character.Do nothing
             } else if (ch == '\r' || ch == '\n') {
                 //do nothing
@@ -192,7 +232,7 @@ public class Base64Utils {
         for (int i = off; i < off + len; i++) {
             char ch = data[i];
             if (ch == Base64Constants.S_BASE64PAD || ch < Base64Constants.S_DECODETABLE.length
-                    && Base64Constants.S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                    && Base64Constants.S_DECODETABLE[ch] >= 0) {
                 ibuf[ibufcount++] = ch;
                 if (ibufcount == ibuf.length) {
                     ibufcount = 0;
@@ -214,7 +254,7 @@ public class Base64Utils {
         for (int i = 0; i < data.length(); i++) {
             char ch = data.charAt(i);
             if (ch == Base64Constants.S_BASE64PAD || ch < Base64Constants.S_DECODETABLE.length
-                    && Base64Constants.S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                    && Base64Constants.S_DECODETABLE[ch] >= 0) {
                 ibuf[ibufcount++] = ch;
                 if (ibufcount == ibuf.length) {
                     ibufcount = 0;

Modified: webservices/axiom/trunk/modules/axiom-api/src/test/java/org/apache/axiom/util/base64/Base64UtilsTest.java
URL: http://svn.apache.org/viewvc/webservices/axiom/trunk/modules/axiom-api/src/test/java/org/apache/axiom/util/base64/Base64UtilsTest.java?rev=1677340&r1=1677339&r2=1677340&view=diff
==============================================================================
--- webservices/axiom/trunk/modules/axiom-api/src/test/java/org/apache/axiom/util/base64/Base64UtilsTest.java (original)
+++ webservices/axiom/trunk/modules/axiom-api/src/test/java/org/apache/axiom/util/base64/Base64UtilsTest.java Sat May  2 19:33:21 2015
@@ -19,33 +19,74 @@
 
 package org.apache.axiom.util.base64;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-import org.apache.commons.codec.binary.Base64;
+import java.util.Random;
 
 import junit.framework.TestCase;
 
+import org.apache.commons.codec.binary.Base64;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+
 public class Base64UtilsTest extends TestCase {
+    public void testDecode() {
+        Random random = new Random(43219876);
+        for (int len=0; len<20; len++) {
+            byte[] data = new byte[len];
+            random.nextBytes(data);
+            Assert.assertThat(
+                    Base64Utils.decode(Base64.encodeBase64String(data)),
+                    CoreMatchers.equalTo(data));
+        }
+    }
+    
+    public void testMissingPadding() {
+        try {
+            Base64Utils.decode("cw");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
 
-    Object expectedObject;
+    public void testTooMuchPadding() {
+        try {
+            Base64Utils.decode("cw===");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+    
+    public void testNonZeroRemainder() {
+        try {
+            Base64Utils.decode("//==");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+    
+    public void testSpace() throws Exception{
+        assertEquals(
+                "any carnal pleasure.",
+                new String(Base64Utils.decode(" YW55IG\tNhcm5hbC\r\nBwb  GVhc3VyZS4 = "), "utf-8"));
+    }
 
-    ByteArrayInputStream byteStream;
+    public void testInvalidCharacter() {
+        try {
+            Base64Utils.decode("//-/");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
 
-    public void testDecode() throws Exception {
-        Object actualObject;
-        String expectedBase64;
-        expectedObject = new String("Lanka Software Foundation");
-        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
-        ObjectOutputStream objectOutStream = new ObjectOutputStream(byteStream);
-        objectOutStream.writeObject(expectedObject);
-        expectedBase64 = Base64.encodeBase64String(byteStream.toByteArray());
-        byte[] tempa = Base64Utils.decode(expectedBase64);
-        ObjectInputStream objectInStream = new ObjectInputStream(
-                new ByteArrayInputStream(tempa));
-        actualObject = objectInStream.readObject();
-        assertEquals("Base64 Encoding Check", expectedObject, actualObject);
+    public void testInvalidPadding() {
+        try {
+            Base64Utils.decode("//=/");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
     }
 }
\ No newline at end of file