index.js (2711B)
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 15 var CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/ 16 17 /** 18 * Module exports. 19 * @public 20 */ 21 22 module.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 33 function 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 94 function 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 110 function 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 }