mirror of
https://git.bits.team/Bits/mod-manager.git
synced 2024-11-21 05:28:22 -05:00
Check checksums for mod downloads to ensure file integrity
This commit is contained in:
parent
40cdbf0aca
commit
73e6ed2edd
17
package-lock.json
generated
17
package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"chalk": "^5.0.1",
|
||||
"commander": "^9.4.0",
|
||||
"inquirer": "^9.1.0",
|
||||
"node-downloader-helper": "^2.1.2",
|
||||
"ora": "^6.1.2",
|
||||
"pino": "^8.3.1",
|
||||
"string-format": "^2.0.0",
|
||||
@ -499,6 +500,17 @@
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"node_modules/node-downloader-helper": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-downloader-helper/-/node-downloader-helper-2.1.2.tgz",
|
||||
"integrity": "sha512-h2QdFMIfy2Arl5R4el6CMQr3NzVMm4uHqeZp0kN8XAK7E8MDXJR74DpFJIuWSvXk4q5LzL/9Z3zsFA3rLgax2Q==",
|
||||
"bin": {
|
||||
"ndh": "bin/ndh"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/on-exit-leak-free": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
|
||||
@ -1158,6 +1170,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"node-downloader-helper": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-downloader-helper/-/node-downloader-helper-2.1.2.tgz",
|
||||
"integrity": "sha512-h2QdFMIfy2Arl5R4el6CMQr3NzVMm4uHqeZp0kN8XAK7E8MDXJR74DpFJIuWSvXk4q5LzL/9Z3zsFA3rLgax2Q=="
|
||||
},
|
||||
"on-exit-leak-free": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"chalk": "^5.0.1",
|
||||
"commander": "^9.4.0",
|
||||
"inquirer": "^9.1.0",
|
||||
"node-downloader-helper": "^2.1.2",
|
||||
"ora": "^6.1.2",
|
||||
"pino": "^8.3.1",
|
||||
"string-format": "^2.0.0",
|
||||
|
@ -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
3
src/mods/mod.d.ts
vendored
@ -15,7 +15,8 @@ declare global {
|
||||
fileName: string,
|
||||
url: string
|
||||
versionNumber: string,
|
||||
dependencies: Array<Version>
|
||||
dependencies: Array<Version>,
|
||||
checksum: string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user