pax_global_header00006660000000000000000000000064131564041750014520gustar00rootroot0000000000000052 comment=02df6303ff260b6b7da0b479f3e42222e8157b47 fresh-0.5.2/000077500000000000000000000000001315640417500126335ustar00rootroot00000000000000fresh-0.5.2/.eslintignore000066400000000000000000000000261315640417500153340ustar00rootroot00000000000000coverage node_modules fresh-0.5.2/.eslintrc000066400000000000000000000000341315640417500144540ustar00rootroot00000000000000{ "extends": "standard" } fresh-0.5.2/.gitignore000066400000000000000000000000501315640417500146160ustar00rootroot00000000000000node_modules coverage package-lock.json fresh-0.5.2/.travis.yml000066400000000000000000000020241315640417500147420ustar00rootroot00000000000000language: node_js node_js: - "0.6" - "0.8" - "0.10" - "0.12" - "1.8" - "2.5" - "3.3" - "4.8" - "5.12" - "6.11" - "7.10" - "8.5" sudo: false dist: precise before_install: # Skip updating shrinkwrap / lock - "npm config set shrinkwrap false" # Remove all non-test dependencies - "npm rm --save-dev beautify-benchmark benchmark" # Setup Node.js version-specific dependencies - "test $TRAVIS_NODE_VERSION != '0.6' || npm rm --save-dev istanbul" - "test $TRAVIS_NODE_VERSION != '0.8' || npm rm --save-dev istanbul" - "test $(echo $TRAVIS_NODE_VERSION | cut -d. -f1) -ge 4 || npm rm --save-dev $(grep -E '\"eslint\\S*\"' package.json | cut -d'\"' -f2)" script: # Run test script, depending on istanbul install - "test ! -z $(npm -ps ls istanbul) || npm test" - "test -z $(npm -ps ls istanbul) || npm run-script test-travis" - "test -z $(npm -ps ls eslint ) || npm run-script lint" after_script: - "test -e ./coverage/lcov.info && npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" fresh-0.5.2/HISTORY.md000066400000000000000000000027341315640417500143240ustar00rootroot000000000000000.5.2 / 2017-09-13 ================== * Fix regression matching multiple ETags in `If-None-Match` * perf: improve `If-None-Match` token parsing 0.5.1 / 2017-09-11 ================== * Fix handling of modified headers with invalid dates * perf: improve ETag match loop 0.5.0 / 2017-02-21 ================== * Fix incorrect result when `If-None-Match` has both `*` and ETags * Fix weak `ETag` matching to match spec * perf: delay reading header values until needed * perf: skip checking modified time if ETag check failed * perf: skip parsing `If-None-Match` when no `ETag` header * perf: use `Date.parse` instead of `new Date` 0.4.0 / 2017-02-05 ================== * Fix false detection of `no-cache` request directive * perf: enable strict mode * perf: hoist regular expressions * perf: remove duplicate conditional * perf: remove unnecessary boolean coercions 0.3.0 / 2015-05-12 ================== * Add weak `ETag` matching support 0.2.4 / 2014-09-07 ================== * Support Node.js 0.6 0.2.3 / 2014-09-07 ================== * Move repository to jshttp 0.2.2 / 2014-02-19 ================== * Revert "Fix for blank page on Safari reload" 0.2.1 / 2014-01-29 ================== * Fix for blank page on Safari reload 0.2.0 / 2013-08-11 ================== * Return stale for `Cache-Control: no-cache` 0.1.0 / 2012-06-15 ================== * Add `If-None-Match: *` support 0.0.1 / 2012-06-10 ================== * Initial release fresh-0.5.2/LICENSE000066400000000000000000000022261315640417500136420ustar00rootroot00000000000000(The MIT License) Copyright (c) 2012 TJ Holowaychuk Copyright (c) 2016-2017 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fresh-0.5.2/README.md000066400000000000000000000064561315640417500141250ustar00rootroot00000000000000# fresh [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-version-image]][node-version-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] HTTP response freshness testing ## Installation This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): ``` $ npm install fresh ``` ## API ```js var fresh = require('fresh') ``` ### fresh(reqHeaders, resHeaders) Check freshness of the response using request and response headers. When the response is still "fresh" in the client's cache `true` is returned, otherwise `false` is returned to indicate that the client cache is now stale and the full response should be sent. When a client sends the `Cache-Control: no-cache` request header to indicate an end-to-end reload request, this module will return `false` to make handling these requests transparent. ## Known Issues This module is designed to only follow the HTTP specifications, not to work-around all kinda of client bugs (especially since this module typically does not recieve enough information to understand what the client actually is). There is a known issue that in certain versions of Safari, Safari will incorrectly make a request that allows this module to validate freshness of the resource even when Safari does not have a representation of the resource in the cache. The module [jumanji](https://www.npmjs.com/package/jumanji) can be used in an Express application to work-around this issue and also provides links to further reading on this Safari bug. ## Example ### API usage ```js var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"bar"' } fresh(reqHeaders, resHeaders) // => false var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"foo"' } fresh(reqHeaders, resHeaders) // => true ``` ### Using with Node.js http server ```js var fresh = require('fresh') var http = require('http') var server = http.createServer(function (req, res) { // perform server logic // ... including adding ETag / Last-Modified response headers if (isFresh(req, res)) { // client has a fresh copy of resource res.statusCode = 304 res.end() return } // send the resource res.statusCode = 200 res.end('hello, world!') }) function isFresh (req, res) { return fresh(req.headers, { 'etag': res.getHeader('ETag'), 'last-modified': res.getHeader('Last-Modified') }) } server.listen(3000) ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/fresh.svg [npm-url]: https://npmjs.org/package/fresh [node-version-image]: https://img.shields.io/node/v/fresh.svg [node-version-url]: https://nodejs.org/en/ [travis-image]: https://img.shields.io/travis/jshttp/fresh/master.svg [travis-url]: https://travis-ci.org/jshttp/fresh [coveralls-image]: https://img.shields.io/coveralls/jshttp/fresh/master.svg [coveralls-url]: https://coveralls.io/r/jshttp/fresh?branch=master [downloads-image]: https://img.shields.io/npm/dm/fresh.svg [downloads-url]: https://npmjs.org/package/fresh fresh-0.5.2/benchmark/000077500000000000000000000000001315640417500145655ustar00rootroot00000000000000fresh-0.5.2/benchmark/etag.js000066400000000000000000000016141315640417500160450ustar00rootroot00000000000000 /** * Module dependencies. */ var benchmark = require('benchmark') var benchmarks = require('beautify-benchmark') /** * Globals for benchmark.js */ global.fresh = require('..') var suite = new benchmark.Suite() suite.add({ name: 'star', minSamples: 100, fn: 'var val = fresh({ \'if-none-match\': \'*\' }, { etag: \'"foo"\' })' }) suite.add({ name: 'single etag', minSamples: 100, fn: 'var val = fresh({ \'if-none-match\': \'"foo"\' }, { etag: \'"foo"\' })' }) suite.add({ name: 'several etags', minSamples: 100, fn: 'var val = fresh({ \'if-none-match\': \'"foo", "bar", "fizz", "buzz"\' }, { etag: \'"buzz"\' })' }) suite.on('start', function onCycle (event) { process.stdout.write(' etag\n\n') }) suite.on('cycle', function onCycle (event) { benchmarks.add(event.target) }) suite.on('complete', function onComplete () { benchmarks.log() }) suite.run({async: false}) fresh-0.5.2/benchmark/index.js000066400000000000000000000013751315640417500162400ustar00rootroot00000000000000var fs = require('fs') var path = require('path') var spawn = require('child_process').spawn var exe = process.argv[0] var cwd = process.cwd() for (var dep in process.versions) { console.log(' %s@%s', dep, process.versions[dep]) } console.log('') runScripts(fs.readdirSync(__dirname)) function runScripts (fileNames) { var fileName = fileNames.shift() if (!fileName) return if (!/\.js$/i.test(fileName)) return runScripts(fileNames) if (fileName.toLowerCase() === 'index.js') return runScripts(fileNames) var fullPath = path.join(__dirname, fileName) console.log('> %s %s', exe, path.relative(cwd, fullPath)) var proc = spawn(exe, [fullPath], { 'stdio': 'inherit' }) proc.on('exit', function () { runScripts(fileNames) }) } fresh-0.5.2/benchmark/modified.js000066400000000000000000000015701315640417500167060ustar00rootroot00000000000000 /** * Module dependencies. */ var benchmark = require('benchmark') var benchmarks = require('beautify-benchmark') /** * Globals for benchmark.js */ global.fresh = require('..') var suite = new benchmark.Suite() suite.add({ name: 'not modified', minSamples: 100, fn: 'var val = fresh({ \'if-modified-since\': \'Fri, 01 Jan 2010 00:00:00 GMT\' }, { \'last-modified\': \'Sat, 01 Jan 2000 00:00:00 GMT\' })' }) suite.add({ name: 'modified', minSamples: 100, fn: 'var val = fresh({ \'if-modified-since\': \'Mon, 01 Jan 1990 00:00:00 GMT\' }, { \'last-modified\': \'Sat, 01 Jan 2000 00:00:00 GMT\' })' }) suite.on('start', function onCycle (event) { process.stdout.write(' modified\n\n') }) suite.on('cycle', function onCycle (event) { benchmarks.add(event.target) }) suite.on('complete', function onComplete () { benchmarks.log() }) suite.run({async: false}) fresh-0.5.2/index.js000066400000000000000000000052271315640417500143060ustar00rootroot00000000000000/*! * fresh * Copyright(c) 2012 TJ Holowaychuk * Copyright(c) 2016-2017 Douglas Christopher Wilson * MIT Licensed */ 'use strict' /** * RegExp to check for no-cache token in Cache-Control. * @private */ var CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/ /** * Module exports. * @public */ module.exports = fresh /** * Check freshness of the response using request and response headers. * * @param {Object} reqHeaders * @param {Object} resHeaders * @return {Boolean} * @public */ function fresh (reqHeaders, resHeaders) { // fields var modifiedSince = reqHeaders['if-modified-since'] var noneMatch = reqHeaders['if-none-match'] // unconditional request if (!modifiedSince && !noneMatch) { return false } // Always return stale when Cache-Control: no-cache // to support end-to-end reload requests // https://tools.ietf.org/html/rfc2616#section-14.9.4 var cacheControl = reqHeaders['cache-control'] if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) { return false } // if-none-match if (noneMatch && noneMatch !== '*') { var etag = resHeaders['etag'] if (!etag) { return false } var etagStale = true var matches = parseTokenList(noneMatch) for (var i = 0; i < matches.length; i++) { var match = matches[i] if (match === etag || match === 'W/' + etag || 'W/' + match === etag) { etagStale = false break } } if (etagStale) { return false } } // if-modified-since if (modifiedSince) { var lastModified = resHeaders['last-modified'] var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince)) if (modifiedStale) { return false } } return true } /** * Parse an HTTP Date into a number. * * @param {string} date * @private */ function parseHttpDate (date) { var timestamp = date && Date.parse(date) // istanbul ignore next: guard against date.js Date.parse patching return typeof timestamp === 'number' ? timestamp : NaN } /** * Parse a HTTP token list. * * @param {string} str * @private */ function parseTokenList (str) { var end = 0 var list = [] var start = 0 // gather tokens for (var i = 0, len = str.length; i < len; i++) { switch (str.charCodeAt(i)) { case 0x20: /* */ if (start === end) { start = end = i + 1 } break case 0x2c: /* , */ list.push(str.substring(start, end)) start = end = i + 1 break default: end = i + 1 break } } // final token list.push(str.substring(start, end)) return list } fresh-0.5.2/package.json000066400000000000000000000025151315640417500151240ustar00rootroot00000000000000{ "name": "fresh", "description": "HTTP response freshness testing", "version": "0.5.2", "author": "TJ Holowaychuk (http://tjholowaychuk.com)", "contributors": [ "Douglas Christopher Wilson ", "Jonathan Ong (http://jongleberry.com)" ], "license": "MIT", "keywords": [ "fresh", "http", "conditional", "cache" ], "repository": "jshttp/fresh", "devDependencies": { "beautify-benchmark": "0.2.4", "benchmark": "2.1.4", "eslint": "3.19.0", "eslint-config-standard": "10.2.1", "eslint-plugin-import": "2.7.0", "eslint-plugin-markdown": "1.0.0-beta.6", "eslint-plugin-node": "5.1.1", "eslint-plugin-promise": "3.5.0", "eslint-plugin-standard": "3.0.1", "istanbul": "0.4.5", "mocha": "1.21.5" }, "files": [ "HISTORY.md", "LICENSE", "index.js" ], "engines": { "node": ">= 0.6" }, "scripts": { "bench": "node benchmark/index.js", "lint": "eslint --plugin markdown --ext js,md .", "test": "mocha --reporter spec --bail --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" } } fresh-0.5.2/test/000077500000000000000000000000001315640417500136125ustar00rootroot00000000000000fresh-0.5.2/test/.eslintrc000066400000000000000000000000441315640417500154340ustar00rootroot00000000000000{ "env": { "mocha": true } } fresh-0.5.2/test/fresh.js000066400000000000000000000154451315640417500152700ustar00rootroot00000000000000 var assert = require('assert') var fresh = require('..') describe('fresh(reqHeaders, resHeaders)', function () { describe('when a non-conditional GET is performed', function () { it('should be stale', function () { var reqHeaders = {} var resHeaders = {} assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when requested with If-None-Match', function () { describe('when ETags match', function () { it('should be fresh', function () { var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when ETags mismatch', function () { it('should be stale', function () { var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"bar"' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when at least one matches', function () { it('should be fresh', function () { var reqHeaders = { 'if-none-match': ' "bar" , "foo"' } var resHeaders = { 'etag': '"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when etag is missing', function () { it('should be stale', function () { var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = {} assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when ETag is weak', function () { it('should be fresh on exact match', function () { var reqHeaders = { 'if-none-match': 'W/"foo"' } var resHeaders = { 'etag': 'W/"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) it('should be fresh on strong match', function () { var reqHeaders = { 'if-none-match': 'W/"foo"' } var resHeaders = { 'etag': '"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when ETag is strong', function () { it('should be fresh on exact match', function () { var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) it('should be fresh on weak match', function () { var reqHeaders = { 'if-none-match': '"foo"' } var resHeaders = { 'etag': 'W/"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when * is given', function () { it('should be fresh', function () { var reqHeaders = { 'if-none-match': '*' } var resHeaders = { 'etag': '"foo"' } assert.ok(fresh(reqHeaders, resHeaders)) }) it('should get ignored if not only value', function () { var reqHeaders = { 'if-none-match': '*, "bar"' } var resHeaders = { 'etag': '"foo"' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) }) describe('when requested with If-Modified-Since', function () { describe('when modified since the date', function () { it('should be stale', function () { var reqHeaders = { 'if-modified-since': 'Sat, 01 Jan 2000 00:00:00 GMT' } var resHeaders = { 'last-modified': 'Sat, 01 Jan 2000 01:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when unmodified since the date', function () { it('should be fresh', function () { var reqHeaders = { 'if-modified-since': 'Sat, 01 Jan 2000 01:00:00 GMT' } var resHeaders = { 'last-modified': 'Sat, 01 Jan 2000 00:00:00 GMT' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when Last-Modified is missing', function () { it('should be stale', function () { var reqHeaders = { 'if-modified-since': 'Sat, 01 Jan 2000 00:00:00 GMT' } var resHeaders = {} assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('with invalid If-Modified-Since date', function () { it('should be stale', function () { var reqHeaders = { 'if-modified-since': 'foo' } var resHeaders = { 'last-modified': 'Sat, 01 Jan 2000 00:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('with invalid Last-Modified date', function () { it('should be stale', function () { var reqHeaders = { 'if-modified-since': 'Sat, 01 Jan 2000 00:00:00 GMT' } var resHeaders = { 'last-modified': 'foo' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) }) describe('when requested with If-Modified-Since and If-None-Match', function () { describe('when both match', function () { it('should be fresh', function () { var reqHeaders = { 'if-none-match': '"foo"', 'if-modified-since': 'Sat, 01 Jan 2000 01:00:00 GMT' } var resHeaders = { 'etag': '"foo"', 'last-modified': 'Sat, 01 Jan 2000 00:00:00 GMT' } assert.ok(fresh(reqHeaders, resHeaders)) }) }) describe('when only ETag matches', function () { it('should be stale', function () { var reqHeaders = { 'if-none-match': '"foo"', 'if-modified-since': 'Sat, 01 Jan 2000 00:00:00 GMT' } var resHeaders = { 'etag': '"foo"', 'last-modified': 'Sat, 01 Jan 2000 01:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when only Last-Modified matches', function () { it('should be stale', function () { var reqHeaders = { 'if-none-match': '"foo"', 'if-modified-since': 'Sat, 01 Jan 2000 01:00:00 GMT' } var resHeaders = { 'etag': '"bar"', 'last-modified': 'Sat, 01 Jan 2000 00:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when none match', function () { it('should be stale', function () { var reqHeaders = { 'if-none-match': '"foo"', 'if-modified-since': 'Sat, 01 Jan 2000 00:00:00 GMT' } var resHeaders = { 'etag': '"bar"', 'last-modified': 'Sat, 01 Jan 2000 01:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) }) describe('when requested with Cache-Control: no-cache', function () { it('should be stale', function () { var reqHeaders = { 'cache-control': ' no-cache' } var resHeaders = {} assert.ok(!fresh(reqHeaders, resHeaders)) }) describe('when ETags match', function () { it('should be stale', function () { var reqHeaders = { 'cache-control': ' no-cache', 'if-none-match': '"foo"' } var resHeaders = { 'etag': '"foo"' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) describe('when unmodified since the date', function () { it('should be stale', function () { var reqHeaders = { 'cache-control': ' no-cache', 'if-modified-since': 'Sat, 01 Jan 2000 01:00:00 GMT' } var resHeaders = { 'last-modified': 'Sat, 01 Jan 2000 00:00:00 GMT' } assert.ok(!fresh(reqHeaders, resHeaders)) }) }) }) })