diff --git a/install.js b/install.js index b38b3c1..a6bbb1f 100644 --- a/install.js +++ b/install.js @@ -4,6 +4,7 @@ const fs = require("fs"); const path = require("path"); const { https } = require("follow-redirects"); const decompress = require('decompress'); +const sumchecker = require('sumchecker'); (async () => { install() @@ -22,6 +23,7 @@ async function install() { // Hugo Extended supports: macOS x64, macOS ARM64, Linux x64, Windows x64. // all other combos fall back to vanilla Hugo. + const checksumFile = `hugo_${version}_checksums.txt` const downloadFile = process.platform === 'darwin' && process.arch === 'x64' ? `hugo_extended_${version}_macOS-64bit.tar.gz` : @@ -61,8 +63,10 @@ async function install() { if (!downloadFile) throw "Are you sure this platform is supported?"; let downloadUrl = downloadBaseUrl + downloadFile; + let checksumUrl = downloadBaseUrl + checksumFile; let vendorDir = path.join(__dirname, 'vendor'); let archivePath = path.join(vendorDir, downloadFile); + let checksumPath = path.join(vendorDir, checksumFile); let binName = process.platform === 'win32' ? 'hugo.exe' : 'hugo'; let binPath = path.join(vendorDir, binName); @@ -87,16 +91,39 @@ async function install() { ); }).on('error', reject)); - // TODO: validate the checksum of the download - // https://github.com/jakejarvis/hugo-extended/issues/1 + // fetch the checksum file from GitHub + await new Promise((resolve, reject) => https.get(checksumUrl, response => { + // throw an error immediately if the download failed + if (response.statusCode !== 200) { + response.resume(); + reject(new Error(`Download failed: status code ${response.statusCode} from ${checksumUrl}`)); + return; + } + + // pipe the response directly to a file + response.pipe( + fs.createWriteStream(checksumPath) + .on('finish', resolve) + .on('error', reject) + ); + }).on('error', reject)); + + // validate the checksum of the download + const checker = new sumchecker.ChecksumValidator('sha256', checksumPath, { + defaultTextEncoding: 'binary' + }); + await checker.validate(vendorDir, downloadFile); // extract the downloaded file await decompress(archivePath, vendorDir); } finally { // delete the downloaded archive when finished - if (fs.existsSync(archivePath)) { + if (fs.existsSync(archivePath)) await fs.promises.unlink(archivePath); - } + + // ...and the checksum file + if (fs.existsSync(checksumPath)) + await fs.promises.unlink(checksumPath); } // return the full path to our Hugo binary diff --git a/package-lock.json b/package-lock.json index 274bf6e..810b379 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "cross-spawn": "^7.0.3", "decompress": "^4.2.1", - "follow-redirects": "^1.14.1" + "follow-redirects": "^1.14.1", + "sumchecker": "^3.0.1" }, "bin": { "hugo": "cli.js", @@ -501,7 +502,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -515,7 +515,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1522,8 +1521,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.1.20", @@ -1979,6 +1977,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2692,7 +2701,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -3441,8 +3449,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nanoid": { "version": "3.1.20", @@ -3767,6 +3774,14 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "requires": { + "debug": "^4.1.0" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 06f04b6..deaa90e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "dependencies": { "cross-spawn": "^7.0.3", "decompress": "^4.2.1", - "follow-redirects": "^1.14.1" + "follow-redirects": "^1.14.1", + "sumchecker": "^3.0.1" }, "devDependencies": { "eslint": "^7.27.0",