From b53e7e063e752526fb982559bf525e879b76bfe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sat, 7 Jan 2023 23:20:24 +0100 Subject: [PATCH 01/10] util: Add support for GraalVM Native-Image resource:-URIs and Paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GraalVM Native-Image makes its classpath resources accessible through an opaque resource: URL scheme. Add support for this scheme in URIUtil and PathResource. Automatically create the NativeImageResourceFileSystem when necessary. Also add a workaround where converting such Native-Image Paths back to URI would yield the wrong URI (potentially a bug in GraalVM). https://github.com/eclipse/jetty.project/issues/9116 https://github.com/oracle/graal/issues/5720 Signed-off-by: Christian Kohlschütter --- .../java/org/eclipse/jetty/util/URIUtil.java | 54 +++++++++++++++++++ .../jetty/util/resource/PathResource.java | 9 ++-- .../util/resource/PathResourceFactory.java | 31 ++++++++++- .../resource/ResourceFactoryInternals.java | 1 + 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 88cecb426dfa..792d6f81ffe6 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -19,11 +19,14 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -54,6 +57,7 @@ public final class URIUtil .with("file:") .with("jrt:") .with("jar:") + .with("resource:") .build(); // From https://www.rfc-editor.org/rfc/rfc3986 @@ -211,6 +215,8 @@ public final class URIUtil private static final boolean[] ENCODE_PATH_NEEDS_ENCODING; + private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!"; + private URIUtil() { } @@ -1735,6 +1741,54 @@ public static String addQueries(String query1, String query2) return query1 + '&' + query2; } + /** + * Corrects any bad {@code resource} based URIs, such as those starting with {@code resource:file:///resources!}. + * + * @param uri + * The URI to correct. + * @return the corrected URI, or the original URI. + * @see Graal issue 5720 + */ + public static URI correctResourceURI(URI uri) + { + if (uri == null || !"resource".equals(uri.getScheme())) + return uri; + + String ssp = uri.getSchemeSpecificPart(); + if (ssp.startsWith(URI_BAD_RESOURCE_PREFIX)) + { + return URI.create("resource:" + ssp.substring(URI_BAD_RESOURCE_PREFIX.length())); + } + else + { + return uri; + } + } + + /** + * A wrapper for {@link Paths#get(URI)} and {@link Path#of(URI)}. + * + * It automatically handles custom file systems, such as ZipFileSystem and NativeImageResourceFileSystem. + * + * @param uri + * The URI. + * @return The path. + * @throws IOException + * on error. + */ + public static Path getPath(URI uri) throws IOException + { + try + { + return Path.of(uri); + } + catch (FileSystemNotFoundException e) + { + FileSystems.newFileSystem(uri, Collections.emptyMap()); + return Path.of(uri); + } + } + /** *

