diff --git a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java index 4e67c962a46e1..c28e4de66f2d3 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -31,113 +31,85 @@ import java.net.MalformedURLException; import java.net.URL; -import jdk.internal.jimage.ImageLocation; import jdk.internal.jimage.ImageReader; +import jdk.internal.jimage.ImageReader.Node; import jdk.internal.jimage.ImageReaderFactory; -import jdk.internal.loader.Resource; import sun.net.www.ParseUtil; import sun.net.www.URLConnection; /** * URLConnection implementation that can be used to connect to resources - * contained in the runtime image. + * contained in the runtime image. See section "New URI scheme for naming stored + * modules, classes, and resources" in + * JEP 220. */ public class JavaRuntimeURLConnection extends URLConnection { - // ImageReader to access resources in jimage - private static final ImageReader reader = ImageReaderFactory.getImageReader(); + // ImageReader to access resources in jimage. + private static final ImageReader READER = ImageReaderFactory.getImageReader(); - // the module and resource name in the URL + // The module and resource name in the URL (i.e. "jrt:/[$MODULE[/$PATH]]"). + // + // The module name is not percent-decoded, and can be empty. private final String module; - private final String name; + // The resource name permits UTF-8 percent encoding of non-ASCII characters. + private final String path; - // the Resource when connected - private volatile Resource resource; + // The resource node (when connected). + private volatile Node resourceNode; JavaRuntimeURLConnection(URL url) throws IOException { super(url); - String path = url.getPath(); - if (path.isEmpty() || path.charAt(0) != '/') + String urlPath = url.getPath(); + if (urlPath.isEmpty() || urlPath.charAt(0) != '/') { throw new MalformedURLException(url + " missing path or /"); - if (path.length() == 1) { - this.module = null; - this.name = null; + } + int pathSep = urlPath.indexOf('/', 1); + if (pathSep == -1) { + // No trailing resource path. This can never "connect" or return a + // resource (see JEP 220 for details). + this.module = urlPath.substring(1); + this.path = null; } else { - int pos = path.indexOf('/', 1); - if (pos == -1) { - this.module = path.substring(1); - this.name = null; - } else { - this.module = path.substring(1, pos); - this.name = ParseUtil.decode(path.substring(pos+1)); - } + this.module = urlPath.substring(1, pathSep); + this.path = percentDecode(urlPath.substring(pathSep + 1)); } } /** - * Finds a resource in a module, returning {@code null} if the resource - * is not found. + * Finds and caches the resource node associated with this URL and marks the + * connection as "connected". */ - private static Resource findResource(String module, String name) { - if (reader != null) { - URL url = toJrtURL(module, name); - ImageLocation location = reader.findLocation(module, name); - if (location != null) { - return new Resource() { - @Override - public String getName() { - return name; - } - @Override - public URL getURL() { - return url; - } - @Override - public URL getCodeSourceURL() { - return toJrtURL(module); - } - @Override - public InputStream getInputStream() throws IOException { - byte[] resource = reader.getResource(location); - return new ByteArrayInputStream(resource); - } - @Override - public int getContentLength() { - long size = location.getUncompressedSize(); - return (size > Integer.MAX_VALUE) ? -1 : (int) size; - } - }; + private synchronized Node getResourceNode() throws IOException { + if (resourceNode == null) { + if (path == null) { + throw new IOException("cannot connect to jrt:/" + module); + } + Node node = READER.findNode("/modules/" + module + "/" + path); + if (node == null || !node.isResource()) { + throw new IOException(module + "/" + path + " not found"); } + this.resourceNode = node; + super.connected = true; } - return null; + return resourceNode; } @Override - public synchronized void connect() throws IOException { - if (!connected) { - if (name == null) { - String s = (module == null) ? "" : module; - throw new IOException("cannot connect to jrt:/" + s); - } - resource = findResource(module, name); - if (resource == null) - throw new IOException(module + "/" + name + " not found"); - connected = true; - } + public void connect() throws IOException { + getResourceNode(); } @Override public InputStream getInputStream() throws IOException { - connect(); - return resource.getInputStream(); + return new ByteArrayInputStream(READER.getResource(getResourceNode())); } @Override public long getContentLengthLong() { try { - connect(); - return resource.getContentLength(); + return getResourceNode().size(); } catch (IOException ioe) { return -1L; } @@ -149,27 +121,16 @@ public int getContentLength() { return len > Integer.MAX_VALUE ? -1 : (int)len; } - /** - * Returns a jrt URL for the given module and resource name. - */ - @SuppressWarnings("deprecation") - private static URL toJrtURL(String module, String name) { - try { - return new URL("jrt:/" + module + "/" + name); - } catch (MalformedURLException e) { - throw new InternalError(e); + // Perform percent decoding of the resource name/path from the URL. + private static String percentDecode(String path) throws MalformedURLException { + if (path.indexOf('%') == -1) { + // Nothing to decode (overwhelmingly common case). + return path; } - } - - /** - * Returns a jrt URL for the given module. - */ - @SuppressWarnings("deprecation") - private static URL toJrtURL(String module) { try { - return new URL("jrt:/" + module); - } catch (MalformedURLException e) { - throw new InternalError(e); + return ParseUtil.decode(path); + } catch (IllegalArgumentException e) { + throw new MalformedURLException(e.getMessage()); } } } diff --git a/test/jdk/sun/net/www/protocol/jrt/Basic.java b/test/jdk/sun/net/www/protocol/jrt/Basic.java index 0f0492c9b39dc..3778912c375e5 100644 --- a/test/jdk/sun/net/www/protocol/jrt/Basic.java +++ b/test/jdk/sun/net/www/protocol/jrt/Basic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, 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 @@ -28,7 +28,6 @@ */ import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.net.URLConnection; @@ -41,8 +40,28 @@ public class Basic { @DataProvider(name = "urls") public Object[][] urls() { Object[][] data = { - { "jrt:/java.base/java/lang/Object.class", true }, - { "jrt:/java.desktop/java/lang/Object.class", false }, + {"jrt:/java.base/java/lang/Object.class", true}, + // Valid resource with and without percent-encoding. + {"jrt:/java.base/java/lang/Runtime$Version.class", true}, + {"jrt:/java.base/java%2Flang%2FRuntime%24Version.class", true}, + // Unnecessary percent encoding (just Object again). + {"jrt:/java.base/%6a%61%76%61%2f%6c%61%6e%67%2f%4f%62%6a%65%63%74%2e%63%6c%61%73%73", true}, + // Query parameters and fragments are silently ignored. + {"jrt:/java.base/java/lang/Object.class?yes=no", true}, + {"jrt:/java.base/java/lang/Object.class#anchor", true}, + + // Missing resource (no such class). + {"jrt:/java.base/java/lang/NoSuchClass.class", false}, + // Missing resource (wrong module). + {"jrt:/java.desktop/java/lang/Object.class", false}, + // Entries in jimage which don't reference resources. + {"jrt:/modules/java.base/java/lang", false}, + {"jrt:/packages/java.lang", false}, + // Invalid (incomplete/corrupt) URIs. + {"jrt:/java.base", false}, + {"jrt:/java.base/", false}, + // Cannot escape anything in the module name. + {"jrt:/java%2Ebase/java/lang/Object.class", false}, }; return data; }