From 1fda5ed80d3fc3dcf3d47b26d0b0ce1a55f71f9d Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Sun, 18 May 2025 13:27:23 +0200 Subject: [PATCH] support jar-in-jar for dependencies --- .../falsepattern/lib/internal/Internet.java | 63 +++++++++------- .../dependencies/DependencyLoaderImpl.java | 71 +++++++++++++------ .../impl/dependencies/LetsEncryptHelper.java | 6 ++ src/main/resources/DEPENDENCIES.md | 5 +- 4 files changed, 95 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/falsepattern/lib/internal/Internet.java b/src/main/java/com/falsepattern/lib/internal/Internet.java index df3b370..ff86246 100644 --- a/src/main/java/com/falsepattern/lib/internal/Internet.java +++ b/src/main/java/com/falsepattern/lib/internal/Internet.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.net.URLConnection; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; @@ -44,33 +45,12 @@ public class Internet { public static void connect(URL URL, Map headers, Consumer onError, Consumer onSuccess, Consumer contentLengthCallback) { try { - val connection = (HttpURLConnection) URL.openConnection(); - connection.setConnectTimeout(3500); - connection.setReadTimeout(5000); - connection.setRequestProperty("User-Agent", - Tags.MODNAME - + " " - + Tags.VERSION - + " Internet Connector" - + " (https://github.com/FalsePattern/FalsePatternLib)"); - for (val header : headers.entrySet()) { - val key = header.getKey(); - val value = header.getValue(); - if (key == null || value == null) { - throw new IllegalArgumentException("Null key or value"); - } - if (key.isEmpty()) { - throw new IllegalArgumentException("Empty key"); - } - connection.setRequestProperty(key, value); - } - if (connection.getResponseCode() != 200) { - onError.accept(new Exception("HTTP response code " + connection.getResponseCode())); + val urlConnection = URL.openConnection(); + if (urlConnection instanceof HttpURLConnection) { + connectHTTP((HttpURLConnection) urlConnection, headers, onError, onSuccess, contentLengthCallback); } else { - contentLengthCallback.accept(connection.getContentLengthLong()); - onSuccess.accept(connection.getInputStream()); + executeTransfer(urlConnection, onSuccess, contentLengthCallback); } - connection.disconnect(); } catch (Exception e) { //Check if NonUpdate is present try { @@ -86,6 +66,39 @@ public class Internet { } } + private static void connectHTTP(HttpURLConnection connection, Map headers, Consumer onError, Consumer onSuccess, Consumer contentLengthCallback) throws IOException { + connection.setConnectTimeout(3500); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", + Tags.MODNAME + + " " + + Tags.VERSION + + " Internet Connector" + + " (https://github.com/FalsePattern/FalsePatternLib)"); + for (val header : headers.entrySet()) { + val key = header.getKey(); + val value = header.getValue(); + if (key == null || value == null) { + throw new IllegalArgumentException("Null key or value"); + } + if (key.isEmpty()) { + throw new IllegalArgumentException("Empty key"); + } + connection.setRequestProperty(key, value); + } + if (connection.getResponseCode() != 200) { + onError.accept(new Exception("HTTP response code " + connection.getResponseCode())); + } else { + executeTransfer(connection, onSuccess, contentLengthCallback); + } + connection.disconnect(); + } + + private static void executeTransfer(URLConnection connection, Consumer onSuccess, Consumer contentLengthCallback) throws IOException { + contentLengthCallback.accept(connection.getContentLengthLong()); + onSuccess.accept(connection.getInputStream()); + } + public static void transferAndClose(InputStream is, OutputStream target, Consumer downloadSizeCallback) throws IOException { var bytesRead = 0; diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java index c3cf6ae..12887a7 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java @@ -96,7 +96,8 @@ public class DependencyLoaderImpl { private static final String[] CHECKSUM_TYPES = new String[]{"sha512", "sha256", "sha1", "md5"}; private static final Map loadedLibraries = new ConcurrentHashMap<>(); private static final Map loadedLibraryMods = new ConcurrentHashMap<>(); - private static final Set mavenRepositories = new ConcurrentSet<>(); + private static final Set remoteMavenRepositories = new ConcurrentSet<>(); + private static final Set localMavenRepositories = new ConcurrentSet<>(); private static final Logger LOG = LogManager.getLogger(Tags.MODNAME + " Library Loader"); private static final AtomicLong counter = new AtomicLong(0); @@ -176,7 +177,7 @@ public class DependencyLoaderImpl { } public static void addMavenRepo(String url) { - mavenRepositories.add(url); + remoteMavenRepositories.add(url); } private static String bytesToHex(byte[] hash) { @@ -262,7 +263,7 @@ public class DependencyLoaderImpl { return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } - private static boolean scanForDepSpecs(URL source, List output) { + private static boolean scanForDepSpecs(URL source, List output, List jijURLs) { if (!source.getProtocol().equals("file")) { return false; } @@ -274,14 +275,22 @@ public class DependencyLoaderImpl { val jarFile = new JarInputStream(inputStream, false)) { ZipEntry entry; while ((entry = jarFile.getNextEntry()) != null) { - if (!entry.getName().startsWith("META-INF") || !entry.getName().endsWith(".json")) { + val name = entry.getName(); + if (!name.startsWith("META-INF/")) continue; - } - try { - output.add(new URL("jar:" + source + "!/" + entry.getName())); - found = true; - } catch (MalformedURLException e) { - LOG.error("Failed to add json source {} to dependency source list: {}", entry.getName(), e); + if (name.endsWith(".json") && name.matches("META-INF/\\w+.json")) { + try { + output.add(new URL("jar:" + source + "!/" + entry.getName())); + found = true; + } catch (MalformedURLException e) { + LOG.error("Failed to add json source {} to dependency source list: {}", entry.getName(), e); + } + } else if (name.equals("META-INF/falsepatternlib_repo/")) { + try { + jijURLs.add(new URL("jar:" + source + "!/" + entry.getName())); + } catch (MalformedURLException e) { + LOG.error("Failed to add jar-in-jar repo {}: {}", entry.getName(), e); + } } } } catch (IOException e) { @@ -306,14 +315,22 @@ public class DependencyLoaderImpl { } try (val files = Files.list(metaInf)) { found = files.reduce(false, (prev,file) -> { - if (!file.endsWith(".json")) { - return prev; - } - try { - output.add(file.toUri().toURL()); - return true; - } catch (MalformedURLException e) { - LOG.error("Failed to add json source {} to dependency source list: {}", file.getFileName(), e); + val entryFileName = file.getFileName().toString(); + if (entryFileName.endsWith(".json")) { + try { + output.add(file.toUri().toURL()); + return true; + } catch (MalformedURLException e) { + LOG.error("Failed to add json source {} to dependency source list: {}", + file.toString(), + e); + } + } else if (entryFileName.equals("falsepatternlib_repo")) { + try { + jijURLs.add(file.toUri().toURL()); + } catch (MalformedURLException e) { + LOG.error("Failed to add jar-in-jar repo {}: {}", file.toString(), e); + } } return prev; }, (a,b) -> a || b); @@ -420,8 +437,9 @@ public class DependencyLoaderImpl { .filter((url) -> !urlsWithoutDeps.contains(url.toString())) .collect(Collectors.toList()); val urls = new ArrayList(); + val jijURLs = new ArrayList(); for (val candidate : candidates) { - if (!scanForDepSpecs(candidate, urls)) { + if (!scanForDepSpecs(candidate, urls, jijURLs)) { urlsWithoutDeps.add(candidate.toString()); } } @@ -459,9 +477,11 @@ public class DependencyLoaderImpl { }).filter(Objects::nonNull).collect(Collectors.toSet()); long end = System.currentTimeMillis(); LOG.debug("Discovered {} dependency source candidates in {}ms", dependencySpecs.size(), end - start); - mavenRepositories.addAll(dependencySpecs.stream() - .flatMap((dep) -> dep.repositories().stream()) - .collect(Collectors.toSet())); + remoteMavenRepositories.addAll(dependencySpecs.stream() + .flatMap((dep) -> dep.repositories().stream()) + .map(repo -> repo.endsWith("/") ? repo : repo + "/") + .collect(Collectors.toSet())); + localMavenRepositories.addAll(jijURLs.stream().map(URL::toString).map(repo -> repo.endsWith("/") ? repo : repo + "/").collect(Collectors.toSet())); val artifacts = dependencySpecs.stream() .map((root) -> new Pair<>(root.source(), root.dependencies())) .flatMap(pair -> flatMap(pair, @@ -706,8 +726,13 @@ public class DependencyLoaderImpl { if (tryLoadingExistingFile()) { return; } + for (val repo: localMavenRepositories) { + if (tryDownloadFromMaven(repo)) { + return; + } + } validateDownloadsAllowed(); - for (var repo : mavenRepositories) { + for (var repo : remoteMavenRepositories) { if (tryDownloadFromMaven(repo)) { return; } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java index 087a5d2..21aad91 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java @@ -48,11 +48,14 @@ public class LetsEncryptHelper { @SuppressWarnings("java:S6437") public static void replaceSSLContext() { if (!EarlyConfig.getInstance().enableLetsEncryptRoot()) { + FPLog.LOG.info("[LetsEncryptHelper] Disabled by config"); return; } if (patched) { + FPLog.LOG.info("[LetsEncryptHelper] Already patched"); return; } + FPLog.LOG.info("[LetsEncryptHelper] Starting patcher"); patched = true; try (val x1 = LetsEncryptHelper.class.getResourceAsStream("/letsencrypt/isrgrootx1.pem"); @@ -79,6 +82,9 @@ public class LetsEncryptHelper { FPLog.LOG.error("[LetsEncryptHelper] Failed to load certificates from classpath.", e); } catch (GeneralSecurityException e) { FPLog.LOG.error("[LetsEncryptHelper] Failed to load default keystore.", e); + } catch (Throwable t) { + FPLog.LOG.error("[LetsEncryptHelper] Unknown error", t); + throw t; } } } \ No newline at end of file diff --git a/src/main/resources/DEPENDENCIES.md b/src/main/resources/DEPENDENCIES.md index cbde496..e55e063 100644 --- a/src/main/resources/DEPENDENCIES.md +++ b/src/main/resources/DEPENDENCIES.md @@ -39,6 +39,8 @@ Explanation: to recognize it. - `repositories`: A list of maven repositories to use when downloading dependencies. These are used in addition to the default maven repositories. This is just a list of strings, each string being a https repository url. + Additionally, every jar with a dependencies json file inside of it is treated as a "jar in jar" maven repository. + This maven repository exists under `META-INF/falsepatternlib_repo/` - `dependencies`: A categorized list of dependencies. - The `always` category gets downloaded both inside and outside the dev environment. Usually not needed, as gradle will automatically download dependencies. @@ -57,5 +59,4 @@ Each of these categories also have 3 more subcategories: Each of these subcategories is a list of strings, each string being a maven dependency string. These are the same as the ones used in the gradle dependencies block. Note that this DOES NOT support the version range syntax, so you must specify a specific version. For example, `com.example:examplejar:1.0.0` is valid, -but `com.example:examplejar:[1.0.0,2.0.0]` -is not. \ No newline at end of file +but `com.example:examplejar:[1.0.0,2.0.0]` is not.