Skip to content

Commit 43d1d64

Browse files
committed
dependency checksum validation (suggestion taken from #9)
1 parent 8f07be6 commit 43d1d64

File tree

1 file changed

+122
-37
lines changed

1 file changed

+122
-37
lines changed

src/main/java/com/falsepattern/lib/dependencies/DependencyLoader.java

Lines changed: 122 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.net.URL;
1111
import java.nio.file.Files;
1212
import java.nio.file.Paths;
13+
import java.security.MessageDigest;
1314
import java.util.HashMap;
1415
import java.util.HashSet;
1516
import java.util.Map;
@@ -24,6 +25,11 @@
2425
@StableAPI(since = "0.6.0")
2526
public class DependencyLoader {
2627

28+
/**
29+
* Dependency checksum formats in a decreasing order of quality.
30+
*/
31+
private static final String[] CHECKSUM_TYPES = new String[]{"sha512", "sha256", "sha1", "md5"};
32+
2733
private static final Map<String, Version> loadedLibraries = new HashMap<>();
2834
private static final Map<String, String> loadedLibraryMods = new HashMap<>();
2935
private static final Set<String> mavenRepositories = new HashSet<>();
@@ -130,50 +136,129 @@ public static void loadLibrary(@NonNull String loadingModId,
130136
FalsePatternLib.getLog().fatal(errorMessage);
131137
throw new IllegalStateException(errorMessage);
132138
}
133-
for (var repo : mavenRepositories) {
134-
try {
135-
if (!repo.endsWith("/")) {
136-
repo = repo + "/";
139+
for (var repo : mavenRepositories) {
140+
try {
141+
if (!repo.endsWith("/")) {
142+
repo = repo + "/";
143+
}
144+
val url = String.format("%s%s/%s/%s/%s", repo, groupId.replace('.', '/'), artifactId,
145+
preferredVersion, mavenJarName);
146+
String finalRepo = repo;
147+
boolean retry = true;
148+
int retryCount = 0;
149+
redownload:
150+
while (retry) {
151+
retry = false;
152+
retryCount++;
153+
if (retryCount >= 3) {
154+
break;
155+
}
156+
val success = new AtomicBoolean(false);
157+
Internet.connect(new URL(url), (ex) -> {
158+
FalsePatternLib.getLog()
159+
.info("Artifact {} could not be downloaded from repo {}: {}", artifactLogName,
160+
finalRepo, ex.getMessage());
161+
}, (input) -> {
162+
FalsePatternLib.getLog().info("Downloading {} from {}", artifactLogName, finalRepo);
163+
download(input, file);
164+
FalsePatternLib.getLog().info("Downloaded {}", artifactLogName);
165+
success.set(true);
166+
});
167+
if (success.get()) {
168+
FalsePatternLib.getLog().info("Validating checksum for {}", artifactLogName);
169+
boolean hadChecksum = false;
170+
for (val checksumType : CHECKSUM_TYPES) {
171+
val checksumURL = url + "." + checksumType;
172+
val checksumFile = new File(libDir, jarName + "." + checksumType);
173+
FalsePatternLib.getLog().info("Attempting to get {} checksum...", checksumType);
174+
success.set(false);
175+
Internet.connect(new URL(checksumURL), (ex) -> {
176+
FalsePatternLib.getLog()
177+
.info("Could not get {} checksum for {}: {}", checksumType,
178+
artifactLogName, ex.getMessage());
179+
}, (input) -> {
180+
FalsePatternLib.getLog()
181+
.info("Downloading {} checksum for {}", checksumType, artifactLogName);
182+
download(input, checksumFile);
183+
FalsePatternLib.getLog()
184+
.info("Downloaded {} checksum for {}", checksumType, artifactLogName);
185+
success.set(true);
186+
});
187+
if (success.get()) {
188+
val fileHash = hash(checksumType, file);
189+
val referenceHash = new String(Files.readAllBytes(checksumFile.toPath()));
190+
if (!fileHash.equals(referenceHash)) {
191+
FalsePatternLib.getLog()
192+
.error("Failed {} checksum validation for {}. Retrying download...",
193+
checksumType, artifactLogName);
194+
file.delete();
195+
retry = true;
196+
continue redownload;
197+
}
198+
FalsePatternLib.getLog()
199+
.info("Successfully validated {} checksum for {}", checksumType,
200+
artifactLogName);
201+
hadChecksum = true;
202+
}
203+
}
204+
if (!hadChecksum) {
205+
FalsePatternLib.getLog()
206+
.warn("The library {} had no checksum available on the repository.\n" +
207+
"There's a chance it might have gotten corrupted during download,\n" +
208+
"but we're loading it anyways.", artifactLogName);
209+
}
210+
loadedLibraries.put(artifact, preferredVersion);
211+
loadedLibraryMods.put(artifact, loadingModId);
212+
addToClasspath(file);
213+
return;
214+
}
215+
}
216+
} catch (IOException ignored) {
137217
}
138-
val url = new URL(String.format("%s%s/%s/%s/%s",
139-
repo,
140-
groupId.replace('.', '/'),
141-
artifactId,
142-
preferredVersion,
143-
mavenJarName));
144-
String finalRepo = repo;
145-
val success = new AtomicBoolean(false);
146-
Internet.connect(url, (ex) -> {
147-
FalsePatternLib.getLog()
148-
.info("Artifact {} could not be downloaded from repo {}: {}",
149-
artifactLogName,
150-
finalRepo,
151-
ex.getMessage());
152-
}, (input) -> {
153-
FalsePatternLib.getLog()
154-
.info("Downloading {} from {}",
155-
artifactLogName,
156-
finalRepo);
157-
download(input, file);
158-
FalsePatternLib.getLog().info("Downloaded {}", artifactLogName);
159-
loadedLibraries.put(artifact, preferredVersion);
160-
loadedLibraryMods.put(artifact, loadingModId);
161-
addToClasspath(file);
162-
success.set(true);
163-
});
164-
if (success.get()) {
165-
return;
166-
}
167-
} catch (IOException ignored) {
168-
}
169218
}
170-
val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion +
171-
((suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " +
219+
val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion + ((suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " +
172220
loadingModId;
173221
FalsePatternLib.getLog().fatal(errorMessage);
174222
throw new IllegalStateException(errorMessage);
175223
}
176224

225+
private static String bytesToHex(byte[] hash) {
226+
StringBuilder hexString = new StringBuilder(2 * hash.length);
227+
for (byte b : hash) {
228+
String hex = Integer.toHexString(0xff & b);
229+
if (hex.length() == 1) {
230+
hexString.append('0');
231+
}
232+
hexString.append(hex);
233+
}
234+
return hexString.toString();
235+
}
236+
237+
@SneakyThrows
238+
private static String digest(String algo, byte[] data) {
239+
return bytesToHex(MessageDigest.getInstance(algo).digest(data));
240+
}
241+
242+
@SneakyThrows
243+
private static String hash(String algo, File file) {
244+
byte[] data = Files.readAllBytes(file.toPath());
245+
switch (algo) {
246+
case "md5":
247+
algo = "MD5";
248+
break;
249+
case "sha1":
250+
algo = "SHA-1";
251+
break;
252+
case "sha256":
253+
algo = "SHA-256";
254+
break;
255+
case "sha512":
256+
algo = "SHA-512";
257+
break;
258+
}
259+
return digest(algo, data);
260+
}
261+
177262
private static void addToClasspath(File file) {
178263
try {
179264
val cl = (LaunchClassLoader) DependencyLoader.class.getClassLoader();

0 commit comments

Comments
 (0)