Skip to content

Commit

Permalink
Allow traversal of root JAR directory and hidden directories
Browse files Browse the repository at this point in the history
  • Loading branch information
dmlloyd committed Oct 30, 2024
1 parent c5e7c6e commit d1450aa
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}
Expand All @@ -66,49 +57,30 @@ public DirectoryStream<Resource> openDirectoryStream() throws IOException {
return super.openDirectoryStream();
}
return new DirectoryStream<Resource>() {
Enumeration<JarEntry> entries;

public Iterator<Resource> iterator() {
if (entries == null) {
entries = jarFile.entries();
return new Iterator<Resource>() {
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();
}
};
}
Expand All @@ -124,7 +96,7 @@ public List<CodeSigner> codeSigners() {
}

public InputStream openStream() throws IOException {
return jarFile.getInputStream(jarEntry);
return isDirectory() ? InputStream.nullInputStream() : jarFile.getInputStream(jarEntry);
}

public long size() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand All @@ -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<Resource> ds = dir2.openDirectoryStream()) {
Iterator<Resource> 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<Resource> ds = rootDir.openDirectoryStream()) {
Iterator<Resource> 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());
}
}
}

Expand Down

0 comments on commit d1450aa

Please sign in to comment.