Created
May 12, 2019 07:13
-
-
Save joshuatz/4c2f321c19e503703037536751d01f29 to your computer and use it in GitHub Desktop.
File downloader that you can call with Node and pass a URL, that has options for checking file integrity after download
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Joshua Tzucker | |
* @file A reusable script to download a remote file, usually to have as a dependency, that for some reason can't be fetched through NPM | |
*/ | |
/** | |
* File should be called with arguments: | |
* file_downloader.js remote_file local_folder_to_save_to [newFileName="foobar.js"][forceReDownload=FALSE] [fileHash="sha256-adsfasfd"] | |
* fileHash should be base64 - follow rule of SRI - https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity | |
* Example: | |
* node file_downloader.js https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js ./ fileHash="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" | |
*/ | |
/** | |
* Dependencies | |
*/ | |
var fs = require('fs-extra'); | |
var http = require('http'); | |
var https = require('https'); | |
var protocol = http; | |
/** | |
* Check for crypto support | |
*/ | |
let crypto; | |
let sysHasCrypto = false; | |
try { | |
crypto = require('crypto'); | |
sysHasCrypto = true; | |
} | |
catch (e){ | |
// | |
} | |
/** | |
* Parse command line arguments | |
*/ | |
let LOCAL_FOLDER = ''; | |
let LOCAL_FILE_PATH = ''; | |
let REMOTE_PATH = ''; | |
let FILENAME = ''; | |
let forceReDownload = false; | |
let checkHash = false; | |
let remoteHash = ''; | |
process.argv.forEach((val,index,arr)=>{ | |
// Note that first arg is automatically full path of node, second is the path of this file, and then 3rd+ are actual args passed by user | |
console.log(val); | |
if (index===2){ | |
REMOTE_PATH = val; | |
if (/https/i.test(REMOTE_PATH)){ | |
protocol = https; | |
} | |
console.log(REMOTE_PATH); | |
// Try to guess filename based on remote REMOTE_PATH | |
FILENAME = /\/([^\/]+\.[^\/]+)$/.exec(REMOTE_PATH)[1]; | |
} | |
else if (index===3){ | |
LOCAL_FOLDER = val; | |
} | |
else if (/forceReDownload=(FALSE|TRUE)/i.test(val)) { | |
forceReDownload = /true/i.test(val); | |
} | |
else if (/fileHash="{0,1}([^"\s]+)"{0,1}/i.test(val)) { | |
remoteHash = /fileHash="{0,1}([^"\s]+)"{0,1}/i.exec(val)[1]; | |
checkHash = true; | |
console.log("Turning on file hash checking"); | |
} | |
else if (/newFileName="{0,1}([^"\s]+)"{0,1}/i.test(val)) { | |
FILENAME = /newFileName="{0,1}([^"\s]+)"{0,1}/i.exec(val)[1]; | |
} | |
}); | |
LOCAL_FILE_PATH = LOCAL_FOLDER + FILENAME; | |
// Make sure folder exists | |
if(!fs.existsSync(LOCAL_FOLDER)){ | |
console.log('Could not find ' + LOCAL_FOLDER + ' - making directory'); | |
fs.mkdirSync(LOCAL_FOLDER); | |
} | |
downloadFile(REMOTE_PATH,LOCAL_FILE_PATH,function(res){ | |
if (res){ | |
console.log('File downloaded successfully!'); | |
// Now check file integrity / hash | |
if (checkHash && sysHasCrypto){ | |
// Try to extract algo from string. Assume sha256 if not specified | |
let allowedAlgos = ["sha256","sha384","sha512"]; | |
let algo = "sha256"; | |
if (/(sha\d+)-(.+)$/.test(remoteHash)){ | |
algo = /(sha\d+)-(.+)$/.exec(remoteHash)[1]; | |
remoteHash = /(sha\d+)-(.+)$/.exec(remoteHash)[2]; | |
} | |
let computedDigestHash = ''; | |
// Compute digest hash and compare | |
const fileHash = crypto.createHash(algo); | |
// Generate buffer from file | |
let localFileData = fs.readFileSync(LOCAL_FILE_PATH); | |
// Update hash with buffer data | |
fileHash.update(localFileData); | |
// Get base64 encoded digest of hash | |
computedDigestHash = fileHash.digest('base64'); | |
if (computedDigestHash !== remoteHash){ | |
// Uh-oh! Hash mismatch, so delete file | |
console.error("Hash mismatch! File deleted."); | |
console.warn("Was Expecting Hash: " + remoteHash); | |
console.warn("Computed Hash: " + computedDigestHash); | |
fs.removeSync(LOCAL_FILE_PATH); | |
} | |
else { | |
console.log("File hash matched!"); | |
} | |
} | |
} | |
else { | |
console.error('File download failed!'); | |
} | |
},!forceReDownload); | |
/** | |
* Reusable download function (reminder to self: also in zipcode project WIP) | |
*/ | |
function downloadFile(remote,local,callback,OPT_useCache){ | |
var skipDownload = false; | |
// Default to use cache | |
OPT_useCache = typeof(OPT_useCache)!=='undefined' ? OPT_useCache : true; | |
if (OPT_useCache && fs.existsSync(local)){ | |
skipDownload = true; | |
console.log("Skipped file download; used cache instead"); | |
callback(false); | |
} | |
if (!skipDownload){ | |
var outputFile = fs.createWriteStream(local); | |
outputFile.on("open",function(){ | |
protocol.get(remote,function(response){ | |
response.pipe(outputFile); | |
response.on("end",function(){ | |
outputFile.end(); | |
callback(true); | |
}); | |
}).on("error",function(err){ | |
console.error("Error: " + err); | |
fs.unlink(local); | |
callback(false); | |
}); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment