/** * Resource Audit Script * Belongs to Decentraleyes. * * @author Thomas Rientjes * @since 2014-07-24 * @license MPL 2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Imports */ var fileSystem, crypto, https, sourceMappingURL; fileSystem = require('fs'); crypto = require('crypto'); https = require('https'); sourceMappingURL = require('source-map-url'); /** * Variables */ var localResourceLocation = '../resources'; var localResourceLocationLength = localResourceLocation.length; var localResourcePaths = []; var comparedResourceAmount = 0; var resourceAmount = 0; /** * Functions */ function _fetchLocalResourcePaths (folderPath) { fileSystem.readdirSync(folderPath).forEach(function (resourceName) { var resourcePath = `${folderPath}/${resourceName}`; var resourceStatistics = fileSystem.statSync(resourcePath); if (resourceStatistics && resourceStatistics.isDirectory()) { _fetchLocalResourcePaths(resourcePath); } else { localResourcePaths.push(resourcePath); } }); return localResourcePaths; } function _getLocalResourceContents (fileLocation, callback) { fileSystem.exists(fileLocation, function (exists) { if (exists) { fileSystem.stat(fileLocation, function (error, statistics) { fileSystem.open(fileLocation, 'r', function (error, fileDescriptor) { var buffer = new Buffer(statistics.size); fileSystem.read(fileDescriptor, buffer, 0, buffer.length, null, function (error, bytesRead, buffer) { var localFileContents = buffer.toString('utf8', 0, buffer.length); fileSystem.close(fileDescriptor, function () {}); callback(localFileContents); }); }); }); } }); } function _getRemoteResourceContents (remoteResourceRoute, callback) { var resourceURL = `https://ajax.googleapis.com/ajax/libs/${remoteResourceRoute}`; https.get(resourceURL, function (response) { var resourceContents = ''; response.on('data', function (chunk) { resourceContents += chunk; }); response.on('end', function () { if (response.statusCode === 200) { callback(resourceContents, resourceURL); } else { resourceURL = `https://cdnjs.cloudflare.com/ajax/libs/${remoteResourceRoute}`; https.get(resourceURL, function (response) { resourceContents = ''; response.on('data', function (chunk) { resourceContents += chunk; }); response.on('end', function () { if (response.statusCode !== 200) { throw `Error: Resource ${remoteResourceRoute} could not be fetched.`; } callback(resourceContents, resourceURL); }); }); } }); }); } function _hashFileContents (fileContents) { var hash; hash = crypto.createHash('sha512'); hash.setEncoding('hex'); hash.write(fileContents); hash.end(); return hash.read(); } function _showCompletedMessage () { console.log(); console.log(' *** FILE INTEGRITY CHECKS COMPLETED'); console.log(` *** ${resourceAmount}/${resourceAmount} RESOURCES WERE ANALYZED`); console.log(); } function _incrementComparedResourceAmount () { comparedResourceAmount++; if (comparedResourceAmount === resourceAmount) { setTimeout(function () { _showCompletedMessage(); }, 500); } } function _compareResources (localResourceContents, remoteResourceContents, URL) { var hasSourceMappingURL = sourceMappingURL.existsIn(remoteResourceContents); var sourceMappingNotice = '[ ] REMOTE RESOURCE HAD SOURCE MAPPING URL'; if (hasSourceMappingURL) { remoteResourceContents = sourceMappingURL.removeFrom(remoteResourceContents); sourceMappingNotice = '[X] REMOTE RESOURCE HAD SOURCE MAPPING URL'; } var localResourceHash = _hashFileContents(localResourceContents); var remoteResourceHash = _hashFileContents(remoteResourceContents); console.log(`RESOURCE HASH (SHA512): ${localResourceHash}`); console.log(`RESOURCE HASH (SHA512): ${remoteResourceHash}`); var fileHashesMatch = (localResourceHash === remoteResourceHash); if (!fileHashesMatch) { console.log(URL); console.log(remoteResourceContents); throw 'Error: Decentraleyes resource hash mismatch.'; } console.log(); console.log('[X] LOCAL AND REMOTE RESOURCE HASHES MATCH'); console.log(sourceMappingNotice); _incrementComparedResourceAmount(); } /** * Initializations */ _fetchLocalResourcePaths(localResourceLocation); resourceAmount = localResourcePaths.length; /** * Script */ localResourcePaths.forEach(function (resourcePath) { var resourceRoute = resourcePath.substr(localResourceLocationLength + 1); resourceRoute = resourceRoute.substring(0, resourceRoute.length - 1); _getLocalResourceContents(resourcePath, function (localResourceContents) { _getRemoteResourceContents(resourceRoute, function (remoteResourceContents, URL) { console.log(); console.log(resourceRoute.toUpperCase()); console.log(); // Compare resource content hashes. _compareResources(localResourceContents, remoteResourceContents, URL); console.log(); console.log('------------------------------------------'); }); }); });