* Corrects any bad {@code file} based URIs (even within a {@code jar:file:} based URIs) from the bad out-of-spec diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index 997eb42b4d0b..d2d8ab8f4e4a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -48,6 +48,7 @@ public class PathResource extends Resource .caseSensitive(false) .with("file") .with("jrt") + .with("resource") .build(); // The path object represented by this instance @@ -141,15 +142,16 @@ public static boolean isSameName(Path pathA, Path pathB) * Must be an absolute URI using the file scheme. * * @param uri the URI to build this PathResource from. + * @throws IOException if the path could not be resolved. */ - PathResource(URI uri) + PathResource(URI uri) throws IOException { this(uri, false); } - PathResource(URI uri, boolean bypassAllowedSchemeCheck) + PathResource(URI uri, boolean bypassAllowedSchemeCheck) throws IOException { - this(Paths.get(uri), uri, bypassAllowedSchemeCheck); + this(URIUtil.getPath(URIUtil.correctResourceURI(uri)), uri, bypassAllowedSchemeCheck); } PathResource(Path path) @@ -166,6 +168,7 @@ public static boolean isSameName(Path pathA, Path pathB) */ PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) { + uri = URIUtil.correctResourceURI(uri); if (!uri.isAbsolute()) throw new IllegalArgumentException("not an absolute uri: " + uri); if (!bypassAllowedSchemeCheck && !SUPPORTED_SCHEMES.contains(uri.getScheme())) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java index 4c0737b972f7..4b320a2aac25 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java @@ -13,19 +13,46 @@ package org.eclipse.jetty.util.resource; +import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; + +import org.eclipse.jetty.util.URIUtil; public class PathResourceFactory implements ResourceFactory { @Override public Resource newResource(URI uri) { - Path path = Paths.get(uri.normalize()); + Path path = Path.of(uri.normalize()); + if (!Files.exists(path)) + return null; + return new PathResource(path, uri, false); + } + + @Override + public Resource newResource(Path path) + { + if (path == null) + return null; + + URI uri = path.toUri(); + + try + { + // Validate URI conversion, and trigger FileSystem initialization, if necessary + if (URIUtil.getPath(uri) == null) + return null; + } + catch (IOException e) + { + return null; + } + if (!Files.exists(path)) return null; + return new PathResource(path, uri, false); } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 5f3406e24cd1..2c9ad4462f15 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -52,6 +52,7 @@ class ResourceFactoryInternals PathResourceFactory pathResourceFactory = new PathResourceFactory(); RESOURCE_FACTORIES.put("file", pathResourceFactory); RESOURCE_FACTORIES.put("jrt", pathResourceFactory); + RESOURCE_FACTORIES.put("resource", pathResourceFactory); // GraalVM native-image resource } static ResourceFactory ROOT = new CompositeResourceFactory() From 41c80b148c206c5376a6e379bf252fbf7b601073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sun, 8 Jan 2023 15:23:51 +0100 Subject: [PATCH 02/10] URIUtil: Suppress CodeQL false positive error about path injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Github CodeQL code scanning reports a high-severity error "Uncontrolled data used in path expression", because a path depends on a user-provided value. This is a false positive. Suppress the error by annotating a corresponding @SuppressWarnings tag. Signed-off-by: Christian Kohlschütter --- .../jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 792d6f81ffe6..97172540b104 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -1776,6 +1776,7 @@ public static URI correctResourceURI(URI uri) * @throws IOException * on error. */ + @SuppressWarnings("lgtm[java/path-injection]") public static Path getPath(URI uri) throws IOException { try From 71055615f935fdab5a90ba2060d156cfc4eb06fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sun, 8 Jan 2023 16:25:43 +0100 Subject: [PATCH 03/10] URIUtil: Removed unused code KNOWN_SCHEMES isn't used anywhere. --- .../src/main/java/org/eclipse/jetty/util/URIUtil.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 97172540b104..fd75625e53fa 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -52,13 +52,6 @@ public final class URIUtil { private static final Logger LOG = LoggerFactory.getLogger(URIUtil.class); - private static final Index KNOWN_SCHEMES = new Index.Builder() - .caseSensitive(false) - .with("file:") - .with("jrt:") - .with("jar:") - .with("resource:") - .build(); // From https://www.rfc-editor.org/rfc/rfc3986 private static final String UNRESERVED = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~"; From b5dabf9fddcf8ed2473b2866c40fcc51ab7e15f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sun, 8 Jan 2023 16:27:07 +0100 Subject: [PATCH 04/10] PathResource: Selectively enable resource: for GraalVM Native Image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In regular HotSpot VMs, the resource: scheme may be registered by other clients. However, in GraalVM Native Image, this is used for classpath resources. This resource scheme needs to be enabled by default for Native Image environments so we can support code that is unaware of GraalVM internals, such as: URL resourceURL = MyClass.class.getResource("some/resource"); Resource resource = ResourceFactory.root().newResource(resourceURL); Signed-off-by: Christian Kohlschütter --- .../java/org/eclipse/jetty/util/URIUtil.java | 25 ---------- .../jetty/util/resource/PathResource.java | 26 ++++++---- .../util/resource/PathResourceFactory.java | 49 +++++++++++++------ .../jetty/util/resource/ResourceFactory.java | 2 +- .../resource/ResourceFactoryInternals.java | 4 +- 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index fd75625e53fa..64d5335782bb 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -1758,31 +1758,6 @@ public static URI correctResourceURI(URI uri) } } - /** - * A wrapper for {@link Paths#get(URI)} and {@link Path#of(URI)}. - * - * It automatically handles custom file systems, such as ZipFileSystem and NativeImageResourceFileSystem. - * - * @param uri - * The URI. - * @return The path. - * @throws IOException - * on error. - */ - @SuppressWarnings("lgtm[java/path-injection]") - public static Path getPath(URI uri) throws IOException - { - try - { - return Path.of(uri); - } - catch (FileSystemNotFoundException e) - { - FileSystems.newFileSystem(uri, Collections.emptyMap()); - return Path.of(uri); - } - } - /** *

