You've already forked careful-downloader
mirror of
https://github.com/jakejarvis/careful-downloader.git
synced 2026-01-14 06:42:56 -05:00
BREAKING: allow hashes provided as a URL to a text file or a string
(closes #1)
This commit is contained in:
97
lib/checksum.js
Normal file
97
lib/checksum.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import path from "path";
|
||||
import crypto from "crypto";
|
||||
import fs from "fs-extra";
|
||||
|
||||
import debug from "./debug.js";
|
||||
|
||||
// Check the checksum via a parsed text file containing one or more hashes.
|
||||
export const checksumViaFile = async (desiredFile, checksumFile, algorithm, encoding) => {
|
||||
debug(`Validating checksum of '${desiredFile}' against hashes listed in '${checksumFile}' (algo: '${algorithm}', encoding: '${encoding}')`);
|
||||
|
||||
const parsedHashes = await parseChecksumFile(checksumFile);
|
||||
debug(`All hashes pulled from the checksums file '${path.basename(checksumFile)}' : ${JSON.stringify(parsedHashes)}`);
|
||||
|
||||
const generatedHash = await generateHashFromFile(desiredFile, algorithm, encoding);
|
||||
debug(`Generated a ${algorithm} hash of '${path.basename(desiredFile)}' => ${generatedHash}`);
|
||||
|
||||
const correctHash = parsedHashes[path.basename(desiredFile)];
|
||||
debug(`Found '${path.basename(desiredFile)}' in '${path.basename(checksumFile)}', provided hash is: ${correctHash}`);
|
||||
|
||||
if (!correctHash) {
|
||||
throw new Error(`'${path.basename(desiredFile)}' isn't listed in checksums file.`);
|
||||
}
|
||||
|
||||
debug(`Checking if generated === provided... ${generatedHash} : ${correctHash}`);
|
||||
if (generatedHash !== correctHash) {
|
||||
debug(`Nope... ${generatedHash} !== ${correctHash}`);
|
||||
throw new Error(`Hash of '${path.basename(desiredFile)}' doesn't match the given checksum.`);
|
||||
}
|
||||
|
||||
debug("Checksum validated via file, it's a match!");
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check the checksum via a provided hash.
|
||||
export const checksumViaString = async (desiredFile, correctHash, algorithm, encoding) => {
|
||||
debug(`Validating checksum of '${desiredFile}' against provided hash '${correctHash}' (algo: '${algorithm}', encoding: '${encoding}')`);
|
||||
|
||||
const generatedHash = await generateHashFromFile(desiredFile, algorithm, encoding);
|
||||
debug(`Generated a ${algorithm} hash of '${path.basename(desiredFile)}' => ${generatedHash}`);
|
||||
|
||||
debug(`Checking if generated === provided... ${generatedHash} : ${correctHash}`);
|
||||
if (generatedHash !== correctHash) {
|
||||
debug(`Nope... ${generatedHash} !== ${correctHash}`);
|
||||
throw new Error(`Hash of '${path.basename(desiredFile)}' doesn't match the given checksum.`);
|
||||
}
|
||||
|
||||
debug("Checksum validated via string, it's a match!");
|
||||
return true;
|
||||
};
|
||||
|
||||
// Takes a path to a file and returns its hash.
|
||||
const generateHashFromFile = async (file, algorithm, encoding) => {
|
||||
const fileBuffer = fs.readFileSync(file);
|
||||
const hashSum = crypto.createHash(algorithm);
|
||||
hashSum.update(fileBuffer);
|
||||
|
||||
return hashSum.digest(encoding);
|
||||
};
|
||||
|
||||
// Largely adapted from sumchecker:
|
||||
// https://github.com/malept/sumchecker/blob/28aed640a02787490d033fda56eaee30e24e5a71/src/index.ts#L97
|
||||
const parseChecksumFile = async (checksumFile) => {
|
||||
// read the text file holding one or more checksums
|
||||
const data = fs.readFileSync(checksumFile, { encoding: "utf8" });
|
||||
|
||||
// https://regexr.com/67k7i
|
||||
const lineRegex = /^([\da-fA-F]+) ([ *])(.+)$/;
|
||||
|
||||
// extract each file and its hash into this object
|
||||
const checksums = {};
|
||||
|
||||
// loop through each line (should be one file & hash each)
|
||||
let lineNumber = 0;
|
||||
for (const line of data.trim().split(/[\r\n]+/)) {
|
||||
lineNumber += 1;
|
||||
|
||||
// parse the current line using the regex pattern above
|
||||
const parsedLine = lineRegex.exec(line);
|
||||
|
||||
if (parsedLine === null) {
|
||||
// didn't match regex
|
||||
debug(`Could not parse line number ${lineNumber}`);
|
||||
throw new Error(lineNumber, line);
|
||||
} else {
|
||||
parsedLine.shift();
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [hash, binary, file] = parsedLine;
|
||||
|
||||
// save the current file & hash in the checksums object
|
||||
checksums[file] = hash;
|
||||
}
|
||||
}
|
||||
|
||||
// send back the cleaned up object of filenames & hashes
|
||||
return checksums;
|
||||
};
|
||||
4
lib/debug.js
Normal file
4
lib/debug.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import debug from "debug";
|
||||
|
||||
// set DEBUG=careful-downloader in environment to enable detailed logging
|
||||
export default debug("careful-downloader");
|
||||
20
lib/download.js
Normal file
20
lib/download.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import stream from "stream";
|
||||
import { promisify } from "util";
|
||||
import fs from "fs-extra";
|
||||
import got from "got";
|
||||
|
||||
import debug from "./debug.js";
|
||||
|
||||
// Download any file to any destination. Returns a promise.
|
||||
export default async (url, dest) => {
|
||||
debug(`Downloading '${url}' to '${dest}'`);
|
||||
|
||||
// get remote file and write locally
|
||||
const pipeline = promisify(stream.pipeline);
|
||||
const download = pipeline(
|
||||
got.stream(url, { followRedirect: true }), // GitHub releases redirect to unpredictable URLs
|
||||
fs.createWriteStream(dest),
|
||||
);
|
||||
|
||||
return download;
|
||||
};
|
||||
Reference in New Issue
Block a user