From 8d0f9d708f385f42005018693a7c63720b5e1cf5 Mon Sep 17 00:00:00 2001 From: Martii Date: Thu, 20 Apr 2017 01:03:22 -0600 Subject: [PATCH] Ensure a unique eTag for meta.js * Go exclusively off of HTTP/1.1 eTag and ignore `last-modified`... supported since ~1996. * HTTP/1.0 should produce 429's in this configuration NOTES: * Some of the documentation out there on the web is a bit iffy compared to actual implementation in browsers... but does match RFC'd specs. * Edge case found with oujs - Meta View ... so this is why this is happening * This transfers the load to the CPU instead of the bandwidth and is required to get things near, if not, 100% Applies to #432 and post #1070 --- controllers/scriptStorage.js | 86 ++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/controllers/scriptStorage.js b/controllers/scriptStorage.js index 23ee07786..8b6683fe9 100644 --- a/controllers/scriptStorage.js +++ b/controllers/scriptStorage.js @@ -530,7 +530,6 @@ exports.sendScript = function (aReq, aRes, aNext) { let rAnyLocalHost = new RegExp('^(?:openuserjs\.org|oujs\.org' + (isDev ? '|localhost:' + (process.env.PORT || 8080) : '') + ')'); - var lastModified = null; var eTag = null; var maxAge = 1 * 60 * 60 * 24; // nth day(s) in seconds var now = null; @@ -590,12 +589,6 @@ exports.sendScript = function (aReq, aRes, aNext) { } // Set up server to client caching - lastModified = moment( - (!/\.min(\.user)?\.js$/.test(aReq._parsedUrl.pathname) - ? aScript.updated - : mtimeUglifyJS2 > aScript.updated ? mtimeUglifyJS2 : aScript.updated) - ).utc().format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT'; - // Create a based representation of the hex sha512sum eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + '"'; @@ -603,11 +596,8 @@ exports.sendScript = function (aReq, aRes, aNext) { aRes.set('Cache-Control', 'public, max-age=' + maxAge + ', no-cache, no-transform, must-revalidate'); - // NOTE: HTTP/1.0 Caching - aRes.set('Last-Modified', lastModified); - - // If already client-side... NOTE: HTTP/1.0 and/or HTTP/1.1 Caching - if (aReq.get('if-modified-since') === lastModified || aReq.get('if-none-match') === eTag) { + // If already client-side... NOTE: HTTP/1.1 Caching + if (aReq.get('if-none-match') === eTag) { aRes.status(304).send(); // Not Modified return; } @@ -751,35 +741,32 @@ exports.sendMeta = function (aReq, aRes, aNext) { var eTag = null; var maxAge = 1 * 60 * 60 * 24; // nth day(s) in seconds - var lastModified = null; + + var metadataBlocks = ''; if (!aScript) { aNext(); return; } - lastModified = aScript.updated; - - // Create a based representation of the hex sha512sum - eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + '"'; - - script = modelParser.parseScript(aScript); - meta = script.meta; // NOTE: Watchpoint - // NOTE: HTTP/1.1 Caching aRes.set('Cache-Control', 'public, max-age=' + maxAge + ', no-cache, no-transform, must-revalidate'); - // NOTE: HTTP/1.0 Caching - aRes.set('Last-Modified', lastModified); - - // If already client-side... NOTE: HTTP/1.0 and/or HTTP/1.1 Caching - if (aReq.get('if-modified-since') === lastModified || aReq.get('if-none-match') === eTag) { - aRes.status(304).send(); // Not Modified - return; - } + script = modelParser.parseScript(aScript); + meta = script.meta; // NOTE: Watchpoint if (/\.json$/.test(aReq.params.scriptname)) { + // Create a based representation of the hex sha512sum + eTag = '"' + Base62.encode(parseInt('0x' + aScript.hash, 16)) + '"'; + + // If already client-side... NOTE: HTTP/1.1 Caching + if (aReq.get('if-none-match') === eTag) { + aRes.status(304).send(); // Not Modified + return; + } + + // Okay to send JSON... aRes.set('Content-Type', 'application/json; charset=UTF-8'); // NOTE: HTTP/1.0 Caching @@ -808,35 +795,46 @@ exports.sendMeta = function (aReq, aRes, aNext) { async.parallel(tasks, asyncComplete); } else { - aRes.set('Content-Type', 'text/javascript; charset=UTF-8'); - - // NOTE: HTTP/1.0 Caching - aRes.set('Expires', moment(moment() + maxAge * 1000).utc() - .format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT'); - - // NOTE: HTTP/1.1 Caching - aRes.set('Etag', eTag); - - aRes.write('// ==UserScript==\n'); + metadataBlocks += '// ==UserScript==\n'; if (meta.UserScript.version) { - aRes.write('// @version' + whitespace + meta.UserScript.version[0].value + '\n'); + metadataBlocks += '// @version' + whitespace + meta.UserScript.version[0].value + '\n'; } Object.keys(meta.UserScript.name).forEach(function (aName) { var key = meta.UserScript.name[aName].key || 'name'; var value = meta.UserScript.name[aName].value; - aRes.write('// @' + key + whitespace + value + '\n'); + metadataBlocks += '// @' + key + whitespace + value + '\n'; }); if (meta.UserScript.namespace) { - aRes.write('// @namespace' + whitespace + meta.UserScript.namespace[0].value + '\n'); + metadataBlocks += '// @namespace' + whitespace + meta.UserScript.namespace[0].value + '\n'; } - aRes.write('// ==/UserScript==\n'); + metadataBlocks += '// ==/UserScript==\n'; - aRes.end(); + // Calculate unique eTag here... + eTag = '"' + Base62.encode( + parseInt('0x' + crypto.createHash('sha512').update(metadataBlocks).digest('hex'), 16)) + '"'; + + // If already client-side... NOTE: HTTP/1.1 Caching + if (aReq.get('if-none-match') === eTag) { + aRes.status(304).send(); // Not Modified + return; + } + + // Okay to send metadataBlocks + aRes.set('Content-Type', 'text/javascript; charset=UTF-8'); + + // NOTE: HTTP/1.0 Caching + aRes.set('Expires', moment(moment() + maxAge * 1000).utc() + .format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT'); + + // NOTE: HTTP/1.1 Caching + aRes.set('Etag', eTag); + + aRes.end(metadataBlocks); } }); };