* Corrects any bad {@code file} based URIs (even within a {@code jar:file:} based URIs) from the bad out-of-spec diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index d2d8ab8f4e4a..56d53e56b571 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -44,12 +44,20 @@ public class PathResource extends Resource { private static final Logger LOG = LoggerFactory.getLogger(PathResource.class); - public static Index SUPPORTED_SCHEMES = new Index.Builder() - .caseSensitive(false) - .with("file") - .with("jrt") - .with("resource") - .build(); + public static Index SUPPORTED_SCHEMES; + + static + { + Index.Builder builder = new Index.Builder() + .caseSensitive(false) + .with("file") + .with("jrt"); + + if (PathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) + builder = builder.with("resource"); + + SUPPORTED_SCHEMES = builder.build(); + } // The path object represented by this instance private final Path path; @@ -144,14 +152,14 @@ public static boolean isSameName(Path pathA, Path pathB) * @param uri the URI to build this PathResource from. * @throws IOException if the path could not be resolved. */ - PathResource(URI uri) throws IOException + PathResource(URI uri) { this(uri, false); } - PathResource(URI uri, boolean bypassAllowedSchemeCheck) throws IOException + PathResource(URI uri, boolean bypassAllowedSchemeCheck) { - this(URIUtil.getPath(URIUtil.correctResourceURI(uri)), uri, bypassAllowedSchemeCheck); + this(Path.of(URIUtil.correctResourceURI(uri)), uri, bypassAllowedSchemeCheck); } PathResource(Path path) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java index 4b320a2aac25..cd0e6002fd07 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java @@ -15,13 +15,43 @@ import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; - -import org.eclipse.jetty.util.URIUtil; +import java.util.Collections; public class PathResourceFactory implements ResourceFactory { + static final boolean ENABLE_GRAALVM_RESOURCE_SCHEME = (System.getProperty( + "org.graalvm.nativeimage.kind") != null); + + static + { + if (ENABLE_GRAALVM_RESOURCE_SCHEME) + { + // initialize NativeImageResourceFileSystem, if necessary + URI uri; + try + { + uri = new URI("resource:/"); + try + { + Path.of(uri); + } + catch (FileSystemNotFoundException e) + { + FileSystems.newFileSystem(uri, Collections.emptyMap()); + } + } + catch (IOException | URISyntaxException ignore) + { + // ignore + } + } + } + @Override public Resource newResource(URI uri) { @@ -37,22 +67,9 @@ public Resource newResource(Path path) if (path == null) return null; - URI uri = path.toUri(); - - try - { - // Validate URI conversion, and trigger FileSystem initialization, if necessary - if (URIUtil.getPath(uri) == null) - return null; - } - catch (IOException e) - { - return null; - } - if (!Files.exists(path)) return null; - return new PathResource(path, uri, false); + return new PathResource(path, path.toUri(), false); } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index da7f1f4aa947..6453231b3ab4 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -202,7 +202,7 @@ default Resource newResource(Path path) if (path == null) throw new IllegalArgumentException("Path is null"); - return newResource(path.toUri()); + return newResource(URIUtil.correctResourceURI(path.toUri())); } /** diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 2c9ad4462f15..45b0212d384a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -52,7 +52,9 @@ class ResourceFactoryInternals PathResourceFactory pathResourceFactory = new PathResourceFactory(); RESOURCE_FACTORIES.put("file", pathResourceFactory); RESOURCE_FACTORIES.put("jrt", pathResourceFactory); - RESOURCE_FACTORIES.put("resource", pathResourceFactory); // GraalVM native-image resource + + if (PathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) + RESOURCE_FACTORIES.put("resource", pathResourceFactory); } static ResourceFactory ROOT = new CompositeResourceFactory() From ac1592ec681debf76344316f41e25caee64d9a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sun, 8 Jan 2023 17:01:48 +0100 Subject: [PATCH 05/10] PathResourceFactory: Ignore certain RuntimeExceptions upon init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore FileSystemAlreadyExistsException, ProviderNotFoundException, etc. that may be thrown upon calling FileSystems.newFileSystem. Signed-off-by: Christian Kohlschütter --- .../org/eclipse/jetty/util/resource/PathResourceFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java index cd0e6002fd07..3ab2cfd75536 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java @@ -45,7 +45,7 @@ public class PathResourceFactory implements ResourceFactory FileSystems.newFileSystem(uri, Collections.emptyMap()); } } - catch (IOException | URISyntaxException ignore) + catch (IOException | URISyntaxException | RuntimeException ignore) { // ignore } From 0bb9d16d7cc34e38ca086ad8757e2ab8999d3643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Sun, 8 Jan 2023 17:50:12 +0100 Subject: [PATCH 06/10] PathResource: Work-around false positive CodeQL warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeQL prevents amending the call to Paths.get. Work-around this by using a separate constructor. The additional benefit is that URIUtil.correctResourceURI won't need to correct the URI twice. Signed-off-by: Christian Kohlschütter --- .../org/eclipse/jetty/util/resource/PathResource.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index 56d53e56b571..d756f24a8280 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -157,9 +157,9 @@ public static boolean isSameName(Path pathA, Path pathB) this(uri, false); } - PathResource(URI uri, boolean bypassAllowedSchemeCheck) + private PathResource(boolean bypassAllowedSchemeCheck, URI uri) { - this(Path.of(URIUtil.correctResourceURI(uri)), uri, bypassAllowedSchemeCheck); + this(Paths.get(uri), uri, bypassAllowedSchemeCheck); } PathResource(Path path) @@ -167,6 +167,11 @@ public static boolean isSameName(Path pathA, Path pathB) this(path, path.toUri(), true); } + PathResource(URI uri, boolean bypassAllowedSchemeCheck) + { + this(bypassAllowedSchemeCheck, URIUtil.correctResourceURI(uri)); + } + /** * Create a PathResource. * From a5011d128d263b7cc88a721d9fa30110a858a53d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Mon, 9 Jan 2023 01:12:40 +0100 Subject: [PATCH 07/10] PathResource: Fix "isAlias" issue with resource: URIs Fix two more places where Native Image resource: URIs need to be changed. Without this change, Resource#isAlias() would return true for such URIs, and a warning like "BaseResource resource:/some/package/ is aliased to resource:file:///resources!/some/package/" would be logged. --- .../java/org/eclipse/jetty/util/resource/PathResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index d756f24a8280..b9132586a45a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -230,7 +230,7 @@ public Path getRealPath() public URI getRealURI() { Path realPath = getRealPath(); - return (realPath == null) ? null : realPath.toUri(); + return (realPath == null) ? null : URIUtil.correctResourceURI(realPath.toUri()); } public List list() @@ -528,8 +528,8 @@ public void copyTo(Path destination) throws IOException */ private static URI toUri(Path path) { - URI pathUri = path.toUri(); - String rawUri = path.toUri().toASCIIString(); + URI pathUri = URIUtil.correctResourceURI(path.toUri()); + String rawUri = pathUri.toASCIIString(); if (Files.isDirectory(path) && !rawUri.endsWith("/")) { From c74ad3cec64f217206ea6e5ad049c4742b722333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Mon, 9 Jan 2023 16:29:44 +0100 Subject: [PATCH 08/10] Move GraalVM Native-Image code to NativeImagePathResource/-Factory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the GraalVM Native-Image code contained in custom subclasses. We still enable the "resource:" URL scheme by default if a GraalVM Native-Image environment is detected via System property. This allows us to transparently support calls like ResourceFactory.root().newResource(MyClass.class.getResorce("webapp")) Signed-off-by: Christian Kohlschütter --- .../java/org/eclipse/jetty/util/URIUtil.java | 29 ------- .../resource/NativeImagePathResource.java | 80 +++++++++++++++++++ .../NativeImagePathResourceFactory.java | 71 ++++++++++++++++ .../jetty/util/resource/PathResource.java | 34 +++----- .../util/resource/PathResourceFactory.java | 48 +---------- .../jetty/util/resource/ResourceFactory.java | 2 +- .../resource/ResourceFactoryInternals.java | 4 +- 7 files changed, 165 insertions(+), 103 deletions(-) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 64d5335782bb..a7fc6c328501 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -19,14 +19,11 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -208,8 +205,6 @@ public final class URIUtil private static final boolean[] ENCODE_PATH_NEEDS_ENCODING; - private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!"; - private URIUtil() { } @@ -1734,30 +1729,6 @@ public static String addQueries(String query1, String query2) return query1 + '&' + query2; } - /** - * Corrects any bad {@code resource} based URIs, such as those starting with {@code resource:file:///resources!}. - * - * @param uri - * The URI to correct. - * @return the corrected URI, or the original URI. - * @see Graal issue 5720 - */ - public static URI correctResourceURI(URI uri) - { - if (uri == null || !"resource".equals(uri.getScheme())) - return uri; - - String ssp = uri.getSchemeSpecificPart(); - if (ssp.startsWith(URI_BAD_RESOURCE_PREFIX)) - { - return URI.create("resource:" + ssp.substring(URI_BAD_RESOURCE_PREFIX.length())); - } - else - { - return uri; - } - } - /** *

* Corrects any bad {@code file} based URIs (even within a {@code jar:file:} based URIs) from the bad out-of-spec diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java new file mode 100644 index 000000000000..05418e99a96a --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java @@ -0,0 +1,80 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.util.resource; + +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * GraalVM Native-Image {@link Path} Resource. + */ +public class NativeImagePathResource extends PathResource +{ + private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!"; + + NativeImagePathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) + { + super(path, correctResourceURI(uri), (bypassAllowedSchemeCheck || isResourceScheme(uri))); + } + + private static final boolean isResourceScheme(URI uri) + { + return "resource".equals(uri.getScheme()); + } + + /** + * Corrects any bad {@code resource} based URIs, such as those starting with {@code resource:file:///resources!}. + * + * @param uri + * The URI to correct. + * @return the corrected URI, or the original URI. + * @see Graal issue 5720 + */ + static URI correctResourceURI(URI uri) + { + if (uri == null || !isResourceScheme(uri)) + return uri; + + String ssp = uri.getSchemeSpecificPart(); + if (ssp.startsWith(URI_BAD_RESOURCE_PREFIX)) + { + return URI.create("resource:" + ssp.substring(URI_BAD_RESOURCE_PREFIX.length())); + } + else + { + return uri; + } + } + + @Override + public URI getRealURI() + { + Path realPath = getRealPath(); + return (realPath == null) ? null : correctResourceURI(realPath.toUri()); + } + + @Override + protected URI toUri(Path path) + { + URI pathUri = correctResourceURI(path.toUri()); + String rawUri = pathUri.toASCIIString(); + + if (Files.isDirectory(path) && !rawUri.endsWith("/")) + { + return URI.create(rawUri + '/'); + } + return pathUri; + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java new file mode 100644 index 000000000000..c645913f4f9b --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java @@ -0,0 +1,71 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.util.resource; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +/** + * GraalVM Native-Image {@link PathResourceFactory}. + */ +public class NativeImagePathResourceFactory extends PathResourceFactory +{ + static final boolean ENABLE_GRAALVM_RESOURCE_SCHEME = (System.getProperty("org.graalvm.nativeimage.kind") != null); + + public NativeImagePathResourceFactory() + { + if (ENABLE_GRAALVM_RESOURCE_SCHEME) + { + initNativeImageResourceFileSystem(); + } + } + + private void initNativeImageResourceFileSystem() + { + try + { + URI uri = new URI("resource:/"); + try + { + Path.of(uri); + } + catch (FileSystemNotFoundException e) + { + FileSystems.newFileSystem(uri, Collections.emptyMap()); + } + } + catch (IOException | URISyntaxException | RuntimeException ignore) + { + // ignore + } + } + + @Override + public Resource newResource(URI uri) + { + uri = NativeImagePathResource.correctResourceURI(uri.normalize()); + Path path = Path.of(uri); + + if (!Files.exists(path)) + return null; + + return new NativeImagePathResource(path, uri, false); + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java index b9132586a45a..440ab8c7bfd2 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java @@ -44,20 +44,11 @@ public class PathResource extends Resource { private static final Logger LOG = LoggerFactory.getLogger(PathResource.class); - public static Index SUPPORTED_SCHEMES; - - static - { - Index.Builder builder = new Index.Builder() - .caseSensitive(false) - .with("file") - .with("jrt"); - - if (PathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) - builder = builder.with("resource"); - - SUPPORTED_SCHEMES = builder.build(); - } + public static Index SUPPORTED_SCHEMES = new Index.Builder() + .caseSensitive(false) + .with("file") + .with("jrt") + .build(); // The path object represented by this instance private final Path path; @@ -150,14 +141,13 @@ public static boolean isSameName(Path pathA, Path pathB) * Must be an absolute URI using the file scheme. * * @param uri the URI to build this PathResource from. - * @throws IOException if the path could not be resolved. */ PathResource(URI uri) { this(uri, false); } - private PathResource(boolean bypassAllowedSchemeCheck, URI uri) + PathResource(URI uri, boolean bypassAllowedSchemeCheck) { this(Paths.get(uri), uri, bypassAllowedSchemeCheck); } @@ -167,11 +157,6 @@ private PathResource(boolean bypassAllowedSchemeCheck, URI uri) this(path, path.toUri(), true); } - PathResource(URI uri, boolean bypassAllowedSchemeCheck) - { - this(bypassAllowedSchemeCheck, URIUtil.correctResourceURI(uri)); - } - /** * Create a PathResource. * @@ -181,7 +166,6 @@ private PathResource(boolean bypassAllowedSchemeCheck, URI uri) */ PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) { - uri = URIUtil.correctResourceURI(uri); if (!uri.isAbsolute()) throw new IllegalArgumentException("not an absolute uri: " + uri); if (!bypassAllowedSchemeCheck && !SUPPORTED_SCHEMES.contains(uri.getScheme())) @@ -230,7 +214,7 @@ public Path getRealPath() public URI getRealURI() { Path realPath = getRealPath(); - return (realPath == null) ? null : URIUtil.correctResourceURI(realPath.toUri()); + return (realPath == null) ? null : realPath.toUri(); } public List list() @@ -526,9 +510,9 @@ public void copyTo(Path destination) throws IOException * @param path the path to convert to URI * @return the appropriate URI for the path */ - private static URI toUri(Path path) + protected URI toUri(Path path) { - URI pathUri = URIUtil.correctResourceURI(path.toUri()); + URI pathUri = path.toUri(); String rawUri = pathUri.toASCIIString(); if (Files.isDirectory(path) && !rawUri.endsWith("/")) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java index 3ab2cfd75536..4c0737b972f7 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java @@ -13,63 +13,19 @@ package org.eclipse.jetty.util.resource; -import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; +import java.nio.file.Paths; public class PathResourceFactory implements ResourceFactory { - static final boolean ENABLE_GRAALVM_RESOURCE_SCHEME = (System.getProperty( - "org.graalvm.nativeimage.kind") != null); - - static - { - if (ENABLE_GRAALVM_RESOURCE_SCHEME) - { - // initialize NativeImageResourceFileSystem, if necessary - URI uri; - try - { - uri = new URI("resource:/"); - try - { - Path.of(uri); - } - catch (FileSystemNotFoundException e) - { - FileSystems.newFileSystem(uri, Collections.emptyMap()); - } - } - catch (IOException | URISyntaxException | RuntimeException ignore) - { - // ignore - } - } - } - @Override public Resource newResource(URI uri) { - Path path = Path.of(uri.normalize()); + Path path = Paths.get(uri.normalize()); if (!Files.exists(path)) return null; return new PathResource(path, uri, false); } - - @Override - public Resource newResource(Path path) - { - if (path == null) - return null; - - if (!Files.exists(path)) - return null; - - return new PathResource(path, path.toUri(), false); - } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java index 6453231b3ab4..da7f1f4aa947 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactory.java @@ -202,7 +202,7 @@ default Resource newResource(Path path) if (path == null) throw new IllegalArgumentException("Path is null"); - return newResource(URIUtil.correctResourceURI(path.toUri())); + return newResource(path.toUri()); } /** diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 45b0212d384a..2e03641b5d4e 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -53,8 +53,8 @@ class ResourceFactoryInternals RESOURCE_FACTORIES.put("file", pathResourceFactory); RESOURCE_FACTORIES.put("jrt", pathResourceFactory); - if (PathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) - RESOURCE_FACTORIES.put("resource", pathResourceFactory); + if (NativeImagePathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) + RESOURCE_FACTORIES.put("resource", new NativeImagePathResourceFactory()); } static ResourceFactory ROOT = new CompositeResourceFactory() From 8d0d3278a6b626b2b0cab978c5715aa893802c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Tue, 10 Jan 2023 14:06:52 +0100 Subject: [PATCH 09/10] Refactor Graal native-image resource: handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detect the resource: scheme by checking the scheme of a well-known resource instead of looking at a system property. Rename NativeImagePathResource* to GraalIssue5720PathResource* and make these classes package-private. Signed-off-by: Christian Kohlschütter --- ...e.java => GraalIssue5720PathResource.java} | 6 ++++-- ...=> GraalIssue5720PathResourceFactory.java} | 21 +++++++++++++------ .../resource/ResourceFactoryInternals.java | 4 ++-- .../jetty-core/resource-config.json | 9 ++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) rename jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/{NativeImagePathResource.java => GraalIssue5720PathResource.java} (90%) rename jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/{NativeImagePathResourceFactory.java => GraalIssue5720PathResourceFactory.java} (69%) create mode 100644 jetty-core/jetty-util/src/main/resources/META-INF/native-image/org.eclipse.jetty/jetty-core/resource-config.json diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java similarity index 90% rename from jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java rename to jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java index 05418e99a96a..292501362b20 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java @@ -19,12 +19,14 @@ /** * GraalVM Native-Image {@link Path} Resource. + * + * @see Graal issue 5720 */ -public class NativeImagePathResource extends PathResource +final class GraalIssue5720PathResource extends PathResource { private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!"; - NativeImagePathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) + GraalIssue5720PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) { super(path, correctResourceURI(uri), (bypassAllowedSchemeCheck || isResourceScheme(uri))); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java similarity index 69% rename from jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java rename to jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java index c645913f4f9b..2d8d0d5e0fca 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/NativeImagePathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -24,14 +25,22 @@ /** * GraalVM Native-Image {@link PathResourceFactory}. + * + * @see Graal issue 5720 */ -public class NativeImagePathResourceFactory extends PathResourceFactory +final class GraalIssue5720PathResourceFactory extends PathResourceFactory { - static final boolean ENABLE_GRAALVM_RESOURCE_SCHEME = (System.getProperty("org.graalvm.nativeimage.kind") != null); + static final boolean ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME; - public NativeImagePathResourceFactory() + static { - if (ENABLE_GRAALVM_RESOURCE_SCHEME) + URL url = GraalIssue5720PathResourceFactory.class.getResource("/org/eclipse/jetty/version/build.properties"); + ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME = (url != null && "resource".equals(url.getProtocol())); + } + + public GraalIssue5720PathResourceFactory() + { + if (ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME) { initNativeImageResourceFileSystem(); } @@ -60,12 +69,12 @@ private void initNativeImageResourceFileSystem() @Override public Resource newResource(URI uri) { - uri = NativeImagePathResource.correctResourceURI(uri.normalize()); + uri = GraalIssue5720PathResource.correctResourceURI(uri.normalize()); Path path = Path.of(uri); if (!Files.exists(path)) return null; - return new NativeImagePathResource(path, uri, false); + return new GraalIssue5720PathResource(path, uri, false); } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 2e03641b5d4e..11f619d7d16b 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -53,8 +53,8 @@ class ResourceFactoryInternals RESOURCE_FACTORIES.put("file", pathResourceFactory); RESOURCE_FACTORIES.put("jrt", pathResourceFactory); - if (NativeImagePathResourceFactory.ENABLE_GRAALVM_RESOURCE_SCHEME) - RESOURCE_FACTORIES.put("resource", new NativeImagePathResourceFactory()); + if (GraalIssue5720PathResourceFactory.ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME) + RESOURCE_FACTORIES.put("resource", new GraalIssue5720PathResourceFactory()); } static ResourceFactory ROOT = new CompositeResourceFactory() diff --git a/jetty-core/jetty-util/src/main/resources/META-INF/native-image/org.eclipse.jetty/jetty-core/resource-config.json b/jetty-core/jetty-util/src/main/resources/META-INF/native-image/org.eclipse.jetty/jetty-core/resource-config.json new file mode 100644 index 000000000000..0d9bcb4fac5b --- /dev/null +++ b/jetty-core/jetty-util/src/main/resources/META-INF/native-image/org.eclipse.jetty/jetty-core/resource-config.json @@ -0,0 +1,9 @@ +{ + "resources":{ + "includes":[ + { + "pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E" + } + ]}, + "bundles":[] +} \ No newline at end of file From cdea111d3f731d8ef0c71d71e09c063a08b601bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kohlschu=CC=88tter?= Date: Mon, 16 Jan 2023 19:49:14 +0100 Subject: [PATCH 10/10] Only use GraalIssue5720PathResourceFactory when truly needed Previously, we were always enabling this resource factory for GraalVM native-image environments. We now check if that's actually necessary. We fall back to MountedPathResourceFactory or PathResourceFactory, depending on whether the URL contains "!/" or not. --- .../resource/GraalIssue5720PathResource.java | 62 +++++++++++++++++++ .../GraalIssue5720PathResourceFactory.java | 43 +------------ .../resource/ResourceFactoryInternals.java | 26 +++++--- 3 files changed, 80 insertions(+), 51 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java index 292501362b20..690178aeeb71 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResource.java @@ -13,9 +13,19 @@ package org.eclipse.jetty.util.resource; +import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * GraalVM Native-Image {@link Path} Resource. @@ -24,6 +34,7 @@ */ final class GraalIssue5720PathResource extends PathResource { + private static final Logger LOG = LoggerFactory.getLogger(GraalIssue5720PathResource.class); private static final String URI_BAD_RESOURCE_PREFIX = "file:///resources!"; GraalIssue5720PathResource(Path path, URI uri, boolean bypassAllowedSchemeCheck) @@ -36,6 +47,57 @@ private static final boolean isResourceScheme(URI uri) return "resource".equals(uri.getScheme()); } + /** + * Checks if the given resource URL is affected by Graal issue 5720. + * + * @param url + * The URL to check. + * @return {@code true} if affected. + */ + static boolean isAffectedURL(URL url) + { + if (url == null || !"resource".equals(url.getProtocol())) + { + return false; + } + + URI uri; + try + { + uri = url.toURI(); + } + catch (URISyntaxException e) + { + return false; + } + + try + { + try + { + uri = Path.of(uri).toUri(); + } + catch (FileSystemNotFoundException e) + { + try + { + FileSystems.newFileSystem(uri, Collections.emptyMap()); + } + catch (FileSystemAlreadyExistsException e2) + { + LOG.debug("Race condition upon calling FileSystems.newFileSystem for: {}", uri, e); + } + uri = Path.of(uri).toUri(); + } + } + catch (IOException | RuntimeException e) + { + LOG.warn("Could not check URL: {}", url, e); + } + + return uri.getSchemeSpecificPart().startsWith(URI_BAD_RESOURCE_PREFIX); + } + /** * Corrects any bad {@code resource} based URIs, such as those starting with {@code resource:file:///resources!}. * diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java index 2d8d0d5e0fca..6660cf3831b3 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/GraalIssue5720PathResourceFactory.java @@ -13,59 +13,18 @@ package org.eclipse.jetty.util.resource; -import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; /** * GraalVM Native-Image {@link PathResourceFactory}. * * @see Graal issue 5720 + * @see GraalIssue5720PathResource */ final class GraalIssue5720PathResourceFactory extends PathResourceFactory { - static final boolean ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME; - - static - { - URL url = GraalIssue5720PathResourceFactory.class.getResource("/org/eclipse/jetty/version/build.properties"); - ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME = (url != null && "resource".equals(url.getProtocol())); - } - - public GraalIssue5720PathResourceFactory() - { - if (ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME) - { - initNativeImageResourceFileSystem(); - } - } - - private void initNativeImageResourceFileSystem() - { - try - { - URI uri = new URI("resource:/"); - try - { - Path.of(uri); - } - catch (FileSystemNotFoundException e) - { - FileSystems.newFileSystem(uri, Collections.emptyMap()); - } - } - catch (IOException | URISyntaxException | RuntimeException ignore) - { - // ignore - } - } - @Override public Resource newResource(URI uri) { diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java index 7cf0e6b5b17d..9d3493b7279b 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceFactoryInternals.java @@ -55,19 +55,27 @@ class ResourceFactoryInternals RESOURCE_FACTORIES.put("file", pathResourceFactory); RESOURCE_FACTORIES.put("jrt", pathResourceFactory); - if (GraalIssue5720PathResourceFactory.ENABLE_NATIVE_IMAGE_RESOURCE_SCHEME) - RESOURCE_FACTORIES.put("resource", new GraalIssue5720PathResourceFactory()); - - /* Best effort attempt to detect that an alternate FileSystem type that is in use. - * We don't attempt to look up a Class, as not all runtimes and environments have the classes anymore - * (e.g., they were compiled into native code) - * The build.properties is present in the jetty-util jar, so it's reasonably safe to look for that - * as a resource + /* Best-effort attempt to support an alternate FileSystem type that is in use for classpath + * resources. + * + * The build.properties is present in the jetty-util jar, and explicitly included for reflection + * with native-image (unlike classes, which are not accessible by default), so we use that + * resource as a reference. */ URL url = ResourceFactoryInternals.class.getResource("/org/eclipse/jetty/version/build.properties"); if ((url != null) && !RESOURCE_FACTORIES.contains(url.getProtocol())) { - RESOURCE_FACTORIES.put(url.getProtocol(), mountedPathResourceFactory); + ResourceFactory resourceFactory; + if (GraalIssue5720PathResource.isAffectedURL(url)) + { + resourceFactory = new GraalIssue5720PathResourceFactory(); + } + else + { + resourceFactory = url.toString().contains("!/") ? mountedPathResourceFactory : pathResourceFactory; + } + + RESOURCE_FACTORIES.put(url.getProtocol(), resourceFactory); } }