8344908: URLClassPath should not propagate IllegalArgumentException when finding resources in classpath URLs
Reviewed-by: alanb
This commit is contained in:
parent
ce9d543eb1
commit
81c44e5eb4
@ -903,7 +903,11 @@ public class URLClassPath {
|
|||||||
private FileLoader(URL url) throws IOException {
|
private FileLoader(URL url) throws IOException {
|
||||||
super(url);
|
super(url);
|
||||||
String path = url.getFile().replace('/', File.separatorChar);
|
String path = url.getFile().replace('/', File.separatorChar);
|
||||||
path = ParseUtil.decode(path);
|
try {
|
||||||
|
path = ParseUtil.decode(path);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IOException(iae);
|
||||||
|
}
|
||||||
dir = (new File(path)).getCanonicalFile();
|
dir = (new File(path)).getCanonicalFile();
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
var _unused = normalizedBase = new URL(getBaseURL(), ".");
|
var _unused = normalizedBase = new URL(getBaseURL(), ".");
|
||||||
|
@ -171,6 +171,7 @@ public final class ParseUtil {
|
|||||||
* Returns a new String constructed from the specified String by replacing
|
* Returns a new String constructed from the specified String by replacing
|
||||||
* the URL escape sequences and UTF8 encoding with the characters they
|
* the URL escape sequences and UTF8 encoding with the characters they
|
||||||
* represent.
|
* represent.
|
||||||
|
* @throws IllegalArgumentException if {@code s} could not be decoded
|
||||||
*/
|
*/
|
||||||
public static String decode(String s) {
|
public static String decode(String s) {
|
||||||
int n = s.length();
|
int n = s.length();
|
||||||
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
package jdk.internal.loader;
|
package jdk.internal.loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import sun.net.www.ParseUtil;
|
import sun.net.www.ParseUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,12 +42,12 @@ import sun.net.www.ParseUtil;
|
|||||||
* @author Michael McMahon
|
* @author Michael McMahon
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class FileURLMapper {
|
final class FileURLMapper {
|
||||||
|
|
||||||
URL url;
|
private final URL url;
|
||||||
String path;
|
private String path;
|
||||||
|
|
||||||
public FileURLMapper (URL url) {
|
FileURLMapper(URL url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +55,18 @@ public class FileURLMapper {
|
|||||||
* @return the platform specific path corresponding to the URL
|
* @return the platform specific path corresponding to the URL
|
||||||
* so long as the URL does not contain a hostname in the authority field.
|
* so long as the URL does not contain a hostname in the authority field.
|
||||||
*/
|
*/
|
||||||
|
String getPath() throws IOException {
|
||||||
public String getPath () {
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
String host = url.getHost();
|
String host = url.getHost();
|
||||||
if (host == null || host.isEmpty() || "localhost".equalsIgnoreCase(host)) {
|
if (host == null || host.isEmpty() || "localhost".equalsIgnoreCase(host)) {
|
||||||
path = url.getFile();
|
path = url.getFile();
|
||||||
path = ParseUtil.decode(path);
|
try {
|
||||||
|
path = ParseUtil.decode(path);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IOException(iae);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
@ -69,12 +74,12 @@ public class FileURLMapper {
|
|||||||
/**
|
/**
|
||||||
* Checks whether the file identified by the URL exists.
|
* Checks whether the file identified by the URL exists.
|
||||||
*/
|
*/
|
||||||
public boolean exists () {
|
boolean exists() throws IOException {
|
||||||
String s = getPath ();
|
String s = getPath();
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
File f = new File (s);
|
File f = new File(s);
|
||||||
return f.exists();
|
return f.exists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
package jdk.internal.loader;
|
package jdk.internal.loader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import sun.net.www.ParseUtil;
|
import sun.net.www.ParseUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,12 +38,12 @@ import sun.net.www.ParseUtil;
|
|||||||
* @author Michael McMahon
|
* @author Michael McMahon
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class FileURLMapper {
|
final class FileURLMapper {
|
||||||
|
|
||||||
URL url;
|
private final URL url;
|
||||||
String file;
|
private String file;
|
||||||
|
|
||||||
public FileURLMapper (URL url) {
|
FileURLMapper (URL url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +51,7 @@ public class FileURLMapper {
|
|||||||
* @return the platform specific path corresponding to the URL, and in particular
|
* @return the platform specific path corresponding to the URL, and in particular
|
||||||
* returns a UNC when the authority contains a hostname
|
* returns a UNC when the authority contains a hostname
|
||||||
*/
|
*/
|
||||||
|
String getPath() throws IOException {
|
||||||
public String getPath () {
|
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
@ -63,13 +64,17 @@ public class FileURLMapper {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
String path = url.getFile().replace('/', '\\');
|
String path = url.getFile().replace('/', '\\');
|
||||||
file = ParseUtil.decode(path);
|
try {
|
||||||
|
file = ParseUtil.decode(path);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
throw new IOException(iae);
|
||||||
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean exists() {
|
boolean exists() throws IOException {
|
||||||
String path = getPath();
|
String path = getPath();
|
||||||
File f = new File (path);
|
File f = new File(path);
|
||||||
return f.exists();
|
return f.exists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user