UNPKG

2.71 kBJavaScriptView Raw
1/*!
2 * fresh
3 * Copyright(c) 2012 TJ Holowaychuk
4 * Copyright(c) 2016-2017 Douglas Christopher Wilson
5 * MIT Licensed
6 */
7
8'use strict'
9
10/**
11 * RegExp to check for no-cache token in Cache-Control.
12 * @private
13 */
14
15var CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/
16
17/**
18 * Module exports.
19 * @public
20 */
21
22module.exports = fresh
23
24/**
25 * Check freshness of the response using request and response headers.
26 *
27 * @param {Object} reqHeaders
28 * @param {Object} resHeaders
29 * @return {Boolean}
30 * @public
31 */
32
33function fresh (reqHeaders, resHeaders) {
34 // fields
35 var modifiedSince = reqHeaders['if-modified-since']
36 var noneMatch = reqHeaders['if-none-match']
37
38 // unconditional request
39 if (!modifiedSince && !noneMatch) {
40 return false
41 }
42
43 // Always return stale when Cache-Control: no-cache
44 // to support end-to-end reload requests
45 // https://tools.ietf.org/html/rfc2616#section-14.9.4
46 var cacheControl = reqHeaders['cache-control']
47 if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
48 return false
49 }
50
51 // if-none-match
52 if (noneMatch && noneMatch !== '*') {
53 var etag = resHeaders['etag']
54
55 if (!etag) {
56 return false
57 }
58
59 var etagStale = true
60 var matches = parseTokenList(noneMatch)
61 for (var i = 0; i < matches.length; i++) {
62 var match = matches[i]
63 if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {
64 etagStale = false
65 break
66 }
67 }
68
69 if (etagStale) {
70 return false
71 }
72 }
73
74 // if-modified-since
75 if (modifiedSince) {
76 var lastModified = resHeaders['last-modified']
77 var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))
78
79 if (modifiedStale) {
80 return false
81 }
82 }
83
84 return true
85}
86
87/**
88 * Parse an HTTP Date into a number.
89 *
90 * @param {string} date
91 * @private
92 */
93
94function parseHttpDate (date) {
95 var timestamp = date && Date.parse(date)
96
97 // istanbul ignore next: guard against date.js Date.parse patching
98 return typeof timestamp === 'number'
99 ? timestamp
100 : NaN
101}
102
103/**
104 * Parse a HTTP token list.
105 *
106 * @param {string} str
107 * @private
108 */
109
110function parseTokenList (str) {
111 var end = 0
112 var list = []
113 var start = 0
114
115 // gather tokens
116 for (var i = 0, len = str.length; i < len; i++) {
117 switch (str.charCodeAt(i)) {
118 case 0x20: /* */
119 if (start === end) {
120 start = end = i + 1
121 }
122 break
123 case 0x2c: /* , */
124 list.push(str.substring(start, end))
125 start = end = i + 1
126 break
127 default:
128 end = i + 1
129 break
130 }
131 }
132
133 // final token
134 list.push(str.substring(start, end))
135
136 return list
137}