From 4ee5ddb496bdd3ab107efa26a001a87ac343fd76 Mon Sep 17 00:00:00 2001 From: muxator Date: Tue, 31 Mar 2020 03:29:46 +0200 Subject: [PATCH] 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] https://github.com/microsoft/TypeScript/commit/9677b0641cc5ba7d8b701b4f892ed7e54ceaee9a [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 --- src/node/utils/caching_middleware.js | 47 ++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index 5ef92b6f4..bc387ca2c 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -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']