You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2019/11/08 09:01:38 UTC

[httpcomponents-client] 01/01: HTTPCLIENT-2023: Allow nested arrays and all primitive types in DefaultHttpCacheEntrySerializer

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 4.5.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 3d09a430085eb05c4121ac619562daff33c43498
Author: Olof Larsson <ol...@sylt.nu>
AuthorDate: Wed Nov 6 18:02:20 2019 +0100

    HTTPCLIENT-2023: Allow nested arrays and all primitive types in DefaultHttpCacheEntrySerializer
---
 .../cache/DefaultHttpCacheEntrySerializer.java     |  61 ++++---
 .../cache/TestHttpCacheEntrySerializers.java       | 193 ++++++++++++++++++---
 2 files changed, 203 insertions(+), 51 deletions(-)

diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java
index 806b194..4751576 100644
--- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java
+++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/DefaultHttpCacheEntrySerializer.java
@@ -54,22 +54,6 @@ import org.apache.http.client.cache.HttpCacheEntrySerializer;
 @Contract(threading = ThreadingBehavior.IMMUTABLE)
 public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer {
 
-    private static final List<Pattern> ALLOWED_CLASS_PATTERNS = Collections.unmodifiableList(Arrays.asList(
-            Pattern.compile("^(\\[L)?org\\.apache\\.http\\.(.*)"),
-            Pattern.compile("^(\\[L)?java\\.util\\.(.*)"),
-            Pattern.compile("^(\\[L)?java\\.lang\\.(.*)$"),
-            Pattern.compile("^\\[B$")));
-
-    private final List<Pattern> allowedClassPatterns;
-
-    DefaultHttpCacheEntrySerializer(final Pattern... allowedClassPatterns) {
-        this.allowedClassPatterns = Collections.unmodifiableList(Arrays.asList(allowedClassPatterns));
-    }
-
-    public DefaultHttpCacheEntrySerializer() {
-        this.allowedClassPatterns = ALLOWED_CLASS_PATTERNS;
-    }
-
     @Override
     public void writeTo(final HttpCacheEntry cacheEntry, final OutputStream os) throws IOException {
         final ObjectOutputStream oos = new ObjectOutputStream(os);
@@ -82,7 +66,7 @@ public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer
 
     @Override
     public HttpCacheEntry readFrom(final InputStream is) throws IOException {
-        final ObjectInputStream ois = new RestrictedObjectInputStream(is, allowedClassPatterns);
+        final ObjectInputStream ois = new RestrictedObjectInputStream(is);
         try {
             return (HttpCacheEntry) ois.readObject();
         } catch (final ClassNotFoundException ex) {
@@ -92,32 +76,47 @@ public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer
         }
     }
 
-    private static class RestrictedObjectInputStream extends ObjectInputStream {
+    // visible for testing
+    static class RestrictedObjectInputStream extends ObjectInputStream {
 
-        private final List<Pattern> allowedClassPatterns;
+        private static final List<Pattern> ALLOWED_CLASS_PATTERNS = Collections.unmodifiableList(Arrays.asList(
+                Pattern.compile("^(?:\\[+L)?org\\.apache\\.http\\..*$"),
+                Pattern.compile("^(?:\\[+L)?java\\.util\\..*$"),
+                Pattern.compile("^(?:\\[+L)?java\\.lang\\..*$"),
+                Pattern.compile("^\\[+Z$"), // boolean
+                Pattern.compile("^\\[+B$"), // byte
+                Pattern.compile("^\\[+C$"), // char
+                Pattern.compile("^\\[+D$"), // double
+                Pattern.compile("^\\[+F$"), // float
+                Pattern.compile("^\\[+I$"), // int
+                Pattern.compile("^\\[+J$"), // long
+                Pattern.compile("^\\[+S$") // short
+        ));
 
-        private RestrictedObjectInputStream(final InputStream in, final List<Pattern> patterns) throws IOException {
+        private RestrictedObjectInputStream(final InputStream in) throws IOException {
             super(in);
-            this.allowedClassPatterns = patterns;
         }
 
         @Override
-        protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
-            if (isProhibited(desc)) {
-                throw new HttpCacheEntrySerializationException(String.format(
-                        "Class %s is not allowed for deserialization", desc.getName()));
+        protected Class<?> resolveClass(final ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
+            final String className = objectStreamClass.getName();
+            if (!isAllowedClassName(className)) {
+                final String message = String.format("Class %s is not allowed for deserialization", className);
+                throw new HttpCacheEntrySerializationException(message);
             }
-            return super.resolveClass(desc);
+            return super.resolveClass(objectStreamClass);
         }
 
-        private boolean isProhibited(final ObjectStreamClass desc) {
-            for (final Pattern pattern : allowedClassPatterns) {
-                if (pattern.matcher(desc.getName()).matches()) {
-                    return false;
+        // visible for testing
+        static boolean isAllowedClassName(final String className) {
+            for (final Pattern allowedClassPattern : ALLOWED_CLASS_PATTERNS) {
+                if (allowedClassPattern.matcher(className).matches()) {
+                    return true;
                 }
             }
-            return true;
+            return false;
         }
+
     }
 
 }
diff --git a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
index fd48dd2..83c7681 100644
--- a/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
+++ b/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestHttpCacheEntrySerializers.java
@@ -26,6 +26,7 @@
  */
 package org.apache.http.impl.client.cache;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.io.ByteArrayInputStream;
@@ -38,7 +39,6 @@ import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.regex.Pattern;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.Header;
@@ -72,25 +72,182 @@ public class TestHttpCacheEntrySerializers {
         readWriteVerify(makeCacheEntryWithVariantMap());
     }
 
-    @Test(expected = HttpCacheEntrySerializationException.class)
-    public void throwExceptionIfUnsafeDeserialization() throws IOException {
-        impl.readFrom(new ByteArrayInputStream(serializeProhibitedObject()));
+    @Test
+    public void isAllowedClassNameStringTrue() {
+        assertIsAllowedClassNameTrue(String.class.getName());
     }
 
-    @Test(expected = HttpCacheEntrySerializationException.class)
-    public void allowClassesToBeDeserialized() throws IOException {
-        impl = new DefaultHttpCacheEntrySerializer(
-                Pattern.compile("javax.sql.rowset.BaseRowSet"),
-                Pattern.compile("com.sun.rowset.JdbcRowSetImpl"));
-        readVerify(serializeProhibitedObject());
+    @Test
+    public void isAllowedClassNameStringArrayTrue() {
+        assertIsAllowedClassNameTrue("[L" + String.class.getName());
+    }
+
+    @Test
+    public void isAllowedClassNameStringArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[L" + String.class.getName());
+    }
+
+    @Test
+    public void isAllowedClassNameDataTrue() {
+        assertIsAllowedClassNameTrue(Date.class.getName());
+    }
+
+    @Test
+    public void isAllowedClassNameStatusLineTrue() {
+        assertIsAllowedClassNameTrue(StatusLine.class.getName());
+    }
+
+    @Test
+    public void isAllowedClassNameResourceTrue() {
+        assertIsAllowedClassNameTrue(Resource.class.getName());
+    }
+
+    @Test
+    public void isAllowedClassNameByteArrayTrue() {
+        assertIsAllowedClassNameTrue("[B");
+    }
+
+    @Test
+    public void isAllowedClassNameByteArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[B");
+    }
+
+    @Test
+    public void isAllowedClassNameCharArrayTrue() {
+        assertIsAllowedClassNameTrue("[C");
+    }
+
+    @Test
+    public void isAllowedClassNameCharArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[C");
+    }
+
+    @Test
+    public void isAllowedClassNameDoubleArrayTrue() {
+        assertIsAllowedClassNameTrue("[D");
+    }
+
+    @Test
+    public void isAllowedClassNameDoubleArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[D");
+    }
+
+    @Test
+    public void isAllowedClassNameFloatArrayTrue() {
+        assertIsAllowedClassNameTrue("[F");
+    }
+
+    @Test
+    public void isAllowedClassNameFloatArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[F");
+    }
+
+    @Test
+    public void isAllowedClassNameIntArrayTrue() {
+        assertIsAllowedClassNameTrue("[I");
+    }
+
+    @Test
+    public void isAllowedClassNameIntArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[I");
+    }
+
+    @Test
+    public void isAllowedClassNameLongArrayTrue() {
+        assertIsAllowedClassNameTrue("[J");
+    }
+
+    @Test
+    public void isAllowedClassNameLongArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[J");
+    }
+
+    @Test
+    public void isAllowedClassNameShortArrayTrue() {
+        assertIsAllowedClassNameTrue("[S");
+    }
+
+    @Test
+    public void isAllowedClassNameShortArrayArrayTrue() {
+        assertIsAllowedClassNameTrue("[[S");
+    }
+
+    @Test
+    public void isAllowedClassNameCollectionsInvokerTransformerFalse() {
+        assertIsAllowedClassNameFalse("org.apache.commons.collections.functors.InvokerTransformer");
+    }
+
+    @Test
+    public void isAllowedClassNameCollections4InvokerTransformerFalse() {
+        assertIsAllowedClassNameFalse("org.apache.commons.collections4.functors.InvokerTransformer");
+    }
+
+    @Test
+    public void isAllowedClassNameCollectionsInstantiateTransformerFalse() {
+        assertIsAllowedClassNameFalse("org.apache.commons.collections.functors.InstantiateTransformer");
+    }
+
+    @Test
+    public void isAllowedClassNameCollections4InstantiateTransformerFalse() {
+        assertIsAllowedClassNameFalse("org.apache.commons.collections4.functors.InstantiateTransformer");
+    }
+
+    @Test
+    public void isAllowedClassNameGroovyConvertedClosureFalse() {
+        assertIsAllowedClassNameFalse("org.codehaus.groovy.runtime.ConvertedClosure");
+    }
+
+    @Test
+    public void isAllowedClassNameGroovyMethodClosureFalse() {
+        assertIsAllowedClassNameFalse("org.codehaus.groovy.runtime.MethodClosure");
+    }
+
+    @Test
+    public void isAllowedClassNameSpringObjectFactoryFalse() {
+        assertIsAllowedClassNameFalse("org.springframework.beans.factory.ObjectFactory");
+    }
+
+    @Test
+    public void isAllowedClassNameCalanTemplatesImplFalse() {
+        assertIsAllowedClassNameFalse("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
+    }
+
+    @Test
+    public void isAllowedClassNameCalanTemplatesImplArrayFalse() {
+        assertIsAllowedClassNameFalse("[Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
+    }
+
+    @Test
+    public void isAllowedClassNameJavaRmiRegistryFalse() {
+        assertIsAllowedClassNameFalse("java.rmi.registry.Registry");
+    }
+
+    @Test
+    public void isAllowedClassNameJavaRmiServerRemoteObjectInvocationHandlerFalse() {
+        assertIsAllowedClassNameFalse("java.rmi.server.RemoteObjectInvocationHandler");
+    }
+
+    @Test
+    public void isAllowedClassNameJavaxXmlTransformTemplatesFalse() {
+        assertIsAllowedClassNameFalse("javax.xml.transform.Templates");
+    }
+
+    @Test
+    public void isAllowedClassNameJavaxManagementMBeanServerInvocationHandlerFalse() {
+        assertIsAllowedClassNameFalse("javax.management.MBeanServerInvocationHandler");
+    }
+
+    private static void assertIsAllowedClassNameTrue(final String className) {
+        assertTrue(DefaultHttpCacheEntrySerializer.RestrictedObjectInputStream.isAllowedClassName(className));
+    }
+
+    private static void assertIsAllowedClassNameFalse(final String className) {
+        assertFalse(DefaultHttpCacheEntrySerializer.RestrictedObjectInputStream.isAllowedClassName(className));
     }
 
     @Test(expected = HttpCacheEntrySerializationException.class)
-    public void allowClassesToBeDeserializedByRegex() throws IOException {
-        impl = new DefaultHttpCacheEntrySerializer(
-                Pattern.compile(("^com\\.sun\\.rowset\\.(.*)")),
-                Pattern.compile("^javax\\.sql\\.rowset\\.BaseRowSet$"));
-        readVerify(serializeProhibitedObject());
+    public void throwExceptionIfUnsafeDeserialization() throws IOException {
+        impl.readFrom(new ByteArrayInputStream(serializeProhibitedObject()));
     }
 
     private byte[] serializeProhibitedObject() throws IOException {
@@ -105,11 +262,7 @@ public class TestHttpCacheEntrySerializers {
         return baos.toByteArray();
     }
 
-    private void readVerify(final byte[] data) throws IOException {
-        impl.readFrom(new ByteArrayInputStream(data));
-    }
-
-    public void readWriteVerify(final HttpCacheEntry writeEntry) throws IOException {
+    private void readWriteVerify(final HttpCacheEntry writeEntry) throws IOException {
         // write the entry
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
         impl.writeTo(writeEntry, out);