Encode entry name before creating URL

During retrieving a resource from the class path a URL is
constructed from the resource name. If the resource name
contains characters that are special in the file part of a URL,
e.g. # or ? then they need to be encoded (e.g. into %23 and %3f
respectively) before passing to the URL in order to ensure that
the name survives intact through to the JarURLConnection which
decodes the URL to extract the file part which it uses to scan
the JAR.

This ensures that the entry/resource name is encoded properly
and adds tests to verify that for resources whose names contain
either # or ?.

Bug: 26137833
Change-Id: I8f31c35e42c0070a0ee78e0cd58b67ebd001fffe
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ClassTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ClassTest.java
index 379dad2..95dad9a 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ClassTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ClassTest.java
@@ -40,6 +40,7 @@
 
     // Relative resource paths.
     private static final String SHARP_RESOURCE_RELATIVE_NAME = "test#.properties";
+    private static final String QUERY_RESOURCE_RELATIVE_NAME = "test?.properties";
     private static final String RESOURCE_RELATIVE_NAME = "test.properties";
 
     // Absolute resource paths.
@@ -47,6 +48,8 @@
             ClassTest.class.getPackage().getName().replace('.', '/');
     public static final String SHARP_RESOURCE_ABS_NAME =
             ABS_PATH + "/" + SHARP_RESOURCE_RELATIVE_NAME;
+    public static final String QUERY_RESOURCE_ABS_NAME =
+            ABS_PATH + "/" + QUERY_RESOURCE_RELATIVE_NAME;
     public static final String RESOURCE_ABS_NAME = ABS_PATH + "/" + RESOURCE_RELATIVE_NAME;
 
     public static class TestClass {
@@ -570,13 +573,52 @@
         assertResourceExists("/" + SHARP_RESOURCE_ABS_NAME);
         assertResourceExists(SHARP_RESOURCE_RELATIVE_NAME);
 
-
         InputStream in =
                 this.getClass().getClassLoader().getResourceAsStream(SHARP_RESOURCE_ABS_NAME);
         assertNotNull(in);
         in.close();
     }
 
+    public void test_getResource_withSharpChar() throws Exception {
+        // Class.getResourceAsStream() requires a leading "/" for absolute paths.
+        assertNull(getClass().getResource(SHARP_RESOURCE_ABS_NAME));
+        URL absoluteURL = getClass().getResource("/" + SHARP_RESOURCE_ABS_NAME);
+
+        // Make sure the name has been encoded.
+        assertEquals(ABS_PATH + "/test%23.properties",
+                absoluteURL.getFile().replaceAll("^.*!/", ""));
+
+        // Make sure accessing it via an absolute and relative path produces the same result.
+        URL relativeURL = getClass().getResource(SHARP_RESOURCE_RELATIVE_NAME);
+        assertEquals(absoluteURL, relativeURL);
+    }
+
+    public void test_getResourceAsStream_withQueryChar() throws Exception {
+        // Class.getResourceAsStream() requires a leading "/" for absolute paths.
+        assertNull(getClass().getResourceAsStream(QUERY_RESOURCE_ABS_NAME));
+        assertResourceExists("/" + QUERY_RESOURCE_ABS_NAME);
+        assertResourceExists(QUERY_RESOURCE_RELATIVE_NAME);
+
+        InputStream in =
+                this.getClass().getClassLoader().getResourceAsStream(QUERY_RESOURCE_ABS_NAME);
+        assertNotNull(in);
+        in.close();
+    }
+
+    public void test_getResource_withQueryChar() throws Exception {
+        // Class.getResourceAsStream() requires a leading "/" for absolute paths.
+        assertNull(getClass().getResource(QUERY_RESOURCE_ABS_NAME));
+        URL absoluteURL = getClass().getResource("/" + QUERY_RESOURCE_ABS_NAME);
+
+        // Make sure the name has been encoded.
+        assertEquals(ABS_PATH + "/test%3f.properties",
+                absoluteURL.getFile().replaceAll("^.*!/", ""));
+
+        // Make sure accessing it via an absolute and relative path produces the same result.
+        URL relativeURL = getClass().getResource(QUERY_RESOURCE_RELATIVE_NAME);
+        assertEquals(absoluteURL, relativeURL);
+    }
+
     public void test_getResourceAsStream() throws Exception {
         // Class.getResourceAsStream() requires a leading "/" for absolute paths.
         assertNull(getClass().getResourceAsStream(RESOURCE_ABS_NAME));
diff --git a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
index 3db2a8d..9f8a844 100644
--- a/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
+++ b/luni/src/main/java/libcore/io/ClassPathURLStreamHandler.java
@@ -25,9 +25,11 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.URLEncoder;
 import java.net.URLStreamHandler;
 import java.util.jar.JarFile;
 import java.util.zip.ZipEntry;
+import sun.net.www.ParseUtil;
 import sun.net.www.protocol.jar.Handler;
 
 /**
@@ -57,9 +59,10 @@
   public URL getEntryUrlOrNull(String entryName) {
     if (findEntryWithDirectoryFallback(jarFile, entryName) != null) {
       try {
-        // We rely on the URL/the stream handler to deal with any url encoding necessary here, and
-        // we assume it is completely reversible.
-        return new URL("jar", null, -1, fileUri + "!/" + entryName, this);
+        // Encode the path to ensure that any special characters like # survive their trip through
+        // the URL. Entry names must use / as the path separator.
+        String encodedName = ParseUtil.encodePath(entryName, false);
+        return new URL("jar", null, -1, fileUri + "!/" + encodedName, this);
       } catch (MalformedURLException e) {
         throw new RuntimeException("Invalid entry name", e);
       }
diff --git a/luni/src/test/resources/org/apache/harmony/tests/java/lang/test?.properties b/luni/src/test/resources/org/apache/harmony/tests/java/lang/test?.properties
new file mode 100644
index 0000000..4a8dfd8
--- /dev/null
+++ b/luni/src/test/resources/org/apache/harmony/tests/java/lang/test?.properties
@@ -0,0 +1 @@
+This is a resource file with a ? in the name
\ No newline at end of file