From d1450aa2d953ad62e1199a25322a71b22ff473fa Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Wed, 30 Oct 2024 14:20:53 -0500 Subject: [PATCH] Allow traversal of root JAR directory and hidden directories --- .../common/resource/JarFileResource.java | 68 ++++++------------- .../resource/JarFileResourceLoader.java | 17 +++++ .../resource/JarFileResourceLoaderTests.java | 28 ++++++++ 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/resource/src/main/java/io/smallrye/common/resource/JarFileResource.java b/resource/src/main/java/io/smallrye/common/resource/JarFileResource.java index f332bc8..2de2208 100644 --- a/resource/src/main/java/io/smallrye/common/resource/JarFileResource.java +++ b/resource/src/main/java/io/smallrye/common/resource/JarFileResource.java @@ -10,11 +10,8 @@ import java.nio.file.attribute.FileTime; import java.security.CodeSigner; import java.time.Instant; -import java.util.Collections; -import java.util.Enumeration; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -51,12 +48,6 @@ public URL url() { return url; } - /** - * {@inheritDoc} - * - * @implNote This implementation does not recognize directories that do not have an explicit entry. - * This restriction may be lifted in future versions. - */ public boolean isDirectory() { return jarEntry.isDirectory(); } @@ -66,49 +57,30 @@ public DirectoryStream openDirectoryStream() throws IOException { return super.openDirectoryStream(); } return new DirectoryStream() { - Enumeration entries; - public Iterator iterator() { - if (entries == null) { - entries = jarFile.entries(); - return new Iterator() { - Resource next; - - public boolean hasNext() { - String ourName = jarEntry.getName(); - while (next == null) { - if (!entries.hasMoreElements()) { - return false; - } - JarEntry e = entries.nextElement(); - String name = e.getName(); - int ourLen = ourName.length(); - if (name.startsWith(ourName) && !name.equals(ourName)) { - int idx = name.indexOf('/', ourLen); - if (idx == -1 || name.length() == idx + 1) { - next = new JarFileResource(base, jarFile, e); - } - break; - } - } - return true; - } - - public Resource next() { - if (!hasNext()) { - throw new NoSuchElementException(); + String ourName = pathName(); + return jarFile.versionedStream() + .filter(e -> { + String name = ResourceUtils.canonicalizeRelativePath(e.getName()); + if (ourName.isEmpty()) { + // find root entries + return name.indexOf('/') == -1; + } else { + // find subdirectory entries + int si; + return name.startsWith(ourName) + && name.length() > ourName.length() + && name.charAt(ourName.length()) == '/' + && ((si = name.indexOf('/', ourName.length() + 1)) == -1 || si == name.length() - 1); } - Resource next = this.next; - this.next = null; - return next; - } - }; - } - throw new IllegalStateException(); + }) + .map(e -> new JarFileResource(base, jarFile, e)) + // appease the generics demons + .map(r -> (Resource) r) + .iterator(); } public void close() { - entries = Collections.emptyEnumeration(); } }; } @@ -124,7 +96,7 @@ public List codeSigners() { } public InputStream openStream() throws IOException { - return jarFile.getInputStream(jarEntry); + return isDirectory() ? InputStream.nullInputStream() : jarFile.getInputStream(jarEntry); } public long size() { diff --git a/resource/src/main/java/io/smallrye/common/resource/JarFileResourceLoader.java b/resource/src/main/java/io/smallrye/common/resource/JarFileResourceLoader.java index d1ee822..c292db5 100644 --- a/resource/src/main/java/io/smallrye/common/resource/JarFileResourceLoader.java +++ b/resource/src/main/java/io/smallrye/common/resource/JarFileResourceLoader.java @@ -63,6 +63,13 @@ public JarFileResourceLoader(final Resource resource) throws IOException { public Resource findResource(final String path) { String canonPath = ResourceUtils.canonicalizeRelativePath(path); + if (canonPath.isEmpty()) { + // root directory + JarEntry entry = new JarEntry("/"); + entry.setSize(0); + entry.setCompressedSize(0); + return new JarFileResource(base, jarFile, entry); + } JarEntry jarEntry = jarFile.getJarEntry(canonPath); if (jarEntry != null) { return new JarFileResource(base, jarFile, jarEntry); @@ -71,6 +78,16 @@ public Resource findResource(final String path) { if (jarEntry != null) { return new JarFileResource(base, jarFile, jarEntry); } else { + // search for a directory with the given name (todo: may be slow) + String dirName = canonPath + "/"; + boolean found = jarFile.versionedStream().map(JarEntry::getName).map(ResourceUtils::canonicalizeRelativePath) + .anyMatch(n -> n.startsWith(dirName)); + if (found) { + JarEntry entry = new JarEntry(dirName); + entry.setSize(0); + entry.setCompressedSize(0); + return new JarFileResource(base, jarFile, entry); + } return null; } } diff --git a/resource/src/test/java/io/smallrye/common/resource/JarFileResourceLoaderTests.java b/resource/src/test/java/io/smallrye/common/resource/JarFileResourceLoaderTests.java index 4584bd9..c5d00fc 100644 --- a/resource/src/test/java/io/smallrye/common/resource/JarFileResourceLoaderTests.java +++ b/resource/src/test/java/io/smallrye/common/resource/JarFileResourceLoaderTests.java @@ -31,6 +31,8 @@ public void testOpenJar() throws IOException { dir("dir1"), store("dir1/file-stored.txt", FILE_TXT), deflate("dir1/file-deflated.txt", FILE_TXT), + // hidden directory + store("dir2/hidden-file.txt", FILE_TXT), // keep this as last entry dir("end"))) { assertNull(rl.findResource("missing")); @@ -53,6 +55,32 @@ public void testOpenJar() throws IOException { assertEquals("dir1/file-deflated.txt", iterator.next().pathName()); assertFalse(iterator.hasNext()); } + Resource dir2_hidden_file_txt = rl.findResource("dir2/hidden-file.txt"); + assertNotNull(dir2_hidden_file_txt); + assertEquals(FILE_TXT, dir2_hidden_file_txt.asString(StandardCharsets.UTF_8)); + Resource dir2 = rl.findResource("dir2"); + assertNotNull(dir2); + assertEquals(0, dir2.size()); + assertEquals("jar:memory:test.jar!/dir2", dir2.url().toString()); + try (DirectoryStream ds = dir2.openDirectoryStream()) { + Iterator iterator = ds.iterator(); + assertTrue(iterator.hasNext()); + assertEquals("dir2/hidden-file.txt", iterator.next().pathName()); + assertFalse(iterator.hasNext()); + } + Resource rootDir = rl.findResource("/"); + assertNotNull(rootDir); + assertEquals(0, rootDir.size()); + assertEquals("jar:memory:test.jar!/", rootDir.url().toString()); + try (DirectoryStream ds = rootDir.openDirectoryStream()) { + Iterator iterator = ds.iterator(); + assertTrue(iterator.hasNext()); + assertEquals("META-INF", iterator.next().pathName()); + assertTrue(iterator.hasNext()); + assertEquals("dir1", iterator.next().pathName()); + assertEquals("end", iterator.next().pathName()); + assertFalse(iterator.hasNext()); + } } }