Check checksums for mod downloads to ensure file integrity

This commit is contained in:
Kallum Jones
2022-08-09 22:21:21 +01:00
parent 40cdbf0aca
commit 73e6ed2edd
6 changed files with 73 additions and 19 deletions

View File

@ -1,28 +1,48 @@
import path from "path";
import * as https from "https";
import {createWriteStream} from "fs";
import {readFileSync, unlinkSync} from "fs";
import DownloadError from "../errors/download_error.js";
import ModManager from "../mod-manager.js";
import {createHash} from "crypto";
import { DownloaderHelper } from "node-downloader-helper";
export default class FileDownloader {
static downloadMod(version: Version): void {
static async downloadMod(version: Version): Promise<void> {
try {
// Error out if url is null
if (version.url == null) {
throw new Error("URL was null");
}
https.get(version.url, res => {
// Download the file
const downloader = new DownloaderHelper(version.url, ModManager.FilePaths.MODS_FOLDER_PATH, {
fileName: version.fileName
}).on("error", err => {
throw err;
});
await downloader.start()
// Check the checksum
if (version.checksum != undefined || version.checksum != "") {
const filePath = path.join(ModManager.FilePaths.MODS_FOLDER_PATH, version.fileName);
const writeStream = createWriteStream(filePath);
res.pipe(writeStream);
writeStream.on("finish", () => writeStream.close());
writeStream.on('error', () => {
throw new Error("Error while writing file during download")
})
})
const hash = this.getHashForFile(filePath)
if (hash != version.checksum) {
unlinkSync(filePath);
throw new DownloadError("The hash for this file does not match the checksum provided")
}
}
} catch (e) {
throw new DownloadError(`Failed to download ${version.fileName} from ${version.url}`)
}
}
private static getHashForFile(filePath: string) {
const hash = createHash("sha1")
const file = readFileSync(filePath);
hash.update(file);
return hash.digest("hex").toString();
}
}

3
src/mods/mod.d.ts vendored
View File

@ -15,7 +15,8 @@ declare global {
fileName: string,
url: string
versionNumber: string,
dependencies: Array<Version>
dependencies: Array<Version>,
checksum: string
}
}

View File

@ -13,7 +13,7 @@ export class CurseforgeSource implements ModSource {
private static readonly SEARCH_URL: string = `${CurseforgeSource.BASE_URL}/mods/search`;
private static readonly GET_MOD_URL: string = `${CurseforgeSource.BASE_URL}/mods/%s`
private static readonly GET_FILE_URL: string = `${CurseforgeSource.BASE_URL}/mods/%s/files/%s`
private static readonly DOWNLOAD_CDN_URL: string = "https://edge.forgecdn.net/files/%s/%s/%s";
private static readonly DOWNLOAD_CDN_URL: string = "https://mediafiles.forgecdn.net/files/%s/%s/%s";
private static readonly MINECRAFT_ID: number = 432;
private static readonly FABRIC_TYPE: number = 4;
@ -49,14 +49,24 @@ export class CurseforgeSource implements ModSource {
}
}
const downloadUrl = fileObj.downloadUrl != null ? fileObj.downloadUrl : this.constructDownloadUrl(id, fileObj.fileName);
const downloadUrl = fileObj.downloadUrl != null ? fileObj.downloadUrl : this.constructDownloadUrl(fileObj.id, fileObj.fileName);
const hashes = fileObj.hashes;
let checksum: string = "";
for (let hash of hashes) {
// If the algorithm for this hash was sha1
if (hash.algo == 1) {
checksum = hash.value;
}
}
return {
modId: id.toString(),
fileName: fileObj.fileName,
url: downloadUrl,
versionNumber: fileObj.displayName,
dependencies: dependencies
dependencies: dependencies,
checksum: checksum
}
}
@ -92,7 +102,7 @@ export class CurseforgeSource implements ModSource {
dependencies.push(dependency.modId)
}
}
FileDownloader.downloadMod(version)
await FileDownloader.downloadMod(version)
const mod = {
name: await this.getProjectName(version.modId),

View File

@ -61,7 +61,7 @@ export default class ModrinthSource implements ModSource {
dependencies.push(dependency.modId)
}
}
FileDownloader.downloadMod(version)
await FileDownloader.downloadMod(version)
const mod = {
name: await this.getProjectName(version.modId),
@ -128,13 +128,15 @@ export default class ModrinthSource implements ModSource {
}
const latestFile = latestVersion.files[0];
const checksum = latestFile.hashes.sha1;
return {
modId: latestVersion.project_id,
versionNumber: latestVersion.version_number,
fileName: latestFile.filename,
url: latestFile.url,
dependencies: dependencies
dependencies: dependencies,
checksum: checksum
};
}
@ -201,12 +203,15 @@ export default class ModrinthSource implements ModSource {
}
}
const checksum = latestFile.hashes.sha1;
return {
modId: latestVersion.project_id,
versionNumber: latestVersion.version_number,
fileName: latestFile.filename,
url: latestFile.url,
dependencies: dependencies
dependencies: dependencies,
checksum: checksum
};
}
}