From d4829ef904a0d434b9ce74808f492998ba78bd46 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Fri, 10 Nov 2023 23:50:26 +0100
Subject: [PATCH 01/14] Add SecureJarBuilder
---
build.gradle | 2 +
.../java/cpw/mods/jarhandling/SecureJar.java | 86 +++++++++++--------
.../mods/jarhandling/SecureJarBuilder.java | 71 +++++++++++++++
src/main/java/module-info.java | 3 +-
4 files changed, 127 insertions(+), 35 deletions(-)
create mode 100644 src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
diff --git a/build.gradle b/build.gradle
index 4579fca..c511e45 100644
--- a/build.gradle
+++ b/build.gradle
@@ -118,10 +118,12 @@ version = gradleutils.version
logger.lifecycle('Version: ' + version)
ext.asmVersion = 9.3
+ext.jbAnnotationsVersion = '22.0.0'
dependencies {
api("org.ow2.asm:asm:${asmVersion}")
api("org.ow2.asm:asm-tree:${asmVersion}")
api("org.ow2.asm:asm-commons:${asmVersion}")
+ implementation("org.jetbrains:annotations:${jbAnnotationsVersion}")
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.+')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.+')
}
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index 9af0197..2e786d1 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -19,17 +19,19 @@
import java.util.jar.Attributes;
import java.util.jar.Manifest;
+/**
+ * A secure jar is the full definition for a module,
+ * including all its paths and code signing metadata.
+ *
+ *
An instance can be built with {@link SecureJarBuilder}.
+ */
public interface SecureJar {
- interface ModuleDataProvider {
- String name();
- ModuleDescriptor descriptor();
- URI uri();
- Optional findFile(String name);
- Optional open(final String name);
-
- Manifest getManifest();
-
- CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes);
+ /**
+ * Creates a jar from a list of paths.
+ * See {@link SecureJarBuilder} for more configuration options.
+ */
+ static SecureJar from(final Path... paths) {
+ return new SecureJarBuilder().paths(paths).build();
}
ModuleDataProvider moduleDataProvider();
@@ -46,30 +48,6 @@ interface ModuleDataProvider {
boolean hasSecurityData();
- static SecureJar from(final Path... paths) {
- return from(jar -> JarMetadata.from(jar, paths), paths);
- }
-
- static SecureJar from(BiPredicate filter, final Path... paths) {
- return from(jar->JarMetadata.from(jar, paths), filter, paths);
- }
-
- static SecureJar from(Function metadataSupplier, final Path... paths) {
- return from(Manifest::new, metadataSupplier, paths);
- }
-
- static SecureJar from(Function metadataSupplier, BiPredicate filter, final Path... paths) {
- return from(Manifest::new, metadataSupplier, filter, paths);
- }
-
- static SecureJar from(Supplier defaultManifest, Function metadataSupplier, final Path... paths) {
- return from(defaultManifest, metadataSupplier, null, paths);
- }
-
- static SecureJar from(Supplier defaultManifest, Function metadataSupplier, BiPredicate filter, final Path... paths) {
- return new Jar(defaultManifest, metadataSupplier, filter, paths);
- }
-
Set getPackages();
List getProviders();
@@ -80,6 +58,18 @@ static SecureJar from(Supplier defaultManifest, Function findFile(String name);
+ Optional open(final String name);
+
+ Manifest getManifest();
+
+ CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes);
+ }
+
record Provider(String serviceName, List providers) {
public static Provider fromPath(final Path path, final BiPredicate pkgFilter) {
final var sname = path.getFileName().toString();
@@ -99,4 +89,32 @@ public static Provider fromPath(final Path path, final BiPredicate filter, final Path... paths) {
+ return from(jar->JarMetadata.from(jar, paths), filter, paths);
+ }
+
+ @Deprecated(forRemoval = true)
+ static SecureJar from(Function metadataSupplier, final Path... paths) {
+ return from(Manifest::new, metadataSupplier, paths);
+ }
+
+ @Deprecated(forRemoval = true)
+ static SecureJar from(Function metadataSupplier, BiPredicate filter, final Path... paths) {
+ return from(Manifest::new, metadataSupplier, filter, paths);
+ }
+
+ @Deprecated(forRemoval = true)
+ static SecureJar from(Supplier defaultManifest, Function metadataSupplier, final Path... paths) {
+ return from(defaultManifest, metadataSupplier, null, paths);
+ }
+
+ @Deprecated(forRemoval = true)
+ static SecureJar from(Supplier defaultManifest, Function metadataSupplier, BiPredicate filter, final Path... paths) {
+ return new Jar(defaultManifest, metadataSupplier, filter, paths);
+ }
}
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
new file mode 100644
index 0000000..b0bd252
--- /dev/null
+++ b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
@@ -0,0 +1,71 @@
+package cpw.mods.jarhandling;
+
+import cpw.mods.jarhandling.impl.Jar;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.jar.Manifest;
+
+public final class SecureJarBuilder {
+ private Supplier defaultManifest = Manifest::new;
+ @Nullable
+ private BiPredicate pathFilter = null;
+ private Function metadataSupplier = null;
+ private Path[] paths = new Path[0];
+
+ public SecureJarBuilder() {}
+
+ /**
+ * Overrides the default manifest for this jar.
+ * The default manifest is only used when the jar does not provide a manifest already.
+ */
+ public SecureJarBuilder defaultManifest(Supplier manifest) {
+ Objects.requireNonNull(manifest);
+
+ this.defaultManifest = manifest;
+ return this;
+ }
+
+ /**
+ * Overrides the path filter for this jar.
+ * TODO: wtf are the arguments passed to the path filter
+ */
+ public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFilter) {
+ this.pathFilter = pathFilter;
+ return this;
+ }
+
+ /**
+ * Overrides the {@link JarMetadata} for this jar.
+ * TODO: this function is
+ */
+ public SecureJarBuilder metadata(Function metadataSupplier) {
+ Objects.requireNonNull(metadataSupplier);
+
+ this.metadataSupplier = metadataSupplier;
+ return this;
+ }
+
+ /**
+ * Sets the paths for the files of this jar.
+ */
+ public SecureJarBuilder paths(Path... paths) {
+ this.paths = paths;
+ return this;
+ }
+
+ /**
+ * Builds the jar.
+ */
+ public SecureJar build() {
+ if (metadataSupplier == null) {
+ metadataSupplier = jar -> JarMetadata.from(jar, paths);
+ }
+
+ return new Jar(defaultManifest, metadataSupplier, pathFilter, paths);
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 2aac894..6ad8598 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -11,7 +11,8 @@
requires org.objectweb.asm;
requires org.objectweb.asm.tree;
requires java.base;
+ requires static org.jetbrains.annotations;
provides java.nio.file.spi.FileSystemProvider with UnionFileSystemProvider;
uses cpw.mods.cl.ModularURLHandler.IURLProvider;
provides ModularURLHandler.IURLProvider with UnionURLStreamHandler;
-}
\ No newline at end of file
+}
From a40e0ab5cfd789f1a9f2d05bdbb06c9161087d9d Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sat, 11 Nov 2023 11:28:40 +0100
Subject: [PATCH 02/14] Split SecureJar implementation across new JarContents
and JarSigningData
---
.../cpw/mods/jarhandling/JarContents.java | 45 ++++
.../cpw/mods/jarhandling/JarMetadata.java | 43 +++-
.../java/cpw/mods/jarhandling/SecureJar.java | 25 +-
.../mods/jarhandling/SecureJarBuilder.java | 14 +-
.../java/cpw/mods/jarhandling/impl/Jar.java | 213 ++++--------------
.../jarhandling/impl/JarContentsImpl.java | 167 ++++++++++++++
.../mods/jarhandling/impl/JarSigningData.java | 111 +++++++++
.../jarhandling/impl/ModuleJarMetadata.java | 5 +-
.../jarhandling/impl/SecureJarVerifier.java | 3 +
.../jarhandling/impl/SimpleJarMetadata.java | 3 +
.../cpw/mods/niofs/union/UnionFileSystem.java | 6 +-
.../niofs/union/UnionFileSystemProvider.java | 6 +-
.../impl/TestSecureJarLoading.java | 12 +-
13 files changed, 451 insertions(+), 202 deletions(-)
create mode 100644 src/main/java/cpw/mods/jarhandling/JarContents.java
create mode 100644 src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
create mode 100644 src/main/java/cpw/mods/jarhandling/impl/JarSigningData.java
diff --git a/src/main/java/cpw/mods/jarhandling/JarContents.java b/src/main/java/cpw/mods/jarhandling/JarContents.java
new file mode 100644
index 0000000..ce2e33a
--- /dev/null
+++ b/src/main/java/cpw/mods/jarhandling/JarContents.java
@@ -0,0 +1,45 @@
+package cpw.mods.jarhandling;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+/**
+ * Access to the contents of a list of {@link Path}s, interpreted as a jar file.
+ * Typically used to build the {@linkplain JarMetadata metadata} for a {@link SecureJar}.
+ */
+@ApiStatus.NonExtendable
+public interface JarContents {
+ /**
+ * @see SecureJar#getPrimaryPath()
+ */
+ // TODO: document what this is actually used for
+ Path getPrimaryPath();
+
+ /**
+ * Looks for a file in the jar.
+ */
+ Optional findFile(String name);
+
+ /**
+ * Gets the manifest of the jar.
+ * Empty if no manifest is present in the jar.
+ */
+ Manifest getManifest();
+
+ /**
+ * Gets all the packages in the jar.
+ * (Every folder containing a {@code .class} file is considered a package.)
+ */
+ Set getPackages();
+
+ /**
+ * Parses the {@code META-INF/services} files in the jar, and returns the list of service providers.
+ */
+ List getMetaInfServices();
+}
diff --git a/src/main/java/cpw/mods/jarhandling/JarMetadata.java b/src/main/java/cpw/mods/jarhandling/JarMetadata.java
index f46f243..dc02498 100644
--- a/src/main/java/cpw/mods/jarhandling/JarMetadata.java
+++ b/src/main/java/cpw/mods/jarhandling/JarMetadata.java
@@ -33,16 +33,22 @@ public interface JarMetadata {
"volatile","const","float","native","super","while");
Pattern KEYWORD_PARTS = Pattern.compile("(?<=^|\\.)(" + String.join("|", ILLEGAL_KEYWORDS) + ")(?=\\.|$)");
- static JarMetadata from(final SecureJar jar, final Path... path) {
- if (path.length==0) throw new IllegalArgumentException("Need at least one path");
+ /**
+ * Builds the jar metadata for a jar following the normal rules for Java jars.
+ *
+ * If the jar has a {@code module-info.class} file, the module info is read from there.
+ * Otherwise, the jar is an automatic module, whose name is optionally derived
+ * from {@code Automatic-Module-Name} in the manifest.
+ */
+ static JarMetadata from(JarContents jar) {
final var pkgs = jar.getPackages();
- var mi = jar.moduleDataProvider().findFile("module-info.class");
+ var mi = jar.findFile("module-info.class");
if (mi.isPresent()) {
return new ModuleJarMetadata(mi.get(), pkgs);
} else {
- var providers = jar.getProviders();
- var fileCandidate = fromFileName(path[0], pkgs, providers);
- var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue("Automatic-Module-Name");
+ var providers = jar.getMetaInfServices();
+ var fileCandidate = fromFileName(jar.getPrimaryPath(), pkgs, providers);
+ var aname = jar.getManifest().getMainAttributes().getValue("Automatic-Module-Name");
if (aname != null) {
return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
} else {
@@ -50,8 +56,8 @@ static JarMetadata from(final SecureJar jar, final Path... path) {
}
}
}
- static SimpleJarMetadata fromFileName(final Path path, final Set pkgs, final List providers) {
+ static SimpleJarMetadata fromFileName(final Path path, final Set pkgs, final List providers) {
// detect Maven-like paths
Path versionMaybe = path.getParent();
if (versionMaybe != null)
@@ -137,4 +143,27 @@ private static String cleanModuleName(String mn) {
return mn;
}
+
+ /**
+ * @deprecated Use {@link #from(JarContents)} instead.
+ * TODO: add since
+ */
+ @Deprecated(forRemoval = true)
+ static JarMetadata from(final SecureJar jar, final Path... path) {
+ if (path.length==0) throw new IllegalArgumentException("Need at least one path");
+ final var pkgs = jar.getPackages();
+ var mi = jar.moduleDataProvider().findFile("module-info.class");
+ if (mi.isPresent()) {
+ return new ModuleJarMetadata(mi.get(), pkgs);
+ } else {
+ var providers = jar.getProviders();
+ var fileCandidate = fromFileName(path[0], pkgs, providers);
+ var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue("Automatic-Module-Name");
+ if (aname != null) {
+ return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
+ } else {
+ return fileCandidate;
+ }
+ }
+ }
}
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index 2e786d1..095f3e5 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -1,6 +1,7 @@
package cpw.mods.jarhandling;
import cpw.mods.jarhandling.impl.Jar;
+import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
@@ -36,21 +37,30 @@ static SecureJar from(final Path... paths) {
ModuleDataProvider moduleDataProvider();
+ /**
+ * A {@link SecureJar} can be built from multiple paths, either to directories or to {@code .jar} archives.
+ * This function returns the first of these paths, either to a directory or to an archive file.
+ */
Path getPrimaryPath();
+ /**
+ * {@return the signers of the manifest, or {@code null} if the manifest is not signed}
+ */
+ @Nullable
CodeSigner[] getManifestSigners();
Status verifyPath(Path path);
Status getFileStatus(String name);
+ @Nullable
Attributes getTrustedManifestEntries(String name);
boolean hasSecurityData();
- Set getPackages();
-
- List getProviders();
+ default Set getPackages() {
+ return moduleDataProvider().descriptor().packages();
+ }
String name();
@@ -90,9 +100,16 @@ enum Status {
NONE, INVALID, UNVERIFIED, VERIFIED
}
- // The methods below are deprecated for removal - use SecureJarBuilder instead!
// TODO: add since
+ /**
+ * @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
+ */
+ @Deprecated(forRemoval = true)
+ List getProviders();
+
+ // The members below are deprecated for removal - use SecureJarBuilder instead!
+
@Deprecated(forRemoval = true)
static SecureJar from(BiPredicate filter, final Path... paths) {
return from(jar->JarMetadata.from(jar, paths), filter, paths);
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
index b0bd252..348f4f3 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
@@ -1,6 +1,7 @@
package cpw.mods.jarhandling;
import cpw.mods.jarhandling.impl.Jar;
+import cpw.mods.jarhandling.impl.JarContentsImpl;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
@@ -14,7 +15,7 @@ public final class SecureJarBuilder {
private Supplier defaultManifest = Manifest::new;
@Nullable
private BiPredicate pathFilter = null;
- private Function metadataSupplier = null;
+ private Function metadataSupplier = JarMetadata::from;
private Path[] paths = new Path[0];
public SecureJarBuilder() {}
@@ -41,9 +42,8 @@ public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFil
/**
* Overrides the {@link JarMetadata} for this jar.
- * TODO: this function is
*/
- public SecureJarBuilder metadata(Function metadataSupplier) {
+ public SecureJarBuilder metadata(Function metadataSupplier) {
Objects.requireNonNull(metadataSupplier);
this.metadataSupplier = metadataSupplier;
@@ -62,10 +62,8 @@ public SecureJarBuilder paths(Path... paths) {
* Builds the jar.
*/
public SecureJar build() {
- if (metadataSupplier == null) {
- metadataSupplier = jar -> JarMetadata.from(jar, paths);
- }
-
- return new Jar(defaultManifest, metadataSupplier, pathFilter, paths);
+ JarContentsImpl contents = new JarContentsImpl(defaultManifest, pathFilter, paths);
+ JarMetadata metadata = metadataSupplier.apply(contents);
+ return new Jar(contents, metadata);
}
}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
index 520b637..0766475 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
@@ -3,45 +3,53 @@
import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.niofs.union.UnionFileSystem;
-import cpw.mods.niofs.union.UnionFileSystemProvider;
import cpw.mods.util.LambdaExceptionUtils;
+import org.jetbrains.annotations.Nullable;
-import java.io.IOException;
import java.io.InputStream;
-import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
import java.security.CodeSigner;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.Attributes;
-import java.util.jar.JarFile;
-import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
-import static java.util.stream.Collectors.*;
-
public class Jar implements SecureJar {
- private static final CodeSigner[] EMPTY_CODESIGNERS = new CodeSigner[0];
- private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders().stream().filter(fsp->fsp.getScheme().equals("union")).findFirst().orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));
+ private final JarContentsImpl contents;
private final Manifest manifest;
- private final Hashtable pendingSigners = new Hashtable<>();
- private final Hashtable verifiedSigners = new Hashtable<>();
- private final ManifestVerifier verifier = new ManifestVerifier();
- private final Map statusData = new HashMap<>();
- private final JarMetadata metadata;
+ private final JarSigningData signingData;
private final UnionFileSystem filesystem;
- private final boolean isMultiRelease;
- private final Map nameOverrides;
+
private final JarModuleDataProvider moduleDataProvider;
- private Set packages;
- private List providers;
+
+ private final JarMetadata metadata;
+
+ @Deprecated(forRemoval = true)
+ public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
+ this.contents = new JarContentsImpl(defaultManifest, pathfilter, paths);
+ this.manifest = contents.getManifest();
+ this.signingData = contents.signingData;
+ this.filesystem = contents.filesystem;
+
+ this.moduleDataProvider = new JarModuleDataProvider(this);
+ this.metadata = metadataFunction.apply(this);
+ }
+
+ public Jar(JarContentsImpl contents, JarMetadata metadata) {
+ this.contents = contents;
+ this.manifest = contents.getManifest();
+ this.signingData = contents.signingData;
+ this.filesystem = contents.filesystem;
+
+ this.moduleDataProvider = new JarModuleDataProvider(this);
+ this.metadata = metadata;
+ }
public URI getURI() {
return this.filesystem.getRootDirectories().iterator().next().toUri();
@@ -62,148 +70,36 @@ public Path getPrimaryPath() {
}
public Optional findFile(final String name) {
- var rel = filesystem.getPath(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);
- }
-
- private record StatusData(String name, Status status, CodeSigner[] signers) {
- static void add(final String name, final Status status, final CodeSigner[] signers, Jar jar) {
- jar.statusData.put(name, new StatusData(name, status, signers));
- }
- }
-
- @SuppressWarnings("unchecked")
- public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
- 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.moduleDataProvider = new JarModuleDataProvider(this);
- this.filesystem = UFSP.newFileSystem(pathfilter, validPaths);
- try {
- Manifest mantmp = null;
- for (int x = validPaths.length - 1; x >= 0; x--) { // Walk backwards because this is what cpw wanted?
- var path = validPaths[x];
- if (Files.isDirectory(path)) {
- var manfile = path.resolve(JarFile.MANIFEST_NAME);
- if (Files.exists(manfile)) {
- try (var is = Files.newInputStream(manfile)) {
- mantmp = new Manifest(is);
- break;
- }
- }
- } else {
- try (var jis = new JarInputStream(Files.newInputStream(path))) {
- var jv = SecureJarVerifier.getJarVerifier(jis);
- if (jv != null) {
- while (SecureJarVerifier.isParsingMeta(jv)) {
- if (jis.getNextJarEntry() == null) break;
- }
-
- if (SecureJarVerifier.hasSignatures(jv)) {
- pendingSigners.putAll(SecureJarVerifier.getPendingSigners(jv));
- var manifestSigners = SecureJarVerifier.getVerifiedSigners(jv).get(JarFile.MANIFEST_NAME);
- if (manifestSigners != null) verifiedSigners.put(JarFile.MANIFEST_NAME, manifestSigners);
- StatusData.add(JarFile.MANIFEST_NAME, Status.VERIFIED, verifiedSigners.get(JarFile.MANIFEST_NAME), this);
- }
- }
-
- if (jis.getManifest() != null) {
- mantmp = new Manifest(jis.getManifest());
- break;
- }
- }
- }
- }
- this.manifest = mantmp == null ? defaultManifest.get() : mantmp;
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- this.isMultiRelease = Boolean.parseBoolean(getManifest().getMainAttributes().getValue("Multi-Release"));
- if (this.isMultiRelease) {
- var vers = filesystem.getRoot().resolve("META-INF/versions");
- try (var walk = Files.walk(vers)){
- var allnames = walk.filter(p1 ->!p1.isAbsolute())
- .filter(path1 -> !Files.isDirectory(path1))
- .map(p1 -> p1.subpath(2, p1.getNameCount()))
- .collect(groupingBy(p->p.subpath(1, p.getNameCount()),
- mapping(p->Integer.parseInt(p.getName(0).toString()), toUnmodifiableList())));
- this.nameOverrides = allnames.entrySet().stream()
- .map(e->Map.entry(e.getKey(), e.getValue().stream().reduce(Integer::max).orElse(8)))
- .filter(e-> e.getValue() < Runtime.version().feature())
- .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- } else {
- this.nameOverrides = Map.of();
- }
- this.metadata = metadataFunction.apply(this);
- }
-
- public Manifest getManifest() {
- return manifest;
+ return contents.findFile(name);
}
@Override
+ @Nullable
public CodeSigner[] getManifestSigners() {
- return getData(JarFile.MANIFEST_NAME).map(r->r.signers).orElse(null);
- }
-
- public synchronized CodeSigner[] verifyAndGetSigners(final String name, final byte[] bytes) {
- if (!hasSecurityData()) return null;
- if (statusData.containsKey(name)) return statusData.get(name).signers;
-
- var signers = verifier.verify(this.manifest, pendingSigners, verifiedSigners, name, bytes);
- if (signers == null) {
- StatusData.add(name, Status.INVALID, null, this);
- return null;
- } else {
- var ret = signers.orElse(null);
- StatusData.add(name, Status.VERIFIED, ret, this);
- return ret;
- }
+ return signingData.getManifestSigners();
}
@Override
public Status verifyPath(final Path path) {
if (path.getFileSystem() != filesystem) throw new IllegalArgumentException("Wrong filesystem");
final var pathname = path.toString();
- if (statusData.containsKey(pathname)) return getFileStatus(pathname);
- try {
- var bytes = Files.readAllBytes(path);
- verifyAndGetSigners(pathname, bytes);
- return getFileStatus(pathname);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private Optional getData(final String name) {
- return Optional.ofNullable(statusData.get(name));
+ return signingData.verifyPath(manifest, path, pathname);
}
@Override
public Status getFileStatus(final String name) {
- return hasSecurityData() ? getData(name).map(r->r.status).orElse(Status.NONE) : Status.UNVERIFIED;
+ return signingData.getFileStatus(name);
}
@Override
+ @Nullable
public Attributes getTrustedManifestEntries(final String name) {
- var manattrs = manifest.getAttributes(name);
- var mansigners = getManifestSigners();
- var objsigners = getData(name).map(sd->sd.signers).orElse(EMPTY_CODESIGNERS);
- if (mansigners == null || (mansigners.length == objsigners.length)) {
- return manattrs;
- } else {
- return null;
- }
+ return signingData.getTrustedManifestEntries(manifest, name);
}
+
@Override
public boolean hasSecurityData() {
- return !pendingSigners.isEmpty() || !this.verifiedSigners.isEmpty();
+ return signingData.hasSecurityData();
}
@Override
@@ -213,41 +109,12 @@ public String name() {
@Override
public Set getPackages() {
- if (this.packages == null) {
- try (var walk = Files.walk(this.filesystem.getRoot())) {
- this.packages = walk
- .filter(path->path.getNameCount()>0)
- .filter(path->!path.getName(0).toString().equals("META-INF"))
- .filter(path->path.getFileName().toString().endsWith(".class"))
- .filter(Files::isRegularFile)
- .map(Path::getParent)
- .map(path->path.toString().replace('/','.'))
- .filter(pkg->pkg.length()!=0)
- .collect(toSet());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- return this.packages;
+ return contents.getPackages();
}
@Override
public List getProviders() {
- if (this.providers == null) {
- final var services = this.filesystem.getRoot().resolve("META-INF/services/");
- if (Files.exists(services)) {
- try (var walk = Files.walk(services)) {
- this.providers = walk.filter(path->!Files.isDirectory(path))
- .map((Path path1) -> Provider.fromPath(path1, filesystem.getFilesystemFilter()))
- .toList();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- } else {
- this.providers = List.of();
- }
- }
- return this.providers;
+ return contents.getMetaInfServices();
}
@Override
@@ -293,12 +160,12 @@ public Optional open(final String name) {
@Override
public Manifest getManifest() {
- return jar.getManifest();
+ return jar.manifest;
}
@Override
public CodeSigner[] verifyAndGetSigners(final String cname, final byte[] bytes) {
- return jar.verifyAndGetSigners(cname, bytes);
+ return jar.signingData.verifyAndGetSigners(jar.manifest, cname, bytes);
}
}
}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
new file mode 100644
index 0000000..936a598
--- /dev/null
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -0,0 +1,167 @@
+package cpw.mods.jarhandling.impl;
+
+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.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Supplier;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import static java.util.stream.Collectors.*;
+
+public class JarContentsImpl implements JarContents {
+ private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders()
+ .stream()
+ .filter(fsp->fsp.getScheme().equals("union"))
+ .findFirst()
+ .orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));
+
+ final UnionFileSystem filesystem;
+ // Code signing data
+ final JarSigningData signingData = new JarSigningData();
+ // Manifest of the jar
+ private final Manifest manifest;
+ // Name overrides, if the jar is a multi-release jar
+ private final Map nameOverrides;
+
+ // Cache for repeated getPackages calls
+ private Set packages;
+ // Cache for repeated getMetaInfServices calls
+ private List providers;
+
+ public JarContentsImpl(Supplier defaultManifest, @Nullable BiPredicate pathFilter, Path[] paths) {
+ 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);
+ // Find the manifest, and read its signing data
+ this.manifest = readManifestAndSigningData(defaultManifest, validPaths);
+ // Read multi-release jar information
+ this.nameOverrides = readMultiReleaseInfo();
+ }
+
+ private Manifest readManifestAndSigningData(Supplier defaultManifest, Path[] validPaths) {
+ try {
+ for (int x = validPaths.length - 1; x >= 0; x--) { // Walk backwards because this is what cpw wanted?
+ var path = validPaths[x];
+ if (Files.isDirectory(path)) {
+ // Just a directory: read the manifest file, but don't do any signature verification
+ var manfile = path.resolve(JarFile.MANIFEST_NAME);
+ if (Files.exists(manfile)) {
+ try (var is = Files.newInputStream(manfile)) {
+ return new Manifest(is);
+ }
+ }
+ } else {
+ try (var jis = new JarInputStream(Files.newInputStream(path))) {
+ // Jar file: use the signature verification code
+ signingData.readJarSigningData(jis);
+
+ if (jis.getManifest() != null) {
+ return new Manifest(jis.getManifest());
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ return defaultManifest.get();
+ }
+
+ private Map readMultiReleaseInfo() {
+ boolean isMultiRelease = Boolean.parseBoolean(getManifest().getMainAttributes().getValue("Multi-Release"));
+ if (isMultiRelease) {
+ var vers = filesystem.getRoot().resolve("META-INF/versions");
+ try (var walk = Files.walk(vers)){
+ var allnames = walk.filter(p1 ->!p1.isAbsolute())
+ .filter(path1 -> !Files.isDirectory(path1))
+ .map(p1 -> p1.subpath(2, p1.getNameCount()))
+ .collect(groupingBy(p->p.subpath(1, p.getNameCount()),
+ mapping(p->Integer.parseInt(p.getName(0).toString()), toUnmodifiableList())));
+ return allnames.entrySet().stream()
+ .map(e->Map.entry(e.getKey(), e.getValue().stream().reduce(Integer::max).orElse(8)))
+ .filter(e-> e.getValue() < Runtime.version().feature())
+ .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ } else {
+ return Map.of();
+ }
+ }
+
+ @Override
+ public Path getPrimaryPath() {
+ return filesystem.getPrimaryPath();
+ }
+
+ @Override
+ public Optional findFile(String name) {
+ var rel = filesystem.getPath(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);
+ }
+
+ @Override
+ public Manifest getManifest() {
+ return manifest;
+ }
+
+ @Override
+ public Set getPackages() {
+ if (this.packages == null) {
+ try (var walk = Files.walk(this.filesystem.getRoot())) {
+ this.packages = walk
+ .filter(path->path.getNameCount()>0)
+ .filter(path->!path.getName(0).toString().equals("META-INF"))
+ .filter(path->path.getFileName().toString().endsWith(".class"))
+ .filter(Files::isRegularFile)
+ .map(Path::getParent)
+ .map(path->path.toString().replace('/','.'))
+ .filter(pkg->pkg.length()!=0)
+ .collect(toSet());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ return this.packages;
+ }
+
+ @Override
+ public List getMetaInfServices() {
+ if (this.providers == null) {
+ final var services = this.filesystem.getRoot().resolve("META-INF/services/");
+ if (Files.exists(services)) {
+ try (var walk = Files.walk(services)) {
+ this.providers = walk.filter(path->!Files.isDirectory(path))
+ .map((Path path1) -> SecureJar.Provider.fromPath(path1, filesystem.getFilesystemFilter()))
+ .toList();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ } else {
+ this.providers = List.of();
+ }
+ }
+ return this.providers;
+ }
+}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarSigningData.java b/src/main/java/cpw/mods/jarhandling/impl/JarSigningData.java
new file mode 100644
index 0000000..1ba3d3a
--- /dev/null
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarSigningData.java
@@ -0,0 +1,111 @@
+package cpw.mods.jarhandling.impl;
+
+import cpw.mods.jarhandling.SecureJar;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.CodeSigner;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Optional;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+/**
+ * The signing data for a {@link Jar}.
+ */
+public class JarSigningData {
+ private static final CodeSigner[] EMPTY_CODESIGNERS = new CodeSigner[0];
+
+ private final Hashtable pendingSigners = new Hashtable<>();
+ private final Hashtable verifiedSigners = new Hashtable<>();
+ private final ManifestVerifier verifier = new ManifestVerifier();
+ private final Map statusData = new HashMap<>();
+
+ record StatusData(String name, SecureJar.Status status, CodeSigner[] signers) {
+ static void add(final String name, final SecureJar.Status status, final CodeSigner[] signers, JarSigningData data) {
+ data.statusData.put(name, new StatusData(name, status, signers));
+ }
+ }
+
+ /**
+ * Read signing data from a {@link JarInputStream}.
+ * For now this is the only way of reading signing data.
+ */
+ void readJarSigningData(JarInputStream jis) throws IOException {
+ var jv = SecureJarVerifier.getJarVerifier(jis);
+ if (jv != null) {
+ while (SecureJarVerifier.isParsingMeta(jv)) {
+ if (jis.getNextJarEntry() == null) break;
+ }
+
+ if (SecureJarVerifier.hasSignatures(jv)) {
+ pendingSigners.putAll(SecureJarVerifier.getPendingSigners(jv));
+ var manifestSigners = SecureJarVerifier.getVerifiedSigners(jv).get(JarFile.MANIFEST_NAME);
+ if (manifestSigners != null) verifiedSigners.put(JarFile.MANIFEST_NAME, manifestSigners);
+ StatusData.add(JarFile.MANIFEST_NAME, SecureJar.Status.VERIFIED, verifiedSigners.get(JarFile.MANIFEST_NAME), this);
+ }
+ }
+ }
+
+ @Nullable
+ CodeSigner[] getManifestSigners() {
+ return getData(JarFile.MANIFEST_NAME).map(r->r.signers).orElse(null);
+ }
+
+ SecureJar.Status verifyPath(Manifest manifest, Path path, String filename) {
+ if (statusData.containsKey(filename)) return getFileStatus(filename);
+ try {
+ var bytes = Files.readAllBytes(path);
+ verifyAndGetSigners(manifest, filename, bytes);
+ return getFileStatus(filename);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ SecureJar.Status getFileStatus(String name) {
+ return hasSecurityData() ? getData(name).map(r->r.status).orElse(SecureJar.Status.NONE) : SecureJar.Status.UNVERIFIED;
+ }
+
+ @Nullable
+ Attributes getTrustedManifestEntries(Manifest manifest, String name) {
+ var manattrs = manifest.getAttributes(name);
+ var mansigners = getManifestSigners();
+ var objsigners = getData(name).map(sd->sd.signers).orElse(EMPTY_CODESIGNERS);
+ if (mansigners == null || (mansigners.length == objsigners.length)) {
+ return manattrs;
+ } else {
+ return null;
+ }
+ }
+
+ boolean hasSecurityData() {
+ return !pendingSigners.isEmpty() || !this.verifiedSigners.isEmpty();
+ }
+
+ private Optional getData(final String name) {
+ return Optional.ofNullable(statusData.get(name));
+ }
+
+ synchronized CodeSigner[] verifyAndGetSigners(Manifest manifest, String name, byte[] bytes) {
+ if (!hasSecurityData()) return null;
+ if (statusData.containsKey(name)) return statusData.get(name).signers;
+
+ var signers = verifier.verify(manifest, pendingSigners, verifiedSigners, name, bytes);
+ if (signers == null) {
+ StatusData.add(name, SecureJar.Status.INVALID, null, this);
+ return null;
+ } else {
+ var ret = signers.orElse(null);
+ StatusData.add(name, SecureJar.Status.VERIFIED, ret, this);
+ return ret;
+ }
+ }
+}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java b/src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java
index 08a15f9..9956d13 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java
@@ -16,7 +16,10 @@
import java.util.HashSet;
import java.util.Set;
-
+/**
+ * {@link JarMetadata} implementation for a modular jar.
+ * Reads the module descriptor from the jar.
+ */
public class ModuleJarMetadata implements JarMetadata {
private final ModuleDescriptor descriptor;
diff --git a/src/main/java/cpw/mods/jarhandling/impl/SecureJarVerifier.java b/src/main/java/cpw/mods/jarhandling/impl/SecureJarVerifier.java
index 42ac5bf..25e5dfa 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/SecureJarVerifier.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/SecureJarVerifier.java
@@ -8,6 +8,9 @@
import java.util.Map;
import java.util.jar.JarInputStream;
+/**
+ * Reflection / Unsafe wrapper class around the unexposed {@link java.util.jar.JarVerifier}.
+ */
public class SecureJarVerifier {
private static final boolean USE_UNSAAFE = Boolean.parseBoolean(System.getProperty("securejarhandler.useUnsafeAccessor", "true"));
private static IAccessor ACCESSOR = USE_UNSAAFE ? new UnsafeAccessor() : new Reflection();
diff --git a/src/main/java/cpw/mods/jarhandling/impl/SimpleJarMetadata.java b/src/main/java/cpw/mods/jarhandling/impl/SimpleJarMetadata.java
index 00e1a45..1ded493 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/SimpleJarMetadata.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/SimpleJarMetadata.java
@@ -7,6 +7,9 @@
import java.util.List;
import java.util.Set;
+/**
+ * {@link JarMetadata} implementation for a non-modular jar, turning it into an automatic module.
+ */
public record SimpleJarMetadata(String name, String version, Set pkgs, List providers) implements JarMetadata {
@Override
public ModuleDescriptor descriptor() {
diff --git a/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java b/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java
index 42f7aec..2f0ca75 100644
--- a/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java
+++ b/src/main/java/cpw/mods/niofs/union/UnionFileSystem.java
@@ -1,5 +1,7 @@
package cpw.mods.niofs.union;
+import org.jetbrains.annotations.Nullable;
+
import java.io.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -101,6 +103,7 @@ public synchronized Throwable fillInStackTrace() {
private final String key;
private final List basepaths;
private final int lastElementIndex;
+ @Nullable
private final BiPredicate pathFilter;
private final Map embeddedFileSystems;
@@ -108,6 +111,7 @@ public Path getPrimaryPath() {
return basepaths.get(basepaths.size() - 1);
}
+ @Nullable
public BiPredicate getFilesystemFilter() {
return pathFilter;
}
@@ -119,7 +123,7 @@ String getKey() {
private record EmbeddedFileSystemMetadata(Path path, FileSystem fs, SeekableByteChannel fsCh) {
}
- public UnionFileSystem(final UnionFileSystemProvider provider, final BiPredicate pathFilter, final String key, final Path... basepaths) {
+ public UnionFileSystem(final UnionFileSystemProvider provider, @Nullable BiPredicate pathFilter, final String key, final Path... basepaths) {
this.pathFilter = pathFilter;
this.provider = provider;
this.key = key;
diff --git a/src/main/java/cpw/mods/niofs/union/UnionFileSystemProvider.java b/src/main/java/cpw/mods/niofs/union/UnionFileSystemProvider.java
index ea4f7df..ba0a689 100644
--- a/src/main/java/cpw/mods/niofs/union/UnionFileSystemProvider.java
+++ b/src/main/java/cpw/mods/niofs/union/UnionFileSystemProvider.java
@@ -1,5 +1,7 @@
package cpw.mods.niofs.union;
+import org.jetbrains.annotations.Nullable;
+
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
@@ -99,13 +101,13 @@ public FileSystem newFileSystem(final Path path, final Map env) throw
}
}
- public UnionFileSystem newFileSystem(final BiPredicate pathfilter, final Path... paths) {
+ public UnionFileSystem newFileSystem(@Nullable BiPredicate pathfilter, final Path... paths) {
if (paths.length == 0) throw new IllegalArgumentException("Need at least one path");
var key = makeKey(paths[0]);
return newFileSystemInternal(key, pathfilter, paths);
}
- private UnionFileSystem newFileSystemInternal(final String key, final BiPredicate pathfilter, final Path... paths) {
+ private UnionFileSystem newFileSystemInternal(final String key, @Nullable BiPredicate pathfilter, final Path... paths) {
var normpaths = Arrays.stream(paths)
.map(Path::toAbsolutePath)
.map(Path::normalize)
diff --git a/src/test/java/cpw/mods/jarhandling/impl/TestSecureJarLoading.java b/src/test/java/cpw/mods/jarhandling/impl/TestSecureJarLoading.java
index ed8596d..5df9ccd 100644
--- a/src/test/java/cpw/mods/jarhandling/impl/TestSecureJarLoading.java
+++ b/src/test/java/cpw/mods/jarhandling/impl/TestSecureJarLoading.java
@@ -31,7 +31,7 @@ void testSecureJar() throws Exception {
if (SecureJarVerifier.isSigningRelated(ze.getName())) continue;
if (ze.isDirectory()) continue;
final var zeName = ze.getName();
- var cs = ((Jar)jar).verifyAndGetSigners(ze.getName(), zis.readAllBytes());
+ var cs = jar.moduleDataProvider().verifyAndGetSigners(ze.getName(), zis.readAllBytes());
jar.getTrustedManifestEntries(zeName);
assertAll("Behaves as a properly secured JAR",
()->assertNotNull(cs, "Has code signers array"),
@@ -53,7 +53,7 @@ void testInsecureJar() throws Exception {
if (SecureJarVerifier.isSigningRelated(ze.getName())) continue;
if (ze.isDirectory()) continue;
final var zeName = ze.getName();
- var cs = ((Jar)jar).verifyAndGetSigners(ze.getName(), zis.readAllBytes());
+ var cs = jar.moduleDataProvider().verifyAndGetSigners(ze.getName(), zis.readAllBytes());
assertAll("Jar behaves correctly",
()->assertNull(cs, "No code signers")
);
@@ -83,7 +83,7 @@ void testTampered() throws Exception {
SecureJar jar = SecureJar.from(path);
ZipFile zf = new ZipFile(path.toFile());
final var entry = zf.getEntry("test/Signed.class");
- var cs = ((Jar)jar).verifyAndGetSigners(entry.getName(), zf.getInputStream(entry).readAllBytes());
+ var cs = jar.moduleDataProvider().verifyAndGetSigners(entry.getName(), zf.getInputStream(entry).readAllBytes());
assertNull(cs);
}
@@ -93,7 +93,7 @@ void testPartial() throws Exception {
SecureJar jar = SecureJar.from(path);
ZipFile zf = new ZipFile(path.toFile());
final var sentry = zf.getEntry("test/Signed.class");
- final var scs = ((Jar)jar).verifyAndGetSigners(sentry.getName(), zf.getInputStream(sentry).readAllBytes());
+ final var scs = jar.moduleDataProvider().verifyAndGetSigners(sentry.getName(), zf.getInputStream(sentry).readAllBytes());
assertAll("Behaves as a properly secured JAR",
()->assertNotNull(scs, "Has code signers array"),
()->assertTrue(scs.length>0, "With length > 0"),
@@ -101,7 +101,7 @@ void testPartial() throws Exception {
()->assertNotNull(jar.getTrustedManifestEntries(sentry.getName()), "Has trusted manifest entries")
);
final var uentry = zf.getEntry("test/UnSigned.class");
- final var ucs = ((Jar)jar).verifyAndGetSigners(uentry.getName(), zf.getInputStream(uentry).readAllBytes());
+ final var ucs = jar.moduleDataProvider().verifyAndGetSigners(uentry.getName(), zf.getInputStream(uentry).readAllBytes());
assertNull(ucs);
}
@@ -115,7 +115,7 @@ void testEmptyJar() throws Exception {
if (SecureJarVerifier.isSigningRelated(ze.getName())) continue;
if (ze.isDirectory()) continue;
final var zeName = ze.getName();
- var cs = ((Jar)jar).verifyAndGetSigners(ze.getName(), zis.readAllBytes());
+ var cs = jar.moduleDataProvider().verifyAndGetSigners(ze.getName(), zis.readAllBytes());
assertAll("Jar behaves correctly",
()->assertNull(cs, "No code signers")
);
From ae6275bcf2825cc050fb0e21d96021eb9e86c29f Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sat, 11 Nov 2023 13:07:42 +0100
Subject: [PATCH 03/14] Add option to exclude some folder from the package scan
---
.../java/cpw/mods/jarhandling/SecureJar.java | 15 ++++++-
.../mods/jarhandling/SecureJarBuilder.java | 37 +++++++++++-----
.../java/cpw/mods/jarhandling/impl/Jar.java | 2 +-
.../jarhandling/impl/JarContentsImpl.java | 43 ++++++++++++++-----
4 files changed, 73 insertions(+), 24 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index 095f3e5..ab32a91 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -40,6 +40,9 @@ static SecureJar from(final Path... paths) {
/**
* A {@link SecureJar} can be built from multiple paths, either to directories or to {@code .jar} archives.
* This function returns the first of these paths, either to a directory or to an archive file.
+ *
+ * This is generally used for reporting purposes,
+ * for example to obtain a human-readable single location for this jar.
*/
Path getPrimaryPath();
@@ -66,6 +69,9 @@ default Set getPackages() {
Path getPath(String first, String... rest);
+ /**
+ * {@return the root path in the jar's own filesystem}
+ */
Path getRootPath();
interface ModuleDataProvider {
@@ -80,13 +86,20 @@ interface ModuleDataProvider {
CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes);
}
+ /**
+ * Same as {@link ModuleDescriptor.Provides}, but with an exposed constructor.
+ * Use only if the {@link #fromPath} method is useful to you.
+ */
record Provider(String serviceName, List providers) {
+ /**
+ * Helper method to parse service provider implementations from a {@link Path}.
+ */
public static Provider fromPath(final Path path, final BiPredicate pkgFilter) {
final var sname = path.getFileName().toString();
try {
var entries = Files.readAllLines(path).stream()
.map(String::trim)
- .filter(l->l.length() > 0 && !l.startsWith("#"))
+ .filter(l->l.length() > 0 && !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/SecureJarBuilder.java b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
index 348f4f3..70c23b4 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
@@ -12,14 +12,23 @@
import java.util.jar.Manifest;
public final class SecureJarBuilder {
+ private Path[] paths = new Path[0];
private Supplier defaultManifest = Manifest::new;
+ private String[] ignoredRootPackages = new String[0];
@Nullable
private BiPredicate pathFilter = null;
private Function metadataSupplier = JarMetadata::from;
- private Path[] paths = new Path[0];
public SecureJarBuilder() {}
+ /**
+ * Sets the paths for the files of this jar.
+ */
+ public SecureJarBuilder paths(Path... paths) {
+ this.paths = paths;
+ return this;
+ }
+
/**
* Overrides the default manifest for this jar.
* The default manifest is only used when the jar does not provide a manifest already.
@@ -32,8 +41,10 @@ public SecureJarBuilder defaultManifest(Supplier manifest) {
}
/**
- * Overrides the path filter for this jar.
- * TODO: wtf are the arguments passed to the path filter
+ * Overrides the path filter for this jar, to exclude some entries from the underlying file system.
+ *
+ * The second parameter to the filter is the base path, i.e. one of the paths passed to {@link #paths(Path...)}.
+ * The first parameter to the filter is the path of the entry being checked, relative to the base path.
*/
public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFilter) {
this.pathFilter = pathFilter;
@@ -41,20 +52,24 @@ public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFil
}
/**
- * Overrides the {@link JarMetadata} for this jar.
+ * Exclude some root folders from being scanned for code.
+ * This can be used to skip scanning of folders that are known to not contain code,
+ * but would be expensive to go through.
*/
- public SecureJarBuilder metadata(Function metadataSupplier) {
- Objects.requireNonNull(metadataSupplier);
+ public SecureJarBuilder ignoreRootPackages(String... ignoredRootPackages) {
+ Objects.requireNonNull(ignoredRootPackages);
- this.metadataSupplier = metadataSupplier;
+ this.ignoredRootPackages = ignoredRootPackages;
return this;
}
/**
- * Sets the paths for the files of this jar.
+ * Overrides the {@link JarMetadata} for this jar.
*/
- public SecureJarBuilder paths(Path... paths) {
- this.paths = paths;
+ public SecureJarBuilder metadata(Function metadataSupplier) {
+ Objects.requireNonNull(metadataSupplier);
+
+ this.metadataSupplier = metadataSupplier;
return this;
}
@@ -62,7 +77,7 @@ public SecureJarBuilder paths(Path... paths) {
* Builds the jar.
*/
public SecureJar build() {
- JarContentsImpl contents = new JarContentsImpl(defaultManifest, pathFilter, paths);
+ JarContentsImpl contents = new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter);
JarMetadata metadata = metadataSupplier.apply(contents);
return new Jar(contents, metadata);
}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
index 0766475..650c8e6 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
@@ -32,7 +32,7 @@ public class Jar implements SecureJar {
@Deprecated(forRemoval = true)
public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
- this.contents = new JarContentsImpl(defaultManifest, pathfilter, paths);
+ this.contents = new JarContentsImpl(paths, defaultManifest, new String[0], pathfilter);
this.manifest = contents.getManifest();
this.signingData = contents.signingData;
this.filesystem = contents.filesystem;
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
index 936a598..d993630 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -9,10 +9,14 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -40,12 +44,14 @@ public class JarContentsImpl implements JarContents {
// Name overrides, if the jar is a multi-release jar
private final Map nameOverrides;
+ // Folders known to not contain packages
+ private final Set ignoredRootPackages;
// Cache for repeated getPackages calls
private Set packages;
// Cache for repeated getMetaInfServices calls
private List providers;
- public JarContentsImpl(Supplier defaultManifest, @Nullable BiPredicate pathFilter, Path[] paths) {
+ public JarContentsImpl(Path[] paths, Supplier defaultManifest, String[] ignoredRootPackages, @Nullable BiPredicate pathFilter) {
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)));
@@ -54,6 +60,8 @@ public JarContentsImpl(Supplier defaultManifest, @Nullable BiPredicate
this.manifest = readManifestAndSigningData(defaultManifest, validPaths);
// Read multi-release jar information
this.nameOverrides = readMultiReleaseInfo();
+
+ this.ignoredRootPackages = Set.of(ignoredRootPackages);
}
private Manifest readManifestAndSigningData(Supplier defaultManifest, Path[] validPaths) {
@@ -129,16 +137,29 @@ public Manifest getManifest() {
@Override
public Set getPackages() {
if (this.packages == null) {
- try (var walk = Files.walk(this.filesystem.getRoot())) {
- this.packages = walk
- .filter(path->path.getNameCount()>0)
- .filter(path->!path.getName(0).toString().equals("META-INF"))
- .filter(path->path.getFileName().toString().endsWith(".class"))
- .filter(Files::isRegularFile)
- .map(Path::getParent)
- .map(path->path.toString().replace('/','.'))
- .filter(pkg->pkg.length()!=0)
- .collect(toSet());
+ Set packages = new HashSet<>();
+ try {
+ Files.walkFileTree(this.filesystem.getRoot(), new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (file.getFileName().toString().endsWith(".class") && attrs.isRegularFile()) {
+ var pkg = file.getParent().toString().replace('/', '.');
+ if (!pkg.isEmpty()) {
+ packages.add(pkg);
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) {
+ if (path.getNameCount() > 0 && ignoredRootPackages.contains(path.getName(0).toString())) {
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ this.packages = Set.copyOf(packages);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
From a51291d484a03aed49d0d2043f70e384fdc465b9 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 14:58:49 +0100
Subject: [PATCH 04/14] Restore not scanning META-INF for packages
---
src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
index d993630..c271765 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -45,7 +45,7 @@ public class JarContentsImpl implements JarContents {
private final Map nameOverrides;
// Folders known to not contain packages
- private final Set ignoredRootPackages;
+ private final Set ignoredRootPackages = new HashSet<>();
// Cache for repeated getPackages calls
private Set packages;
// Cache for repeated getMetaInfServices calls
@@ -61,7 +61,8 @@ public JarContentsImpl(Path[] paths, Supplier defaultManifest, String[
// Read multi-release jar information
this.nameOverrides = readMultiReleaseInfo();
- this.ignoredRootPackages = Set.of(ignoredRootPackages);
+ this.ignoredRootPackages.add("META-INF"); // Always ignore META-INF
+ this.ignoredRootPackages.addAll(List.of(ignoredRootPackages)); // And additional user-provided packages
}
private Manifest readManifestAndSigningData(Supplier defaultManifest, Path[] validPaths) {
From 846e34435195a7eae5b83fe2d98114458ad46e0f Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 15:31:38 +0100
Subject: [PATCH 05/14] Allow instantiating JarContents separately from the
SecureJar
---
.../cpw/mods/jarhandling/JarContents.java | 4 +-
.../mods/jarhandling/JarContentsBuilder.java | 72 +++++++++++++++++++
.../java/cpw/mods/jarhandling/SecureJar.java | 1 +
.../mods/jarhandling/SecureJarBuilder.java | 48 +++++++------
4 files changed, 101 insertions(+), 24 deletions(-)
create mode 100644 src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
diff --git a/src/main/java/cpw/mods/jarhandling/JarContents.java b/src/main/java/cpw/mods/jarhandling/JarContents.java
index ce2e33a..8d21b5d 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContents.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContents.java
@@ -12,13 +12,15 @@
/**
* Access to the contents of a list of {@link Path}s, interpreted as a jar file.
* Typically used to build the {@linkplain JarMetadata metadata} for a {@link SecureJar}.
+ *
+ * Create with {@link JarContentsBuilder}.
+ * Convert to a full jar with {@link SecureJarBuilder#contents(JarContents)}.
*/
@ApiStatus.NonExtendable
public interface JarContents {
/**
* @see SecureJar#getPrimaryPath()
*/
- // TODO: document what this is actually used for
Path getPrimaryPath();
/**
diff --git a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
new file mode 100644
index 0000000..67cd4f4
--- /dev/null
+++ b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
@@ -0,0 +1,72 @@
+package cpw.mods.jarhandling;
+
+import cpw.mods.jarhandling.impl.JarContentsImpl;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.Supplier;
+import java.util.jar.Manifest;
+
+/**
+ * Builder for {@link JarContents}.
+ */
+public final class JarContentsBuilder {
+ private Path[] paths = new Path[0];
+ private Supplier defaultManifest = Manifest::new;
+ private String[] ignoredRootPackages = new String[0];
+ @Nullable
+ private BiPredicate pathFilter = null;
+
+ public JarContentsBuilder() {}
+
+ /**
+ * Sets the paths for the files of this jar.
+ */
+ public JarContentsBuilder paths(Path... paths) {
+ this.paths = paths;
+ return this;
+ }
+
+ /**
+ * Overrides the default manifest for this jar.
+ * The default manifest is only used when the jar does not provide a manifest already.
+ */
+ public JarContentsBuilder defaultManifest(Supplier manifest) {
+ Objects.requireNonNull(manifest);
+
+ this.defaultManifest = manifest;
+ return this;
+ }
+
+ /**
+ * Overrides the path filter for this jar, to exclude some entries from the underlying file system.
+ *
+ * The second parameter to the filter is the base path, i.e. one of the paths passed to {@link #paths(Path...)}.
+ * The first parameter to the filter is the path of the entry being checked, relative to the base path.
+ */
+ public JarContentsBuilder pathFilter(@Nullable BiPredicate pathFilter) {
+ this.pathFilter = pathFilter;
+ return this;
+ }
+
+ /**
+ * Exclude some root folders from being scanned for code.
+ * This can be used to skip scanning of folders that are known to not contain code,
+ * but would be expensive to go through.
+ */
+ public JarContentsBuilder ignoreRootPackages(String... ignoredRootPackages) {
+ Objects.requireNonNull(ignoredRootPackages);
+
+ this.ignoredRootPackages = ignoredRootPackages;
+ return this;
+ }
+
+ /**
+ * Builds the jar.
+ */
+ public JarContents build() {
+ return new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter);
+ }
+}
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index ab32a91..a1cf37d 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -1,6 +1,7 @@
package cpw.mods.jarhandling;
import cpw.mods.jarhandling.impl.Jar;
+import cpw.mods.jarhandling.impl.JarContentsImpl;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
index 70c23b4..fbed3b7 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
@@ -11,55 +11,57 @@
import java.util.function.Supplier;
import java.util.jar.Manifest;
+/**
+ * Builder for a {@link SecureJar}.
+ *
+ * @see JarContentsBuilder
+ */
public final class SecureJarBuilder {
- private Path[] paths = new Path[0];
- private Supplier defaultManifest = Manifest::new;
- private String[] ignoredRootPackages = new String[0];
+ private final JarContentsBuilder contentsBuilder = new JarContentsBuilder();
@Nullable
- private BiPredicate pathFilter = null;
+ private JarContentsImpl contents;
private Function metadataSupplier = JarMetadata::from;
public SecureJarBuilder() {}
/**
- * Sets the paths for the files of this jar.
+ * @see JarContentsBuilder#paths(Path...)
*/
public SecureJarBuilder paths(Path... paths) {
- this.paths = paths;
+ contentsBuilder.paths(paths);
return this;
}
/**
- * Overrides the default manifest for this jar.
- * The default manifest is only used when the jar does not provide a manifest already.
+ * @see JarContentsBuilder#defaultManifest(Supplier)
*/
public SecureJarBuilder defaultManifest(Supplier manifest) {
- Objects.requireNonNull(manifest);
-
- this.defaultManifest = manifest;
+ contentsBuilder.defaultManifest(manifest);
return this;
}
/**
- * Overrides the path filter for this jar, to exclude some entries from the underlying file system.
- *
- * The second parameter to the filter is the base path, i.e. one of the paths passed to {@link #paths(Path...)}.
- * The first parameter to the filter is the path of the entry being checked, relative to the base path.
+ * @see JarContentsBuilder#pathFilter(BiPredicate)
*/
public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFilter) {
- this.pathFilter = pathFilter;
+ contentsBuilder.pathFilter(pathFilter);
return this;
}
/**
- * Exclude some root folders from being scanned for code.
- * This can be used to skip scanning of folders that are known to not contain code,
- * but would be expensive to go through.
+ * @see JarContentsBuilder#ignoreRootPackages(String...)
*/
public SecureJarBuilder ignoreRootPackages(String... ignoredRootPackages) {
- Objects.requireNonNull(ignoredRootPackages);
+ contentsBuilder.ignoreRootPackages(ignoredRootPackages);
+ return this;
+ }
- this.ignoredRootPackages = ignoredRootPackages;
+ /**
+ * Directly overrides the {@link JarContents} for this jar.
+ * If set, all the previous builder methods are ignored.
+ */
+ public SecureJarBuilder contents(JarContents contents) {
+ this.contents = (JarContentsImpl) contents;
return this;
}
@@ -77,8 +79,8 @@ public SecureJarBuilder metadata(Function metadataSupp
* Builds the jar.
*/
public SecureJar build() {
- JarContentsImpl contents = new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter);
+ JarContents contents = this.contents == null ? contentsBuilder.build() : this.contents;
JarMetadata metadata = metadataSupplier.apply(contents);
- return new Jar(contents, metadata);
+ return new Jar((JarContentsImpl) contents, metadata);
}
}
From eeb54446f3e5506e1b39faf6d0a628d3923139b7 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 15:44:15 +0100
Subject: [PATCH 06/14] Remove SecureJarBuilder, JarContentsBuilder is enough
---
.../cpw/mods/jarhandling/JarContents.java | 2 +-
.../java/cpw/mods/jarhandling/SecureJar.java | 20 ++++-
.../mods/jarhandling/SecureJarBuilder.java | 86 -------------------
3 files changed, 17 insertions(+), 91 deletions(-)
delete mode 100644 src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
diff --git a/src/main/java/cpw/mods/jarhandling/JarContents.java b/src/main/java/cpw/mods/jarhandling/JarContents.java
index 8d21b5d..ba89748 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContents.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContents.java
@@ -14,7 +14,7 @@
* Typically used to build the {@linkplain JarMetadata metadata} for a {@link SecureJar}.
*
* Create with {@link JarContentsBuilder}.
- * Convert to a full jar with {@link SecureJarBuilder#contents(JarContents)}.
+ * Convert to a full jar with {@link SecureJar#from(JarContents)}.
*/
@ApiStatus.NonExtendable
public interface JarContents {
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index a1cf37d..951b0b8 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -24,16 +24,28 @@
/**
* A secure jar is the full definition for a module,
* including all its paths and code signing metadata.
- *
- *
An instance can be built with {@link SecureJarBuilder}.
*/
public interface SecureJar {
/**
* Creates a jar from a list of paths.
- * See {@link SecureJarBuilder} for more configuration options.
+ * See {@link JarContentsBuilder} for more configuration options.
*/
static SecureJar from(final Path... paths) {
- return new SecureJarBuilder().paths(paths).build();
+ return from(new JarContentsBuilder().paths(paths).build());
+ }
+
+ /**
+ * Creates a jar from its contents, with default metadata.
+ */
+ static SecureJar from(JarContents contents) {
+ return from(contents, JarMetadata.from(contents));
+ }
+
+ /**
+ * Creates a jar from its contents and metadata.
+ */
+ static SecureJar from(JarContents contents, JarMetadata metadata) {
+ return new Jar((JarContentsImpl) contents, metadata);
}
ModuleDataProvider moduleDataProvider();
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java b/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
deleted file mode 100644
index fbed3b7..0000000
--- a/src/main/java/cpw/mods/jarhandling/SecureJarBuilder.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package cpw.mods.jarhandling;
-
-import cpw.mods.jarhandling.impl.Jar;
-import cpw.mods.jarhandling.impl.JarContentsImpl;
-import org.jetbrains.annotations.Nullable;
-
-import java.nio.file.Path;
-import java.util.Objects;
-import java.util.function.BiPredicate;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.jar.Manifest;
-
-/**
- * Builder for a {@link SecureJar}.
- *
- * @see JarContentsBuilder
- */
-public final class SecureJarBuilder {
- private final JarContentsBuilder contentsBuilder = new JarContentsBuilder();
- @Nullable
- private JarContentsImpl contents;
- private Function metadataSupplier = JarMetadata::from;
-
- public SecureJarBuilder() {}
-
- /**
- * @see JarContentsBuilder#paths(Path...)
- */
- public SecureJarBuilder paths(Path... paths) {
- contentsBuilder.paths(paths);
- return this;
- }
-
- /**
- * @see JarContentsBuilder#defaultManifest(Supplier)
- */
- public SecureJarBuilder defaultManifest(Supplier manifest) {
- contentsBuilder.defaultManifest(manifest);
- return this;
- }
-
- /**
- * @see JarContentsBuilder#pathFilter(BiPredicate)
- */
- public SecureJarBuilder pathFilter(@Nullable BiPredicate pathFilter) {
- contentsBuilder.pathFilter(pathFilter);
- return this;
- }
-
- /**
- * @see JarContentsBuilder#ignoreRootPackages(String...)
- */
- public SecureJarBuilder ignoreRootPackages(String... ignoredRootPackages) {
- contentsBuilder.ignoreRootPackages(ignoredRootPackages);
- return this;
- }
-
- /**
- * Directly overrides the {@link JarContents} for this jar.
- * If set, all the previous builder methods are ignored.
- */
- public SecureJarBuilder contents(JarContents contents) {
- this.contents = (JarContentsImpl) contents;
- return this;
- }
-
- /**
- * Overrides the {@link JarMetadata} for this jar.
- */
- public SecureJarBuilder metadata(Function metadataSupplier) {
- Objects.requireNonNull(metadataSupplier);
-
- this.metadataSupplier = metadataSupplier;
- return this;
- }
-
- /**
- * Builds the jar.
- */
- public SecureJar build() {
- JarContents contents = this.contents == null ? contentsBuilder.build() : this.contents;
- JarMetadata metadata = metadataSupplier.apply(contents);
- return new Jar((JarContentsImpl) contents, metadata);
- }
-}
From 3547906b725eeddb7459e6cd9f3b1785f51b1147 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 17:33:39 +0100
Subject: [PATCH 07/14] Use gradle.properties for dependency versions
---
build.gradle | 10 ++++------
gradle.properties | 2 ++
sjh-jmh/build.gradle | 8 ++++----
3 files changed, 10 insertions(+), 10 deletions(-)
create mode 100644 gradle.properties
diff --git a/build.gradle b/build.gradle
index c511e45..32f2445 100644
--- a/build.gradle
+++ b/build.gradle
@@ -117,13 +117,11 @@ group = 'cpw.mods'
version = gradleutils.version
logger.lifecycle('Version: ' + version)
-ext.asmVersion = 9.3
-ext.jbAnnotationsVersion = '22.0.0'
dependencies {
- api("org.ow2.asm:asm:${asmVersion}")
- api("org.ow2.asm:asm-tree:${asmVersion}")
- api("org.ow2.asm:asm-commons:${asmVersion}")
- implementation("org.jetbrains:annotations:${jbAnnotationsVersion}")
+ api("org.ow2.asm:asm:${project.asm_version}")
+ api("org.ow2.asm:asm-tree:${project.asm_version}")
+ api("org.ow2.asm:asm-commons:${project.asm_version}")
+ implementation("org.jetbrains:annotations:${project.jb_annotations_version}")
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.+')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.+')
}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..8d73330
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,2 @@
+asm_version=9.3
+jb_annotations_version=22.0.0
diff --git a/sjh-jmh/build.gradle b/sjh-jmh/build.gradle
index 03d7ed3..28a6b83 100644
--- a/sjh-jmh/build.gradle
+++ b/sjh-jmh/build.gradle
@@ -7,9 +7,9 @@ dependencies {
implementation('org.junit.jupiter:junit-jupiter-engine:5.8.+')
implementation('org.apache.logging.log4j:log4j-core:2.17.1')
implementation('org.apache.logging.log4j:log4j-api:2.17.1')
- implementation('org.ow2.asm:asm:9.3')
- implementation('org.ow2.asm:asm-tree:9.3')
- implementation('org.ow2.asm:asm-commons:9.3')
+ implementation("org.ow2.asm:asm:${project.asm_version}")
+ implementation("org.ow2.asm:asm-tree:${project.asm_version}")
+ implementation("org.ow2.asm:asm-commons:${project.asm_version}")
implementation('org.openjdk.jmh:jmh-core:1.35')
jmhOnly('org.openjdk.jmh:jmh-core:1.35')
jmhOnly('org.openjdk.jmh:jmh-generator-annprocess:1.35')
@@ -42,4 +42,4 @@ task jmh(type: JavaExec, dependsOn: sourceSets.main.output) {
args '-f', '1' // forks
args '-rff', project.file("${rootProject.buildDir}/jmh_results.txt") // results file
args 'cpw.mods.niofs.union.benchmarks.UnionFileSystemBenchmark'
-}
\ No newline at end of file
+}
From c7de85865c0b83fb3e28a9ff065a4fa258deb782 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 19:56:57 +0100
Subject: [PATCH 08/14] Simplify readMultiReleaseInfo and add a test for it
---
.../jarhandling/impl/JarContentsImpl.java | 57 +++++++++++++------
.../jarhandling/impl/TestMultiRelease.java | 39 +++++++++++++
.../multirelease/META-INF/MANIFEST.MF | 1 +
.../multirelease/META-INF/versions/1000/a.txt | 1 +
.../multirelease/META-INF/versions/1000/b.txt | 1 +
.../multirelease/META-INF/versions/9/a.txt | 1 +
src/test/resources/multirelease/a.txt | 1 +
src/test/resources/multirelease/b.txt | 1 +
8 files changed, 86 insertions(+), 16 deletions(-)
create mode 100644 src/test/java/cpw/mods/jarhandling/impl/TestMultiRelease.java
create mode 100644 src/test/resources/multirelease/META-INF/MANIFEST.MF
create mode 100644 src/test/resources/multirelease/META-INF/versions/1000/a.txt
create mode 100644 src/test/resources/multirelease/META-INF/versions/1000/b.txt
create mode 100644 src/test/resources/multirelease/META-INF/versions/9/a.txt
create mode 100644 src/test/resources/multirelease/a.txt
create mode 100644 src/test/resources/multirelease/b.txt
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
index c271765..c37a495 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -16,6 +16,7 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -94,26 +95,50 @@ private Manifest readManifestAndSigningData(Supplier defaultManifest,
return defaultManifest.get();
}
+ /**
+ * Read multi-release information from the jar.
+ * Example of a multi-release jar layout:
+ *
+ *
+ * jar root
+ * - A.class
+ * - B.class
+ * - C.class
+ * - D.class
+ * - META-INF
+ * - versions
+ * - 9
+ * - A.class
+ * - B.class
+ * - 10
+ * - A.class
+ *
+ */
private Map readMultiReleaseInfo() {
+ // Must have the manifest entry
boolean isMultiRelease = Boolean.parseBoolean(getManifest().getMainAttributes().getValue("Multi-Release"));
- if (isMultiRelease) {
- var vers = filesystem.getRoot().resolve("META-INF/versions");
- try (var walk = Files.walk(vers)){
- var allnames = walk.filter(p1 ->!p1.isAbsolute())
- .filter(path1 -> !Files.isDirectory(path1))
- .map(p1 -> p1.subpath(2, p1.getNameCount()))
- .collect(groupingBy(p->p.subpath(1, p.getNameCount()),
- mapping(p->Integer.parseInt(p.getName(0).toString()), toUnmodifiableList())));
- return allnames.entrySet().stream()
- .map(e->Map.entry(e.getKey(), e.getValue().stream().reduce(Integer::max).orElse(8)))
- .filter(e-> e.getValue() < Runtime.version().feature())
- .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
- } catch (IOException ioe) {
- throw new UncheckedIOException(ioe);
- }
- } else {
+ if (!isMultiRelease) {
return Map.of();
}
+
+ var vers = filesystem.getRoot().resolve("META-INF/versions");
+ try (var walk = Files.walk(vers)) {
+ Map pathToJavaVersion = new HashMap<>();
+ walk
+ // Look for files, not directories
+ .filter(p -> !Files.isDirectory(p))
+ .forEach(p -> {
+ int javaVersion = Integer.parseInt(p.getName(2).toString());
+ Path remainder = p.subpath(3, p.getNameCount());
+ if (javaVersion <= Runtime.version().feature()) {
+ // Associate path with the highest supported java version
+ pathToJavaVersion.merge(remainder, javaVersion, Integer::max);
+ }
+ });
+ return pathToJavaVersion;
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
}
@Override
diff --git a/src/test/java/cpw/mods/jarhandling/impl/TestMultiRelease.java b/src/test/java/cpw/mods/jarhandling/impl/TestMultiRelease.java
new file mode 100644
index 0000000..bf5592c
--- /dev/null
+++ b/src/test/java/cpw/mods/jarhandling/impl/TestMultiRelease.java
@@ -0,0 +1,39 @@
+package cpw.mods.jarhandling.impl;
+
+import cpw.mods.jarhandling.SecureJar;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class TestMultiRelease {
+ @Test
+ public void testMultiRelease() {
+ Path rootDir = Paths.get("src", "test", "resources", "multirelease");
+ var jar = SecureJar.from(rootDir);
+
+ var aContents = readString(jar, "a.txt");
+ // Should be overridden by the Java 9 version
+ Assertions.assertEquals("new", aContents.strip());
+ // Java 1000 override should not be loaded
+ Assertions.assertNotEquals("too new", aContents.strip());
+
+ var bContents = readString(jar, "b.txt");
+ // No override
+ Assertions.assertEquals("old", bContents.strip());
+ // In particular, Java 1000 override should not be used
+ Assertions.assertNotEquals("too new", bContents.strip());
+ }
+
+ private static String readString(SecureJar jar, String file) {
+ // Note: we must read the jar through the module data provider for version-specific files to be used
+ try (var is = jar.moduleDataProvider().open(file).get()) {
+ return new String(is.readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/test/resources/multirelease/META-INF/MANIFEST.MF b/src/test/resources/multirelease/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e714012
--- /dev/null
+++ b/src/test/resources/multirelease/META-INF/MANIFEST.MF
@@ -0,0 +1 @@
+Multi-Release: true
diff --git a/src/test/resources/multirelease/META-INF/versions/1000/a.txt b/src/test/resources/multirelease/META-INF/versions/1000/a.txt
new file mode 100644
index 0000000..bac343d
--- /dev/null
+++ b/src/test/resources/multirelease/META-INF/versions/1000/a.txt
@@ -0,0 +1 @@
+too new
diff --git a/src/test/resources/multirelease/META-INF/versions/1000/b.txt b/src/test/resources/multirelease/META-INF/versions/1000/b.txt
new file mode 100644
index 0000000..bac343d
--- /dev/null
+++ b/src/test/resources/multirelease/META-INF/versions/1000/b.txt
@@ -0,0 +1 @@
+too new
diff --git a/src/test/resources/multirelease/META-INF/versions/9/a.txt b/src/test/resources/multirelease/META-INF/versions/9/a.txt
new file mode 100644
index 0000000..3e75765
--- /dev/null
+++ b/src/test/resources/multirelease/META-INF/versions/9/a.txt
@@ -0,0 +1 @@
+new
diff --git a/src/test/resources/multirelease/a.txt b/src/test/resources/multirelease/a.txt
new file mode 100644
index 0000000..3367afd
--- /dev/null
+++ b/src/test/resources/multirelease/a.txt
@@ -0,0 +1 @@
+old
diff --git a/src/test/resources/multirelease/b.txt b/src/test/resources/multirelease/b.txt
new file mode 100644
index 0000000..3367afd
--- /dev/null
+++ b/src/test/resources/multirelease/b.txt
@@ -0,0 +1 @@
+old
From 36e9375ceab5b81b0568000fe2a7448f229cb3d9 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 19:58:47 +0100
Subject: [PATCH 09/14] Apply suggestions from code review
Co-authored-by: Matyrobbrt <65940752+Matyrobbrt@users.noreply.github.com>
---
src/main/java/cpw/mods/jarhandling/JarContents.java | 4 ++--
src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/JarContents.java b/src/main/java/cpw/mods/jarhandling/JarContents.java
index ba89748..357b261 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContents.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContents.java
@@ -29,13 +29,13 @@ public interface JarContents {
Optional findFile(String name);
/**
- * Gets the manifest of the jar.
+ * {@return the manifest of the jar}
* Empty if no manifest is present in the jar.
*/
Manifest getManifest();
/**
- * Gets all the packages in the jar.
+ * {@return all the packages in the jar}
* (Every folder containing a {@code .class} file is considered a package.)
*/
Set getPackages();
diff --git a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
index 67cd4f4..f768e9b 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
@@ -22,7 +22,7 @@ public final class JarContentsBuilder {
public JarContentsBuilder() {}
/**
- * Sets the paths for the files of this jar.
+ * Sets the root paths for the files of this jar.
*/
public JarContentsBuilder paths(Path... paths) {
this.paths = paths;
From c02e249c988095df7b9ef1675a921d2c6150622a Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Sun, 12 Nov 2023 20:01:42 +0100
Subject: [PATCH 10/14] Make ignoredRootPackages a Set in more cases
---
src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java | 5 +++--
src/main/java/cpw/mods/jarhandling/impl/Jar.java | 2 +-
src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java | 4 ++--
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
index f768e9b..880c25c 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
@@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.util.Objects;
+import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
import java.util.jar.Manifest;
@@ -15,7 +16,7 @@
public final class JarContentsBuilder {
private Path[] paths = new Path[0];
private Supplier defaultManifest = Manifest::new;
- private String[] ignoredRootPackages = new String[0];
+ private Set ignoredRootPackages = Set.of();
@Nullable
private BiPredicate pathFilter = null;
@@ -59,7 +60,7 @@ public JarContentsBuilder pathFilter(@Nullable BiPredicate pathF
public JarContentsBuilder ignoreRootPackages(String... ignoredRootPackages) {
Objects.requireNonNull(ignoredRootPackages);
- this.ignoredRootPackages = ignoredRootPackages;
+ this.ignoredRootPackages = Set.of(ignoredRootPackages);
return this;
}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
index 650c8e6..53a3354 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
@@ -32,7 +32,7 @@ public class Jar implements SecureJar {
@Deprecated(forRemoval = true)
public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
- this.contents = new JarContentsImpl(paths, defaultManifest, new String[0], pathfilter);
+ this.contents = new JarContentsImpl(paths, defaultManifest, Set.of(), pathfilter);
this.manifest = contents.getManifest();
this.signingData = contents.signingData;
this.filesystem = contents.filesystem;
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
index c37a495..16b4091 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -52,7 +52,7 @@ public class JarContentsImpl implements JarContents {
// Cache for repeated getMetaInfServices calls
private List providers;
- public JarContentsImpl(Path[] paths, Supplier defaultManifest, String[] ignoredRootPackages, @Nullable BiPredicate pathFilter) {
+ public JarContentsImpl(Path[] paths, Supplier defaultManifest, Set ignoredRootPackages, @Nullable BiPredicate pathFilter) {
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)));
@@ -63,7 +63,7 @@ public JarContentsImpl(Path[] paths, Supplier defaultManifest, String[
this.nameOverrides = readMultiReleaseInfo();
this.ignoredRootPackages.add("META-INF"); // Always ignore META-INF
- this.ignoredRootPackages.addAll(List.of(ignoredRootPackages)); // And additional user-provided packages
+ this.ignoredRootPackages.addAll(ignoredRootPackages); // And additional user-provided packages
}
private Manifest readManifestAndSigningData(Supplier defaultManifest, Path[] validPaths) {
From 96f9deb772f84b7ac33756f3b8b226c592a88122 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Mon, 13 Nov 2023 10:56:01 +0100
Subject: [PATCH 11/14] Replace BiPredicate by UnionPathFilter
in JarContentsBuilder
---
.../cpw/mods/jarhandling/JarContentsBuilder.java | 11 +++++------
.../cpw/mods/niofs/union/UnionPathFilter.java | 16 ++++++++++++++++
2 files changed, 21 insertions(+), 6 deletions(-)
create mode 100644 src/main/java/cpw/mods/niofs/union/UnionPathFilter.java
diff --git a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
index 880c25c..6349254 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
@@ -1,12 +1,12 @@
package cpw.mods.jarhandling;
import cpw.mods.jarhandling.impl.JarContentsImpl;
+import cpw.mods.niofs.union.UnionPathFilter;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Set;
-import java.util.function.BiPredicate;
import java.util.function.Supplier;
import java.util.jar.Manifest;
@@ -18,7 +18,7 @@ public final class JarContentsBuilder {
private Supplier defaultManifest = Manifest::new;
private Set ignoredRootPackages = Set.of();
@Nullable
- private BiPredicate pathFilter = null;
+ private UnionPathFilter pathFilter = null;
public JarContentsBuilder() {}
@@ -44,10 +44,9 @@ public JarContentsBuilder defaultManifest(Supplier manifest) {
/**
* Overrides the path filter for this jar, to exclude some entries from the underlying file system.
*
- * The second parameter to the filter is the base path, i.e. one of the paths passed to {@link #paths(Path...)}.
- * The first parameter to the filter is the path of the entry being checked, relative to the base path.
+ * @see UnionPathFilter
*/
- public JarContentsBuilder pathFilter(@Nullable BiPredicate pathFilter) {
+ public JarContentsBuilder pathFilter(@Nullable UnionPathFilter pathFilter) {
this.pathFilter = pathFilter;
return this;
}
@@ -68,6 +67,6 @@ public JarContentsBuilder ignoreRootPackages(String... ignoredRootPackages) {
* Builds the jar.
*/
public JarContents build() {
- return new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter);
+ return new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter == null ? null : pathFilter::test);
}
}
diff --git a/src/main/java/cpw/mods/niofs/union/UnionPathFilter.java b/src/main/java/cpw/mods/niofs/union/UnionPathFilter.java
new file mode 100644
index 0000000..0a5c1cc
--- /dev/null
+++ b/src/main/java/cpw/mods/niofs/union/UnionPathFilter.java
@@ -0,0 +1,16 @@
+package cpw.mods.niofs.union;
+
+/**
+ * Filter for paths in a {@link UnionFileSystem}.
+ */
+@FunctionalInterface
+public interface UnionPathFilter {
+ /**
+ * Test if an entry should be included in the union filesystem.
+ *
+ * @param entry the path of the entry being checked, relative to the base path
+ * @param basePath the base path, i.e. one of the root paths the filesystem is built out of
+ * @return {@code true} to include the entry, {@code} false to exclude it
+ */
+ boolean test(String entry, String basePath);
+}
From 1cac33177a17755b223141592a355b84f918b1cf Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Mon, 13 Nov 2023 11:31:26 +0100
Subject: [PATCH 12/14] Move root package exclusion from JarContentsBuilder to
getPackagesExcluding
---
.../cpw/mods/jarhandling/JarContents.java | 8 +++
.../mods/jarhandling/JarContentsBuilder.java | 16 +----
.../java/cpw/mods/jarhandling/SecureJar.java | 12 ++--
.../java/cpw/mods/jarhandling/impl/Jar.java | 2 +-
.../jarhandling/impl/JarContentsImpl.java | 66 ++++++++++---------
5 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/JarContents.java b/src/main/java/cpw/mods/jarhandling/JarContents.java
index 357b261..0a0b53b 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContents.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContents.java
@@ -40,6 +40,14 @@ public interface JarContents {
*/
Set getPackages();
+ /**
+ * {@return all the packages in the jar, with some root packages excluded}
+ *
+ * This can be used to skip scanning of folders that are known to not contain code,
+ * but would be expensive to go through.
+ */
+ Set getPackagesExcluding(String... excludedRootPackages);
+
/**
* Parses the {@code META-INF/services} files in the jar, and returns the list of service providers.
*/
diff --git a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
index 6349254..3090066 100644
--- a/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
+++ b/src/main/java/cpw/mods/jarhandling/JarContentsBuilder.java
@@ -6,7 +6,6 @@
import java.nio.file.Path;
import java.util.Objects;
-import java.util.Set;
import java.util.function.Supplier;
import java.util.jar.Manifest;
@@ -16,7 +15,6 @@
public final class JarContentsBuilder {
private Path[] paths = new Path[0];
private Supplier defaultManifest = Manifest::new;
- private Set ignoredRootPackages = Set.of();
@Nullable
private UnionPathFilter pathFilter = null;
@@ -51,22 +49,10 @@ public JarContentsBuilder pathFilter(@Nullable UnionPathFilter pathFilter) {
return this;
}
- /**
- * Exclude some root folders from being scanned for code.
- * This can be used to skip scanning of folders that are known to not contain code,
- * but would be expensive to go through.
- */
- public JarContentsBuilder ignoreRootPackages(String... ignoredRootPackages) {
- Objects.requireNonNull(ignoredRootPackages);
-
- this.ignoredRootPackages = Set.of(ignoredRootPackages);
- return this;
- }
-
/**
* Builds the jar.
*/
public JarContents build() {
- return new JarContentsImpl(paths, defaultManifest, ignoredRootPackages, pathFilter == null ? null : pathFilter::test);
+ return new JarContentsImpl(paths, defaultManifest, pathFilter == null ? null : pathFilter::test);
}
}
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index 951b0b8..4825d40 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -74,10 +74,6 @@ static SecureJar from(JarContents contents, JarMetadata metadata) {
boolean hasSecurityData();
- default Set getPackages() {
- return moduleDataProvider().descriptor().packages();
- }
-
String name();
Path getPath(String first, String... rest);
@@ -128,6 +124,14 @@ enum Status {
// TODO: add since
+ /**
+ * @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
+ */
+ @Deprecated(forRemoval = true)
+ default Set getPackages() {
+ return moduleDataProvider().descriptor().packages();
+ }
+
/**
* @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
*/
diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
index 53a3354..e9f41ba 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
@@ -32,7 +32,7 @@ public class Jar implements SecureJar {
@Deprecated(forRemoval = true)
public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
- this.contents = new JarContentsImpl(paths, defaultManifest, Set.of(), pathfilter);
+ this.contents = new JarContentsImpl(paths, defaultManifest, pathfilter);
this.manifest = contents.getManifest();
this.signingData = contents.signingData;
this.filesystem = contents.filesystem;
diff --git a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
index 16b4091..a39807b 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/JarContentsImpl.java
@@ -28,8 +28,6 @@
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
-import static java.util.stream.Collectors.*;
-
public class JarContentsImpl implements JarContents {
private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders()
.stream()
@@ -45,14 +43,12 @@ public class JarContentsImpl implements JarContents {
// Name overrides, if the jar is a multi-release jar
private final Map nameOverrides;
- // Folders known to not contain packages
- private final Set ignoredRootPackages = new HashSet<>();
// Cache for repeated getPackages calls
private Set packages;
// Cache for repeated getMetaInfServices calls
private List providers;
- public JarContentsImpl(Path[] paths, Supplier defaultManifest, Set ignoredRootPackages, @Nullable BiPredicate pathFilter) {
+ public JarContentsImpl(Path[] paths, Supplier defaultManifest, @Nullable BiPredicate pathFilter) {
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)));
@@ -61,9 +57,6 @@ public JarContentsImpl(Path[] paths, Supplier defaultManifest, Set defaultManifest, Path[] validPaths) {
@@ -161,34 +154,43 @@ public Manifest getManifest() {
}
@Override
- public Set getPackages() {
- if (this.packages == null) {
- Set packages = new HashSet<>();
- try {
- Files.walkFileTree(this.filesystem.getRoot(), new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- if (file.getFileName().toString().endsWith(".class") && attrs.isRegularFile()) {
- var pkg = file.getParent().toString().replace('/', '.');
- if (!pkg.isEmpty()) {
- packages.add(pkg);
- }
+ public Set getPackagesExcluding(String... excludedRootPackages) {
+ Set ignoredRootPackages = new HashSet<>(excludedRootPackages.length+1);
+ ignoredRootPackages.add("META-INF"); // Always ignore META-INF
+ ignoredRootPackages.addAll(List.of(excludedRootPackages)); // And additional user-provided packages
+
+ Set packages = new HashSet<>();
+ try {
+ Files.walkFileTree(this.filesystem.getRoot(), new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (file.getFileName().toString().endsWith(".class") && attrs.isRegularFile()) {
+ var pkg = file.getParent().toString().replace('/', '.');
+ if (!pkg.isEmpty()) {
+ packages.add(pkg);
}
- return FileVisitResult.CONTINUE;
}
+ return FileVisitResult.CONTINUE;
+ }
- @Override
- public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) {
- if (path.getNameCount() > 0 && ignoredRootPackages.contains(path.getName(0).toString())) {
- return FileVisitResult.SKIP_SUBTREE;
- }
- return FileVisitResult.CONTINUE;
+ @Override
+ public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) {
+ if (path.getNameCount() > 0 && ignoredRootPackages.contains(path.getName(0).toString())) {
+ return FileVisitResult.SKIP_SUBTREE;
}
- });
- this.packages = Set.copyOf(packages);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ return Set.copyOf(packages);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public Set getPackages() {
+ if (this.packages == null) {
+ this.packages = getPackagesExcluding();
}
return this.packages;
}
From 95f1496f4bfdb64fba202becf31ba311ce705461 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Mon, 13 Nov 2023 13:17:58 +0100
Subject: [PATCH 13/14] Add some @deprecated annotations
---
.../java/cpw/mods/jarhandling/SecureJar.java | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index 4825d40..aadf561 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -138,28 +138,41 @@ default Set getPackages() {
@Deprecated(forRemoval = true)
List getProviders();
- // The members below are deprecated for removal - use SecureJarBuilder instead!
-
+ /**
+ * @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
+ */
@Deprecated(forRemoval = true)
static SecureJar from(BiPredicate filter, final Path... paths) {
return from(jar->JarMetadata.from(jar, paths), filter, paths);
}
+ /**
+ * @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
+ */
@Deprecated(forRemoval = true)
static SecureJar from(Function metadataSupplier, final Path... paths) {
return from(Manifest::new, metadataSupplier, paths);
}
+ /**
+ * @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
+ */
@Deprecated(forRemoval = true)
static SecureJar from(Function metadataSupplier, BiPredicate filter, final Path... paths) {
return from(Manifest::new, metadataSupplier, filter, paths);
}
+ /**
+ * @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
+ */
@Deprecated(forRemoval = true)
static SecureJar from(Supplier defaultManifest, Function metadataSupplier, final Path... paths) {
return from(defaultManifest, metadataSupplier, null, paths);
}
+ /**
+ * @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
+ */
@Deprecated(forRemoval = true)
static SecureJar from(Supplier defaultManifest, Function metadataSupplier, BiPredicate filter, final Path... paths) {
return new Jar(defaultManifest, metadataSupplier, filter, paths);
From d1eb6d73fcbfd322856e0958b8c5caa2d32c51b3 Mon Sep 17 00:00:00 2001
From: Technici4n <13494793+Technici4n@users.noreply.github.com>
Date: Mon, 13 Nov 2023 13:25:27 +0100
Subject: [PATCH 14/14] Add since=2.1.16 to terminal deprecations
---
.../java/cpw/mods/jarhandling/JarMetadata.java | 3 +--
.../java/cpw/mods/jarhandling/SecureJar.java | 16 +++++++---------
src/main/java/cpw/mods/jarhandling/impl/Jar.java | 2 +-
3 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/src/main/java/cpw/mods/jarhandling/JarMetadata.java b/src/main/java/cpw/mods/jarhandling/JarMetadata.java
index dc02498..7a8eda5 100644
--- a/src/main/java/cpw/mods/jarhandling/JarMetadata.java
+++ b/src/main/java/cpw/mods/jarhandling/JarMetadata.java
@@ -146,9 +146,8 @@ private static String cleanModuleName(String mn) {
/**
* @deprecated Use {@link #from(JarContents)} instead.
- * TODO: add since
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
static JarMetadata from(final SecureJar jar, final Path... path) {
if (path.length==0) throw new IllegalArgumentException("Need at least one path");
final var pkgs = jar.getPackages();
diff --git a/src/main/java/cpw/mods/jarhandling/SecureJar.java b/src/main/java/cpw/mods/jarhandling/SecureJar.java
index aadf561..3b3b3f5 100644
--- a/src/main/java/cpw/mods/jarhandling/SecureJar.java
+++ b/src/main/java/cpw/mods/jarhandling/SecureJar.java
@@ -122,12 +122,10 @@ enum Status {
NONE, INVALID, UNVERIFIED, VERIFIED
}
- // TODO: add since
-
/**
* @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
default Set getPackages() {
return moduleDataProvider().descriptor().packages();
}
@@ -135,13 +133,13 @@ default Set getPackages() {
/**
* @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
List getProviders();
/**
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
static SecureJar from(BiPredicate filter, final Path... paths) {
return from(jar->JarMetadata.from(jar, paths), filter, paths);
}
@@ -149,7 +147,7 @@ static SecureJar from(BiPredicate filter, final Path... paths) {
/**
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
static SecureJar from(Function metadataSupplier, final Path... paths) {
return from(Manifest::new, metadataSupplier, paths);
}
@@ -157,7 +155,7 @@ static SecureJar from(Function metadataSupplier, final P
/**
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
static SecureJar from(Function metadataSupplier, BiPredicate filter, final Path... paths) {
return from(Manifest::new, metadataSupplier, filter, paths);
}
@@ -165,7 +163,7 @@ static SecureJar from(Function metadataSupplier, BiPredi
/**
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
*/
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
static SecureJar from(Supplier defaultManifest, Function metadataSupplier, final Path... paths) {
return from(defaultManifest, metadataSupplier, null, paths);
}
@@ -173,7 +171,7 @@ static SecureJar from(Supplier defaultManifest, Function defaultManifest, Function metadataSupplier, BiPredicate filter, final Path... paths) {
return new Jar(defaultManifest, metadataSupplier, filter, paths);
}
diff --git a/src/main/java/cpw/mods/jarhandling/impl/Jar.java b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
index e9f41ba..ee9c3e2 100644
--- a/src/main/java/cpw/mods/jarhandling/impl/Jar.java
+++ b/src/main/java/cpw/mods/jarhandling/impl/Jar.java
@@ -30,7 +30,7 @@ public class Jar implements SecureJar {
private final JarMetadata metadata;
- @Deprecated(forRemoval = true)
+ @Deprecated(forRemoval = true, since = "2.1.16")
public Jar(final Supplier defaultManifest, final Function metadataFunction, final BiPredicate pathfilter, final Path... paths) {
this.contents = new JarContentsImpl(paths, defaultManifest, pathfilter);
this.manifest = contents.getManifest();