caching_midleware: also run when nodejs does not have crypto module

According to the nodejs docs [0] the `crypto` module might be unavailable on
some platforms:

> It is possible for Node.js to be built without including support for the
> crypto module. In such cases, calling require('crypto') will result in an
> error being thrown

A description of such scenarios can be found here [1].

> * running non-standard node in a resource- or security-constrained
>   environment
> * running in emulated environment (browserify, webpack etc.)
> * building node from source and omitting openssl/crypto for random reason

TypeScript guys dealt with this same issue and they resolved it in an elegant
way in [2].

We copy that approach here: if importing crypto fails at runtime, we replace
sha256 with djb2 [3], which is weaker, but works for our case.

The djb2 story is fun: see this Stack Overflow post [4], and the original
mailing list post from 1991 [5] by Daniel J. Bernstein [6].

He was 20 at the time!

[0] https://nodejs.org/docs/latest-v10.x/api/crypto.html#crypto_determining_if_crypto_support_is_unavailable
[1] https://github.com/microsoft/TypeScript/issues/19100#issuecomment-335871998
[2] 9677b0641c
[3] http://www.cse.yorku.ca/~oz/hash.html#djb2
[4] https://stackoverflow.com/questions/1579721/why-are-5381-and-33-so-important-in-the-djb2-algorithm
[5] https://groups.google.com/forum/#!msg/comp.lang.c/lSKWXiuNOAk/zstZ3SRhCjgJ
[6] https://en.wikipedia.org/wiki/Daniel_J._Bernstein
This commit is contained in:
muxator 2020-03-31 03:29:46 +02:00 committed by muxator
parent fc754c9a1d
commit 4ee5ddb496

View file

@ -22,13 +22,56 @@ var zlib = require('zlib');
var settings = require('./Settings');
var semver = require('semver');
var existsSync = require('./path_exists');
var crypto = require('crypto');
/*
* The crypto module can be absent on reduced node installations.
*
* Here we copy the approach TypeScript guys used for https://github.com/microsoft/TypeScript/issues/19100
* If importing crypto fails at runtime, we replace sha256 with djb2, which is
* weaker, but works for our case.
*
* djb2 was written in 1991 by Daniel J. Bernstein.
*
*/
// MIMIC https://github.com/microsoft/TypeScript/commit/9677b0641cc5ba7d8b701b4f892ed7e54ceaee9a - START
let _crypto;
try {
_crypto = require('crypto');
} catch {
_crypto = undefined;
}
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {};
function djb2Hash(data) {
const chars = data.split("").map(str => str.charCodeAt(0));
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
}
function generateCacheKeyWithSha256(path) {
return _crypto.createHash('sha256').update(path).digest('hex');
}
function generateCacheKeyWithDjb2(path) {
return Buffer.from(djb2Hash(path)).toString('hex');
}
let generateCacheKey;
if (_crypto) {
generateCacheKey = generateCacheKeyWithSha256;
} else {
generateCacheKey = generateCacheKeyWithDjb2;
console.warn('No crypto support in this nodejs runtime. A fallback to Djb2 (weaker) will be used.');
}
// MIMIC https://github.com/microsoft/TypeScript/commit/9677b0641cc5ba7d8b701b4f892ed7e54ceaee9a - END
/*
This caches and compresses 200 and 404 responses to GET and HEAD requests.
TODO: Caching and compressing are solved problems, a middleware configuration
@ -50,7 +93,7 @@ CachingMiddleware.prototype = new function () {
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path;
var cacheKey = crypto.createHash('sha256').update(path).digest('hex');
var cacheKey = generateCacheKey(path);
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
var modifiedSince = (req.headers['if-modified-since']