From 87b3912518462a7fdad746b86087b33d1251ba36 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Thu, 11 Apr 2024 23:26:32 +0200 Subject: [PATCH] Add a system property to make UnionFS optional Introduce a new optional system property that causes SJH to only use UnionFS if it is necessary. UnionFS is needed if a filter or multiple source paths are defined. --- .../java/cpw/mods/jarhandling/SecureJar.java | 2 +- .../java/cpw/mods/jarhandling/impl/Jar.java | 6 ++-- .../jarhandling/impl/JarContentsImpl.java | 35 ++++++++++++++----- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java index 9432a2c..9433779 100644 --- a/src/main/java/cpw/mods/jarhandling/SecureJar.java +++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java @@ -140,7 +140,7 @@ public static Provider fromPath(final Path path, final BiPredicatel.length() > 0 && !l.startsWith("#")) // We support comments :) + .filter(l-> !l.isEmpty() && !l.startsWith("#")) // We support comments :) .filter(p-> pkgFilter == null || pkgFilter.test(p.replace('.','/'), "")) .toList(); return new Provider(sname, entries); diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java index f3827b8..35ecaf6 100644 --- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java +++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java @@ -2,13 +2,13 @@ import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; -import cpw.mods.niofs.union.UnionFileSystem; import cpw.mods.util.LambdaExceptionUtils; import org.jetbrains.annotations.Nullable; import java.io.InputStream; import java.lang.module.ModuleDescriptor; import java.net.URI; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -24,7 +24,7 @@ public class Jar implements SecureJar { private final JarContentsImpl contents; private final Manifest manifest; private final JarSigningData signingData; - private final UnionFileSystem filesystem; + private final FileSystem filesystem; private final JarModuleDataProvider moduleDataProvider; @@ -66,7 +66,7 @@ public ModuleDataProvider moduleDataProvider() { @Override public Path getPrimaryPath() { - return filesystem.getPrimaryPath(); + return contents.getPrimaryPath(); } public Optional findFile(final String name) { diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java index b69cb9e..86f69c3 100644 --- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java +++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java @@ -2,13 +2,14 @@ import cpw.mods.jarhandling.JarContents; import cpw.mods.jarhandling.SecureJar; -import cpw.mods.niofs.union.UnionFileSystem; import cpw.mods.niofs.union.UnionFileSystemProvider; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -29,6 +30,7 @@ import java.util.jar.Manifest; public class JarContentsImpl implements JarContents { + private static final boolean USE_UNION_FS_ONLY_IF_NEEDED = Boolean.getBoolean("securejarhandler.useUnionFsOnlyIfNeeded"); private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders() .stream() .filter(fsp->fsp.getScheme().equals("union")) @@ -36,7 +38,11 @@ public class JarContentsImpl implements JarContents { .orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider")); private static final Set NAUGHTY_SERVICE_FILES = Set.of("org.codehaus.groovy.runtime.ExtensionModule"); - final UnionFileSystem filesystem; + final FileSystem filesystem; + final Path rootPath; + final Path primaryPath; + @Nullable + final BiPredicate pathFilter; // Code signing data final JarSigningData signingData = new JarSigningData(); // Manifest of the jar @@ -53,7 +59,18 @@ public JarContentsImpl(Path[] paths, Supplier defaultManifest, @Nullab var validPaths = Arrays.stream(paths).filter(Files::exists).toArray(Path[]::new); if (validPaths.length == 0) throw new UncheckedIOException(new IOException("Invalid paths argument, contained no existing paths: " + Arrays.toString(paths))); - this.filesystem = UFSP.newFileSystem(pathFilter, validPaths); + if (USE_UNION_FS_ONLY_IF_NEEDED && pathFilter == null && validPaths.length == 1 && !Files.isDirectory(validPaths[0])) { + try { + this.filesystem = FileSystems.newFileSystem(validPaths[0]); + } catch (IOException e) { + throw new UncheckedIOException("Failed to open " + validPaths[0], e); + } + } else { + this.filesystem = UFSP.newFileSystem(pathFilter, validPaths); + } + this.pathFilter = pathFilter; + this.primaryPath = validPaths[0]; + this.rootPath = this.filesystem.getRootDirectories().iterator().next(); // Find the manifest, and read its signing data this.manifest = readManifestAndSigningData(defaultManifest, validPaths); // Read multi-release jar information @@ -115,7 +132,7 @@ private Map readMultiReleaseInfo() { return Map.of(); } - var vers = filesystem.getRoot().resolve("META-INF/versions"); + var vers = rootPath.resolve("META-INF/versions"); if (!Files.isDirectory(vers)) return Map.of(); try (var walk = Files.walk(vers)) { @@ -139,7 +156,7 @@ private Map readMultiReleaseInfo() { @Override public Path getPrimaryPath() { - return filesystem.getPrimaryPath(); + return primaryPath; } @Override @@ -148,7 +165,7 @@ public Optional findFile(String name) { if (this.nameOverrides.containsKey(rel)) { rel = this.filesystem.getPath("META-INF", "versions", this.nameOverrides.get(rel).toString()).resolve(rel); } - return Optional.of(this.filesystem.getRoot().resolve(rel)).filter(Files::exists).map(Path::toUri); + return Optional.of(this.rootPath.resolve(rel)).filter(Files::exists).map(Path::toUri); } @Override @@ -164,7 +181,7 @@ public Set getPackagesExcluding(String... excludedRootPackages) { Set packages = new HashSet<>(); try { - Files.walkFileTree(this.filesystem.getRoot(), new SimpleFileVisitor<>() { + Files.walkFileTree(this.rootPath, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.getFileName().toString().endsWith(".class") && attrs.isRegularFile()) { @@ -201,12 +218,12 @@ public Set getPackages() { @Override public List getMetaInfServices() { if (this.providers == null) { - final var services = this.filesystem.getRoot().resolve("META-INF/services/"); + final var services = this.rootPath.resolve("META-INF/services/"); if (Files.exists(services)) { try (var walk = Files.walk(services, 1)) { this.providers = walk.filter(path->!Files.isDirectory(path)) .filter(path -> !NAUGHTY_SERVICE_FILES.contains(path.getFileName().toString())) - .map((Path path1) -> SecureJar.Provider.fromPath(path1, filesystem.getFilesystemFilter())) + .map((Path path1) -> SecureJar.Provider.fromPath(path1, pathFilter)) .toList(); } catch (IOException e) { throw new UncheckedIOException(e);