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] 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()