1
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:
2021-10-16 06:10:36 -04:00
parent bcd2e89f7d
commit 4d3134afb8
8 changed files with 336 additions and 183 deletions

97
lib/checksum.js Normal file
View 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
View 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
View 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;
};