| /*! | 
|  * 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 | 
| } |