From fc407abbd51650f8a81cf3e2d20d5d0b923042ce Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 25 Mar 2024 09:46:46 -0500 Subject: [PATCH] Support multiple nesting of JAR files * Make sure that `jar:file:///...` are properly formed (the sub-URL must have a scheme of `file`) * When there is no `!` in the JAR URL, treat the file as a JAR and process its root path * Otherwise recursively resolve each segment which is `!`-terminated --- .../common/classloader/ClassPathUtils.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/classloader/src/main/java/io/smallrye/common/classloader/ClassPathUtils.java b/classloader/src/main/java/io/smallrye/common/classloader/ClassPathUtils.java index e089d2ca..6de6a890 100644 --- a/classloader/src/main/java/io/smallrye/common/classloader/ClassPathUtils.java +++ b/classloader/src/main/java/io/smallrye/common/classloader/ClassPathUtils.java @@ -129,22 +129,25 @@ public static R processAsPath(URL url, Function function) { } final String file = url.getFile(); - final int exclam = file.lastIndexOf('!'); - final Path jar; + final int exclam = file.indexOf('!'); try { - jar = toLocalPath(exclam >= 0 ? new URL(file.substring(0, exclam)) : url); + URL fileUrl; + String subPath; + if (exclam == -1) { + // assume the first element is a JAR file, not a plain file, since it was a `jar:` URL + fileUrl = new URL(file); + subPath = "/"; + } else { + fileUrl = new URL(file.substring(0, exclam)); + subPath = file.substring(exclam + 1); + } + if (!fileUrl.getProtocol().equals("file")) { + throw new IllegalArgumentException("Sub-URL of JAR URL is expected to have a scheme of `file`"); + } + return processAsJarPath(toLocalPath(fileUrl), subPath, function); } catch (MalformedURLException e) { throw new RuntimeException("Failed to create a URL for '" + file.substring(0, exclam) + "'", e); } - try (FileSystem jarFs = FileSystems.newFileSystem(jar, (ClassLoader) null)) { - Path localPath = jarFs.getPath("/"); - if (exclam >= 0) { - localPath = localPath.resolve(file.substring(exclam + 1)); - } - return function.apply(localPath); - } catch (IOException e) { - throw new UncheckedIOException("Failed to read " + jar, e); - } } if (FILE.equals(url.getProtocol())) { @@ -154,6 +157,20 @@ public static R processAsPath(URL url, Function function) { throw new IllegalArgumentException("Unexpected protocol " + url.getProtocol() + " for URL " + url); } + private static R processAsJarPath(Path jarPath, String path, Function function) { + try (FileSystem jarFs = FileSystems.newFileSystem(jarPath, (ClassLoader) null)) { + Path localPath = jarFs.getPath("/"); + int idx = path.indexOf('!'); + if (idx == -1) { + return function.apply(localPath.resolve(path)); + } else { + return processAsJarPath(localPath.resolve(path.substring(0, idx)), path.substring(idx + 1), function); + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + jarPath, e); + } + } + /** * Invokes a consumer providing the input streams to read the content of the URL. * The consumer does not have to close the provided input stream.