8344908: URLClassPath should not propagate IllegalArgumentException when finding resources in classpath URLs

Reviewed-by: alanb
This commit is contained in:
Jaikiran Pai 2024-11-28 07:54:00 +00:00
parent ce9d543eb1
commit 81c44e5eb4
5 changed files with 225 additions and 22 deletions

View File

@ -903,7 +903,11 @@ public class URLClassPath {
private FileLoader(URL url) throws IOException {
super(url);
String path = url.getFile().replace('/', File.separatorChar);
try {
path = ParseUtil.decode(path);
} catch (IllegalArgumentException iae) {
throw new IOException(iae);
}
dir = (new File(path)).getCanonicalFile();
@SuppressWarnings("deprecation")
var _unused = normalizedBase = new URL(getBaseURL(), ".");

View File

@ -171,6 +171,7 @@ public final class ParseUtil {
* Returns a new String constructed from the specified String by replacing
* the URL escape sequences and UTF8 encoding with the characters they
* represent.
* @throws IllegalArgumentException if {@code s} could not be decoded
*/
public static String decode(String s) {
int n = s.length();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,8 +25,10 @@
package jdk.internal.loader;
import java.io.IOException;
import java.net.URL;
import java.io.File;
import sun.net.www.ParseUtil;
/**
@ -40,12 +42,12 @@ import sun.net.www.ParseUtil;
* @author Michael McMahon
*/
public class FileURLMapper {
final class FileURLMapper {
URL url;
String path;
private final URL url;
private String path;
public FileURLMapper (URL url) {
FileURLMapper(URL url) {
this.url = url;
}
@ -53,15 +55,18 @@ public class FileURLMapper {
* @return the platform specific path corresponding to the URL
* so long as the URL does not contain a hostname in the authority field.
*/
public String getPath () {
String getPath() throws IOException {
if (path != null) {
return path;
}
String host = url.getHost();
if (host == null || host.isEmpty() || "localhost".equalsIgnoreCase(host)) {
path = url.getFile();
try {
path = ParseUtil.decode(path);
} catch (IllegalArgumentException iae) {
throw new IOException(iae);
}
}
return path;
}
@ -69,7 +74,7 @@ public class FileURLMapper {
/**
* Checks whether the file identified by the URL exists.
*/
public boolean exists () {
boolean exists() throws IOException {
String s = getPath();
if (s == null) {
return false;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,8 +25,10 @@
package jdk.internal.loader;
import java.io.IOException;
import java.net.URL;
import java.io.File;
import sun.net.www.ParseUtil;
/**
@ -36,12 +38,12 @@ import sun.net.www.ParseUtil;
* @author Michael McMahon
*/
public class FileURLMapper {
final class FileURLMapper {
URL url;
String file;
private final URL url;
private String file;
public FileURLMapper (URL url) {
FileURLMapper (URL url) {
this.url = url;
}
@ -49,8 +51,7 @@ public class FileURLMapper {
* @return the platform specific path corresponding to the URL, and in particular
* returns a UNC when the authority contains a hostname
*/
public String getPath () {
String getPath() throws IOException {
if (file != null) {
return file;
}
@ -63,11 +64,15 @@ public class FileURLMapper {
return file;
}
String path = url.getFile().replace('/', '\\');
try {
file = ParseUtil.decode(path);
} catch (IllegalArgumentException iae) {
throw new IOException(iae);
}
return file;
}
public boolean exists() {
boolean exists() throws IOException {
String path = getPath();
File f = new File(path);
return f.exists();

View File

@ -0,0 +1,188 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import jdk.internal.loader.Resource;
import jdk.internal.loader.URLClassPath;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assumptions.abort;
/*
* @test
* @bug 8344908
* @summary verify that when locating resources, the URLClassPath can function properly
* without throwing unexpected exceptions when any URL in the classpath is unusable
* @modules java.base/jdk.internal.loader
* @run junit ClassPathUnusableURLs
*/
public class ClassPathUnusableURLs {
private static final Path SCRATCH_DIR = Path.of(".").normalize();
private static final String RESOURCE_NAME = "foo.txt";
private static final String SMILEY_EMOJI = "\uD83D\uDE00";
private static Path ASCII_DIR;
private static Path EMOJI_DIR;
private static Path JAR_FILE_IN_EMOJI_DIR;
private static int NUM_EXPECTED_LOCATED_RESOURCES;
@BeforeAll
static void beforeAll() throws Exception {
try {
EMOJI_DIR = Files.createTempDirectory(SCRATCH_DIR, SMILEY_EMOJI);
} catch (IllegalArgumentException iae) {
iae.printStackTrace(); // for debug purpose
// if we can't create a directory with an emoji in its path name,
// then skip the entire test
abort("Skipping test since emoji directory couldn't be created: " + iae);
}
// successful creation of the dir, continue with the test
Files.createFile(EMOJI_DIR.resolve(RESOURCE_NAME));
ASCII_DIR = Files.createTempDirectory(SCRATCH_DIR, "test-urlclasspath");
Files.createFile(ASCII_DIR.resolve(RESOURCE_NAME));
// create a jar file containing the resource
JAR_FILE_IN_EMOJI_DIR = Files.createTempDirectory(SCRATCH_DIR, SMILEY_EMOJI)
.resolve("foo.jar");
final Manifest manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
try (OutputStream fos = Files.newOutputStream(JAR_FILE_IN_EMOJI_DIR);
JarOutputStream jos = new JarOutputStream(fos, manifest)) {
final JarEntry jarEntry = new JarEntry(RESOURCE_NAME);
jos.putNextEntry(jarEntry);
jos.write("hello".getBytes(US_ASCII));
jos.closeEntry();
}
// Even if the resource is present in more than one classpath element,
// we expect it to be found by the URLClassPath only in the path which has just ascii
// characters. URLClassPath currently doesn't have the ability to serve resources
// from paths containing emoji character(s).
NUM_EXPECTED_LOCATED_RESOURCES = 1;
}
/**
* Constructs a URLClassPath and then exercises the URLClassPath.findResource()
* and URLClassPath.findResources() methods and expects them to return the expected
* resources.
*/
@Test
void testFindResource() {
// start an empty URL classpath
final URLClassPath urlc = new URLClassPath(new URL[0]);
final String[] classpathElements = getClassPathElements();
try {
// use addFile() to construct classpath
for (final String path : classpathElements) {
urlc.addFile(path);
}
// findResource()
assertNotNull(urlc.findResource(RESOURCE_NAME), "findResource() failed to locate"
+ " resource: " + RESOURCE_NAME + " in classpath: "
+ Arrays.toString(classpathElements));
// findResources()
final Enumeration<URL> locatedResources = urlc.findResources(RESOURCE_NAME);
assertNotNull(locatedResources, "findResources() failed to"
+ " locate resource: " + RESOURCE_NAME + " in classpath: "
+ Arrays.toString(classpathElements));
int numFound = 0;
while (locatedResources.hasMoreElements()) {
System.out.println("located " + locatedResources.nextElement()
+ " for resource " + RESOURCE_NAME);
numFound++;
}
assertEquals(NUM_EXPECTED_LOCATED_RESOURCES, numFound,
"unexpected number of resources located for " + RESOURCE_NAME);
} finally {
urlc.closeLoaders();
}
}
/**
* Constructs a URLClassPath and then exercises the URLClassPath.getResource()
* and URLClassPath.getResources() methods and expects them to return the expected
* resources.
*/
@Test
void testGetResource() {
// start an empty URL classpath
final URLClassPath urlc = new URLClassPath(new URL[0]);
final String[] classpathElements = getClassPathElements();
try {
// use addFile() to construct classpath
for (final String path : classpathElements) {
urlc.addFile(path);
}
// getResource()
assertNotNull(urlc.getResource(RESOURCE_NAME), "getResource() failed to locate"
+ " resource: " + RESOURCE_NAME + " in classpath: "
+ Arrays.toString(classpathElements));
// getResources()
final Enumeration<Resource> locatedResources = urlc.getResources(RESOURCE_NAME);
assertNotNull(locatedResources, "getResources() failed to"
+ " locate resource: " + RESOURCE_NAME + " in classpath: "
+ Arrays.toString(classpathElements));
int numFound = 0;
while (locatedResources.hasMoreElements()) {
System.out.println("located " + locatedResources.nextElement().getURL()
+ " for resource " + RESOURCE_NAME);
numFound++;
}
assertEquals(NUM_EXPECTED_LOCATED_RESOURCES, numFound,
"unexpected number of resources located for " + RESOURCE_NAME);
} finally {
urlc.closeLoaders();
}
}
private static String[] getClassPathElements() {
// Maintain the order - in context of this test, paths with emojis
// or those which can't serve the resource should come before the
// path that can serve the resource.
return new String[]{
// non-existent path
ASCII_DIR.resolve("non-existent").toString(),
// existing emoji dir
EMOJI_DIR.toString(),
// existing jar file in a emoji dir
JAR_FILE_IN_EMOJI_DIR.toString(),
// existing ascii dir
ASCII_DIR.toString()
};
}
}