Skip to content

Commit 81c44e5

Browse files
committed
8344908: URLClassPath should not propagate IllegalArgumentException when finding resources in classpath URLs
Reviewed-by: alanb
1 parent ce9d543 commit 81c44e5

File tree

5 files changed

+225
-22
lines changed

5 files changed

+225
-22
lines changed

src/java.base/share/classes/jdk/internal/loader/URLClassPath.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,11 @@ private static class FileLoader extends Loader {
903903
private FileLoader(URL url) throws IOException {
904904
super(url);
905905
String path = url.getFile().replace('/', File.separatorChar);
906-
path = ParseUtil.decode(path);
906+
try {
907+
path = ParseUtil.decode(path);
908+
} catch (IllegalArgumentException iae) {
909+
throw new IOException(iae);
910+
}
907911
dir = (new File(path)).getCanonicalFile();
908912
@SuppressWarnings("deprecation")
909913
var _unused = normalizedBase = new URL(getBaseURL(), ".");

src/java.base/share/classes/sun/net/www/ParseUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ private static byte unescape(String s, int i) {
171171
* Returns a new String constructed from the specified String by replacing
172172
* the URL escape sequences and UTF8 encoding with the characters they
173173
* represent.
174+
* @throws IllegalArgumentException if {@code s} could not be decoded
174175
*/
175176
public static String decode(String s) {
176177
int n = s.length();

src/java.base/unix/classes/jdk/internal/loader/FileURLMapper.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,8 +25,10 @@
2525

2626
package jdk.internal.loader;
2727

28+
import java.io.IOException;
2829
import java.net.URL;
2930
import java.io.File;
31+
3032
import sun.net.www.ParseUtil;
3133

3234
/**
@@ -40,41 +42,44 @@
4042
* @author Michael McMahon
4143
*/
4244

43-
public class FileURLMapper {
45+
final class FileURLMapper {
4446

45-
URL url;
46-
String path;
47+
private final URL url;
48+
private String path;
4749

48-
public FileURLMapper (URL url) {
50+
FileURLMapper(URL url) {
4951
this.url = url;
5052
}
5153

5254
/**
5355
* @return the platform specific path corresponding to the URL
5456
* so long as the URL does not contain a hostname in the authority field.
5557
*/
56-
57-
public String getPath () {
58+
String getPath() throws IOException {
5859
if (path != null) {
5960
return path;
6061
}
6162
String host = url.getHost();
6263
if (host == null || host.isEmpty() || "localhost".equalsIgnoreCase(host)) {
6364
path = url.getFile();
64-
path = ParseUtil.decode(path);
65+
try {
66+
path = ParseUtil.decode(path);
67+
} catch (IllegalArgumentException iae) {
68+
throw new IOException(iae);
69+
}
6570
}
6671
return path;
6772
}
6873

6974
/**
7075
* Checks whether the file identified by the URL exists.
7176
*/
72-
public boolean exists () {
73-
String s = getPath ();
77+
boolean exists() throws IOException {
78+
String s = getPath();
7479
if (s == null) {
7580
return false;
7681
} else {
77-
File f = new File (s);
82+
File f = new File(s);
7883
return f.exists();
7984
}
8085
}

src/java.base/windows/classes/jdk/internal/loader/FileURLMapper.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,8 +25,10 @@
2525

2626
package jdk.internal.loader;
2727

28+
import java.io.IOException;
2829
import java.net.URL;
2930
import java.io.File;
31+
3032
import sun.net.www.ParseUtil;
3133

3234
/**
@@ -36,21 +38,20 @@
3638
* @author Michael McMahon
3739
*/
3840

39-
public class FileURLMapper {
41+
final class FileURLMapper {
4042

41-
URL url;
42-
String file;
43+
private final URL url;
44+
private String file;
4345

44-
public FileURLMapper (URL url) {
46+
FileURLMapper (URL url) {
4547
this.url = url;
4648
}
4749

4850
/**
4951
* @return the platform specific path corresponding to the URL, and in particular
5052
* returns a UNC when the authority contains a hostname
5153
*/
52-
53-
public String getPath () {
54+
String getPath() throws IOException {
5455
if (file != null) {
5556
return file;
5657
}
@@ -63,13 +64,17 @@ public String getPath () {
6364
return file;
6465
}
6566
String path = url.getFile().replace('/', '\\');
66-
file = ParseUtil.decode(path);
67+
try {
68+
file = ParseUtil.decode(path);
69+
} catch (IllegalArgumentException iae) {
70+
throw new IOException(iae);
71+
}
6772
return file;
6873
}
6974

70-
public boolean exists() {
75+
boolean exists() throws IOException {
7176
String path = getPath();
72-
File f = new File (path);
77+
File f = new File(path);
7378
return f.exists();
7479
}
7580
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.io.OutputStream;
25+
import java.net.URL;
26+
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.util.Arrays;
29+
import java.util.Enumeration;
30+
import java.util.jar.JarEntry;
31+
import java.util.jar.JarOutputStream;
32+
import java.util.jar.Manifest;
33+
34+
import jdk.internal.loader.Resource;
35+
import jdk.internal.loader.URLClassPath;
36+
import org.junit.jupiter.api.BeforeAll;
37+
import org.junit.jupiter.api.Test;
38+
import static java.nio.charset.StandardCharsets.US_ASCII;
39+
import static org.junit.jupiter.api.Assertions.assertEquals;
40+
import static org.junit.jupiter.api.Assertions.assertNotNull;
41+
import static org.junit.jupiter.api.Assumptions.abort;
42+
43+
/*
44+
* @test
45+
* @bug 8344908
46+
* @summary verify that when locating resources, the URLClassPath can function properly
47+
* without throwing unexpected exceptions when any URL in the classpath is unusable
48+
* @modules java.base/jdk.internal.loader
49+
* @run junit ClassPathUnusableURLs
50+
*/
51+
public class ClassPathUnusableURLs {
52+
53+
private static final Path SCRATCH_DIR = Path.of(".").normalize();
54+
private static final String RESOURCE_NAME = "foo.txt";
55+
private static final String SMILEY_EMOJI = "\uD83D\uDE00";
56+
57+
private static Path ASCII_DIR;
58+
private static Path EMOJI_DIR;
59+
private static Path JAR_FILE_IN_EMOJI_DIR;
60+
private static int NUM_EXPECTED_LOCATED_RESOURCES;
61+
62+
63+
@BeforeAll
64+
static void beforeAll() throws Exception {
65+
try {
66+
EMOJI_DIR = Files.createTempDirectory(SCRATCH_DIR, SMILEY_EMOJI);
67+
} catch (IllegalArgumentException iae) {
68+
iae.printStackTrace(); // for debug purpose
69+
// if we can't create a directory with an emoji in its path name,
70+
// then skip the entire test
71+
abort("Skipping test since emoji directory couldn't be created: " + iae);
72+
}
73+
// successful creation of the dir, continue with the test
74+
Files.createFile(EMOJI_DIR.resolve(RESOURCE_NAME));
75+
76+
ASCII_DIR = Files.createTempDirectory(SCRATCH_DIR, "test-urlclasspath");
77+
Files.createFile(ASCII_DIR.resolve(RESOURCE_NAME));
78+
79+
// create a jar file containing the resource
80+
JAR_FILE_IN_EMOJI_DIR = Files.createTempDirectory(SCRATCH_DIR, SMILEY_EMOJI)
81+
.resolve("foo.jar");
82+
final Manifest manifest = new Manifest();
83+
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
84+
try (OutputStream fos = Files.newOutputStream(JAR_FILE_IN_EMOJI_DIR);
85+
JarOutputStream jos = new JarOutputStream(fos, manifest)) {
86+
87+
final JarEntry jarEntry = new JarEntry(RESOURCE_NAME);
88+
jos.putNextEntry(jarEntry);
89+
jos.write("hello".getBytes(US_ASCII));
90+
jos.closeEntry();
91+
}
92+
// Even if the resource is present in more than one classpath element,
93+
// we expect it to be found by the URLClassPath only in the path which has just ascii
94+
// characters. URLClassPath currently doesn't have the ability to serve resources
95+
// from paths containing emoji character(s).
96+
NUM_EXPECTED_LOCATED_RESOURCES = 1;
97+
}
98+
99+
/**
100+
* Constructs a URLClassPath and then exercises the URLClassPath.findResource()
101+
* and URLClassPath.findResources() methods and expects them to return the expected
102+
* resources.
103+
*/
104+
@Test
105+
void testFindResource() {
106+
// start an empty URL classpath
107+
final URLClassPath urlc = new URLClassPath(new URL[0]);
108+
final String[] classpathElements = getClassPathElements();
109+
try {
110+
// use addFile() to construct classpath
111+
for (final String path : classpathElements) {
112+
urlc.addFile(path);
113+
}
114+
// findResource()
115+
assertNotNull(urlc.findResource(RESOURCE_NAME), "findResource() failed to locate"
116+
+ " resource: " + RESOURCE_NAME + " in classpath: "
117+
+ Arrays.toString(classpathElements));
118+
// findResources()
119+
final Enumeration<URL> locatedResources = urlc.findResources(RESOURCE_NAME);
120+
assertNotNull(locatedResources, "findResources() failed to"
121+
+ " locate resource: " + RESOURCE_NAME + " in classpath: "
122+
+ Arrays.toString(classpathElements));
123+
int numFound = 0;
124+
while (locatedResources.hasMoreElements()) {
125+
System.out.println("located " + locatedResources.nextElement()
126+
+ " for resource " + RESOURCE_NAME);
127+
numFound++;
128+
}
129+
assertEquals(NUM_EXPECTED_LOCATED_RESOURCES, numFound,
130+
"unexpected number of resources located for " + RESOURCE_NAME);
131+
} finally {
132+
urlc.closeLoaders();
133+
}
134+
}
135+
136+
/**
137+
* Constructs a URLClassPath and then exercises the URLClassPath.getResource()
138+
* and URLClassPath.getResources() methods and expects them to return the expected
139+
* resources.
140+
*/
141+
@Test
142+
void testGetResource() {
143+
// start an empty URL classpath
144+
final URLClassPath urlc = new URLClassPath(new URL[0]);
145+
final String[] classpathElements = getClassPathElements();
146+
try {
147+
// use addFile() to construct classpath
148+
for (final String path : classpathElements) {
149+
urlc.addFile(path);
150+
}
151+
// getResource()
152+
assertNotNull(urlc.getResource(RESOURCE_NAME), "getResource() failed to locate"
153+
+ " resource: " + RESOURCE_NAME + " in classpath: "
154+
+ Arrays.toString(classpathElements));
155+
// getResources()
156+
final Enumeration<Resource> locatedResources = urlc.getResources(RESOURCE_NAME);
157+
assertNotNull(locatedResources, "getResources() failed to"
158+
+ " locate resource: " + RESOURCE_NAME + " in classpath: "
159+
+ Arrays.toString(classpathElements));
160+
int numFound = 0;
161+
while (locatedResources.hasMoreElements()) {
162+
System.out.println("located " + locatedResources.nextElement().getURL()
163+
+ " for resource " + RESOURCE_NAME);
164+
numFound++;
165+
}
166+
assertEquals(NUM_EXPECTED_LOCATED_RESOURCES, numFound,
167+
"unexpected number of resources located for " + RESOURCE_NAME);
168+
} finally {
169+
urlc.closeLoaders();
170+
}
171+
}
172+
173+
private static String[] getClassPathElements() {
174+
// Maintain the order - in context of this test, paths with emojis
175+
// or those which can't serve the resource should come before the
176+
// path that can serve the resource.
177+
return new String[]{
178+
// non-existent path
179+
ASCII_DIR.resolve("non-existent").toString(),
180+
// existing emoji dir
181+
EMOJI_DIR.toString(),
182+
// existing jar file in a emoji dir
183+
JAR_FILE_IN_EMOJI_DIR.toString(),
184+
// existing ascii dir
185+
ASCII_DIR.toString()
186+
};
187+
}
188+
}

0 commit comments

Comments
 (0)