From 97c4c4bcea10a136d54a22bff5acb0c893e54673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kornel=20Lesin=CC=81ski?= Date: Thu, 8 Dec 2016 00:50:09 +0000 Subject: [PATCH] Assume headers using pre-check are meaningless --- README.md | 2 ++ index.js | 10 +++++++++- test/responsetest.js | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b27494..22a30da 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ If `options.shared` is true (default), then response is evaluated from perspecti `options.cacheHeuristic` is a fraction of response's age that is used as a fallback cache duration. The default is 0.1 (10%), e.g. if a file hasn't been modified for 100 days, it'll be cached for 100*0.1 = 10 days. +If `options.ignoreCargoCult` is true, common anti-cache directives will be completely ignored if the `pre-check` and `post-check` directives are present. These two useless directives are most commonly found in bad StackOverflow answers and PHP's "session limiter" defaults. + ### `storable()` Returns `true` if the response can be stored in a cache. If it's `false` then you MUST NOT store either the request or the response. diff --git a/index.js b/index.js index 8c391d7..2e0eff8 100644 --- a/index.js +++ b/index.js @@ -23,7 +23,7 @@ function parseCacheControl(header) { } module.exports = class CachePolicy { - constructor(req, res, {shared, cacheHeuristic, _fromObject} = {}) { + constructor(req, res, {shared, cacheHeuristic, ignoreCargoCult, _fromObject} = {}) { if (_fromObject) { this._fromObject(_fromObject); return; @@ -50,6 +50,14 @@ module.exports = class CachePolicy { this._reqHeaders = res.headers.vary ? req.headers : null; // Don't keep all request headers if they won't be used this._reqcc = parseCacheControl(req.headers['cache-control']); + // Assume that if someone uses legacy, non-standard uncecessary options they don't understand caching, + // so there's no point stricly adhering to the blindly copy&pasted directives. + if (ignoreCargoCult && "pre-check" in this._rescc && "post-check" in this._rescc) { + delete this._rescc['no-cache']; + delete this._rescc['no-store']; + delete this._rescc['must-revalidate']; + } + // When the Cache-Control header field is not present in a request, caches MUST consider the no-cache request pragma-directive // as having the same effect as if "Cache-Control: no-cache" were present (see Section 5.2.1). if (!res.headers['cache-control'] && /no-cache/.test(res.headers.pragma)) { diff --git a/test/responsetest.js b/test/responsetest.js index cc9054e..2e9e227 100644 --- a/test/responsetest.js +++ b/test/responsetest.js @@ -34,6 +34,20 @@ describe('Response headers', function() { assert.equal(cache.maxAge(), 678); }); + it('pre-check tolerated', function() { + const cache = new CachePolicy(req, {headers:{'cache-control': 'pre-check=0, post-check=0, no-store, no-cache, max-age=100'}}); + assert(cache.stale()); + assert(!cache.storable()); + assert.equal(cache.maxAge(), 0); + }); + + it('pre-check poison', function() { + const cache = new CachePolicy(req, {headers:{'cache-control': 'pre-check=0, post-check=0, no-cache, no-store, max-age=100'}}, {ignoreCargoCult:true}); + assert(!cache.stale()); + assert(cache.storable()); + assert.equal(cache.maxAge(), 100); + }); + it('cache with expires', function() { const cache = new CachePolicy(req, {headers:{ 'date': new Date().toGMTString(),