11cb0ef41Sopenharmony_ci'use strict'; 21cb0ef41Sopenharmony_ci// rfc7231 6.1 31cb0ef41Sopenharmony_ciconst statusCodeCacheableByDefault = new Set([ 41cb0ef41Sopenharmony_ci 200, 51cb0ef41Sopenharmony_ci 203, 61cb0ef41Sopenharmony_ci 204, 71cb0ef41Sopenharmony_ci 206, 81cb0ef41Sopenharmony_ci 300, 91cb0ef41Sopenharmony_ci 301, 101cb0ef41Sopenharmony_ci 308, 111cb0ef41Sopenharmony_ci 404, 121cb0ef41Sopenharmony_ci 405, 131cb0ef41Sopenharmony_ci 410, 141cb0ef41Sopenharmony_ci 414, 151cb0ef41Sopenharmony_ci 501, 161cb0ef41Sopenharmony_ci]); 171cb0ef41Sopenharmony_ci 181cb0ef41Sopenharmony_ci// This implementation does not understand partial responses (206) 191cb0ef41Sopenharmony_ciconst understoodStatuses = new Set([ 201cb0ef41Sopenharmony_ci 200, 211cb0ef41Sopenharmony_ci 203, 221cb0ef41Sopenharmony_ci 204, 231cb0ef41Sopenharmony_ci 300, 241cb0ef41Sopenharmony_ci 301, 251cb0ef41Sopenharmony_ci 302, 261cb0ef41Sopenharmony_ci 303, 271cb0ef41Sopenharmony_ci 307, 281cb0ef41Sopenharmony_ci 308, 291cb0ef41Sopenharmony_ci 404, 301cb0ef41Sopenharmony_ci 405, 311cb0ef41Sopenharmony_ci 410, 321cb0ef41Sopenharmony_ci 414, 331cb0ef41Sopenharmony_ci 501, 341cb0ef41Sopenharmony_ci]); 351cb0ef41Sopenharmony_ci 361cb0ef41Sopenharmony_ciconst errorStatusCodes = new Set([ 371cb0ef41Sopenharmony_ci 500, 381cb0ef41Sopenharmony_ci 502, 391cb0ef41Sopenharmony_ci 503, 401cb0ef41Sopenharmony_ci 504, 411cb0ef41Sopenharmony_ci]); 421cb0ef41Sopenharmony_ci 431cb0ef41Sopenharmony_ciconst hopByHopHeaders = { 441cb0ef41Sopenharmony_ci date: true, // included, because we add Age update Date 451cb0ef41Sopenharmony_ci connection: true, 461cb0ef41Sopenharmony_ci 'keep-alive': true, 471cb0ef41Sopenharmony_ci 'proxy-authenticate': true, 481cb0ef41Sopenharmony_ci 'proxy-authorization': true, 491cb0ef41Sopenharmony_ci te: true, 501cb0ef41Sopenharmony_ci trailer: true, 511cb0ef41Sopenharmony_ci 'transfer-encoding': true, 521cb0ef41Sopenharmony_ci upgrade: true, 531cb0ef41Sopenharmony_ci}; 541cb0ef41Sopenharmony_ci 551cb0ef41Sopenharmony_ciconst excludedFromRevalidationUpdate = { 561cb0ef41Sopenharmony_ci // Since the old body is reused, it doesn't make sense to change properties of the body 571cb0ef41Sopenharmony_ci 'content-length': true, 581cb0ef41Sopenharmony_ci 'content-encoding': true, 591cb0ef41Sopenharmony_ci 'transfer-encoding': true, 601cb0ef41Sopenharmony_ci 'content-range': true, 611cb0ef41Sopenharmony_ci}; 621cb0ef41Sopenharmony_ci 631cb0ef41Sopenharmony_cifunction toNumberOrZero(s) { 641cb0ef41Sopenharmony_ci const n = parseInt(s, 10); 651cb0ef41Sopenharmony_ci return isFinite(n) ? n : 0; 661cb0ef41Sopenharmony_ci} 671cb0ef41Sopenharmony_ci 681cb0ef41Sopenharmony_ci// RFC 5861 691cb0ef41Sopenharmony_cifunction isErrorResponse(response) { 701cb0ef41Sopenharmony_ci // consider undefined response as faulty 711cb0ef41Sopenharmony_ci if(!response) { 721cb0ef41Sopenharmony_ci return true 731cb0ef41Sopenharmony_ci } 741cb0ef41Sopenharmony_ci return errorStatusCodes.has(response.status); 751cb0ef41Sopenharmony_ci} 761cb0ef41Sopenharmony_ci 771cb0ef41Sopenharmony_cifunction parseCacheControl(header) { 781cb0ef41Sopenharmony_ci const cc = {}; 791cb0ef41Sopenharmony_ci if (!header) return cc; 801cb0ef41Sopenharmony_ci 811cb0ef41Sopenharmony_ci // TODO: When there is more than one value present for a given directive (e.g., two Expires header fields, multiple Cache-Control: max-age directives), 821cb0ef41Sopenharmony_ci // the directive's value is considered invalid. Caches are encouraged to consider responses that have invalid freshness information to be stale 831cb0ef41Sopenharmony_ci const parts = header.trim().split(/,/); 841cb0ef41Sopenharmony_ci for (const part of parts) { 851cb0ef41Sopenharmony_ci const [k, v] = part.split(/=/, 2); 861cb0ef41Sopenharmony_ci cc[k.trim()] = v === undefined ? true : v.trim().replace(/^"|"$/g, ''); 871cb0ef41Sopenharmony_ci } 881cb0ef41Sopenharmony_ci 891cb0ef41Sopenharmony_ci return cc; 901cb0ef41Sopenharmony_ci} 911cb0ef41Sopenharmony_ci 921cb0ef41Sopenharmony_cifunction formatCacheControl(cc) { 931cb0ef41Sopenharmony_ci let parts = []; 941cb0ef41Sopenharmony_ci for (const k in cc) { 951cb0ef41Sopenharmony_ci const v = cc[k]; 961cb0ef41Sopenharmony_ci parts.push(v === true ? k : k + '=' + v); 971cb0ef41Sopenharmony_ci } 981cb0ef41Sopenharmony_ci if (!parts.length) { 991cb0ef41Sopenharmony_ci return undefined; 1001cb0ef41Sopenharmony_ci } 1011cb0ef41Sopenharmony_ci return parts.join(', '); 1021cb0ef41Sopenharmony_ci} 1031cb0ef41Sopenharmony_ci 1041cb0ef41Sopenharmony_cimodule.exports = class CachePolicy { 1051cb0ef41Sopenharmony_ci constructor( 1061cb0ef41Sopenharmony_ci req, 1071cb0ef41Sopenharmony_ci res, 1081cb0ef41Sopenharmony_ci { 1091cb0ef41Sopenharmony_ci shared, 1101cb0ef41Sopenharmony_ci cacheHeuristic, 1111cb0ef41Sopenharmony_ci immutableMinTimeToLive, 1121cb0ef41Sopenharmony_ci ignoreCargoCult, 1131cb0ef41Sopenharmony_ci _fromObject, 1141cb0ef41Sopenharmony_ci } = {} 1151cb0ef41Sopenharmony_ci ) { 1161cb0ef41Sopenharmony_ci if (_fromObject) { 1171cb0ef41Sopenharmony_ci this._fromObject(_fromObject); 1181cb0ef41Sopenharmony_ci return; 1191cb0ef41Sopenharmony_ci } 1201cb0ef41Sopenharmony_ci 1211cb0ef41Sopenharmony_ci if (!res || !res.headers) { 1221cb0ef41Sopenharmony_ci throw Error('Response headers missing'); 1231cb0ef41Sopenharmony_ci } 1241cb0ef41Sopenharmony_ci this._assertRequestHasHeaders(req); 1251cb0ef41Sopenharmony_ci 1261cb0ef41Sopenharmony_ci this._responseTime = this.now(); 1271cb0ef41Sopenharmony_ci this._isShared = shared !== false; 1281cb0ef41Sopenharmony_ci this._cacheHeuristic = 1291cb0ef41Sopenharmony_ci undefined !== cacheHeuristic ? cacheHeuristic : 0.1; // 10% matches IE 1301cb0ef41Sopenharmony_ci this._immutableMinTtl = 1311cb0ef41Sopenharmony_ci undefined !== immutableMinTimeToLive 1321cb0ef41Sopenharmony_ci ? immutableMinTimeToLive 1331cb0ef41Sopenharmony_ci : 24 * 3600 * 1000; 1341cb0ef41Sopenharmony_ci 1351cb0ef41Sopenharmony_ci this._status = 'status' in res ? res.status : 200; 1361cb0ef41Sopenharmony_ci this._resHeaders = res.headers; 1371cb0ef41Sopenharmony_ci this._rescc = parseCacheControl(res.headers['cache-control']); 1381cb0ef41Sopenharmony_ci this._method = 'method' in req ? req.method : 'GET'; 1391cb0ef41Sopenharmony_ci this._url = req.url; 1401cb0ef41Sopenharmony_ci this._host = req.headers.host; 1411cb0ef41Sopenharmony_ci this._noAuthorization = !req.headers.authorization; 1421cb0ef41Sopenharmony_ci this._reqHeaders = res.headers.vary ? req.headers : null; // Don't keep all request headers if they won't be used 1431cb0ef41Sopenharmony_ci this._reqcc = parseCacheControl(req.headers['cache-control']); 1441cb0ef41Sopenharmony_ci 1451cb0ef41Sopenharmony_ci // Assume that if someone uses legacy, non-standard uncecessary options they don't understand caching, 1461cb0ef41Sopenharmony_ci // so there's no point stricly adhering to the blindly copy&pasted directives. 1471cb0ef41Sopenharmony_ci if ( 1481cb0ef41Sopenharmony_ci ignoreCargoCult && 1491cb0ef41Sopenharmony_ci 'pre-check' in this._rescc && 1501cb0ef41Sopenharmony_ci 'post-check' in this._rescc 1511cb0ef41Sopenharmony_ci ) { 1521cb0ef41Sopenharmony_ci delete this._rescc['pre-check']; 1531cb0ef41Sopenharmony_ci delete this._rescc['post-check']; 1541cb0ef41Sopenharmony_ci delete this._rescc['no-cache']; 1551cb0ef41Sopenharmony_ci delete this._rescc['no-store']; 1561cb0ef41Sopenharmony_ci delete this._rescc['must-revalidate']; 1571cb0ef41Sopenharmony_ci this._resHeaders = Object.assign({}, this._resHeaders, { 1581cb0ef41Sopenharmony_ci 'cache-control': formatCacheControl(this._rescc), 1591cb0ef41Sopenharmony_ci }); 1601cb0ef41Sopenharmony_ci delete this._resHeaders.expires; 1611cb0ef41Sopenharmony_ci delete this._resHeaders.pragma; 1621cb0ef41Sopenharmony_ci } 1631cb0ef41Sopenharmony_ci 1641cb0ef41Sopenharmony_ci // When the Cache-Control header field is not present in a request, caches MUST consider the no-cache request pragma-directive 1651cb0ef41Sopenharmony_ci // as having the same effect as if "Cache-Control: no-cache" were present (see Section 5.2.1). 1661cb0ef41Sopenharmony_ci if ( 1671cb0ef41Sopenharmony_ci res.headers['cache-control'] == null && 1681cb0ef41Sopenharmony_ci /no-cache/.test(res.headers.pragma) 1691cb0ef41Sopenharmony_ci ) { 1701cb0ef41Sopenharmony_ci this._rescc['no-cache'] = true; 1711cb0ef41Sopenharmony_ci } 1721cb0ef41Sopenharmony_ci } 1731cb0ef41Sopenharmony_ci 1741cb0ef41Sopenharmony_ci now() { 1751cb0ef41Sopenharmony_ci return Date.now(); 1761cb0ef41Sopenharmony_ci } 1771cb0ef41Sopenharmony_ci 1781cb0ef41Sopenharmony_ci storable() { 1791cb0ef41Sopenharmony_ci // The "no-store" request directive indicates that a cache MUST NOT store any part of either this request or any response to it. 1801cb0ef41Sopenharmony_ci return !!( 1811cb0ef41Sopenharmony_ci !this._reqcc['no-store'] && 1821cb0ef41Sopenharmony_ci // A cache MUST NOT store a response to any request, unless: 1831cb0ef41Sopenharmony_ci // The request method is understood by the cache and defined as being cacheable, and 1841cb0ef41Sopenharmony_ci ('GET' === this._method || 1851cb0ef41Sopenharmony_ci 'HEAD' === this._method || 1861cb0ef41Sopenharmony_ci ('POST' === this._method && this._hasExplicitExpiration())) && 1871cb0ef41Sopenharmony_ci // the response status code is understood by the cache, and 1881cb0ef41Sopenharmony_ci understoodStatuses.has(this._status) && 1891cb0ef41Sopenharmony_ci // the "no-store" cache directive does not appear in request or response header fields, and 1901cb0ef41Sopenharmony_ci !this._rescc['no-store'] && 1911cb0ef41Sopenharmony_ci // the "private" response directive does not appear in the response, if the cache is shared, and 1921cb0ef41Sopenharmony_ci (!this._isShared || !this._rescc.private) && 1931cb0ef41Sopenharmony_ci // the Authorization header field does not appear in the request, if the cache is shared, 1941cb0ef41Sopenharmony_ci (!this._isShared || 1951cb0ef41Sopenharmony_ci this._noAuthorization || 1961cb0ef41Sopenharmony_ci this._allowsStoringAuthenticated()) && 1971cb0ef41Sopenharmony_ci // the response either: 1981cb0ef41Sopenharmony_ci // contains an Expires header field, or 1991cb0ef41Sopenharmony_ci (this._resHeaders.expires || 2001cb0ef41Sopenharmony_ci // contains a max-age response directive, or 2011cb0ef41Sopenharmony_ci // contains a s-maxage response directive and the cache is shared, or 2021cb0ef41Sopenharmony_ci // contains a public response directive. 2031cb0ef41Sopenharmony_ci this._rescc['max-age'] || 2041cb0ef41Sopenharmony_ci (this._isShared && this._rescc['s-maxage']) || 2051cb0ef41Sopenharmony_ci this._rescc.public || 2061cb0ef41Sopenharmony_ci // has a status code that is defined as cacheable by default 2071cb0ef41Sopenharmony_ci statusCodeCacheableByDefault.has(this._status)) 2081cb0ef41Sopenharmony_ci ); 2091cb0ef41Sopenharmony_ci } 2101cb0ef41Sopenharmony_ci 2111cb0ef41Sopenharmony_ci _hasExplicitExpiration() { 2121cb0ef41Sopenharmony_ci // 4.2.1 Calculating Freshness Lifetime 2131cb0ef41Sopenharmony_ci return ( 2141cb0ef41Sopenharmony_ci (this._isShared && this._rescc['s-maxage']) || 2151cb0ef41Sopenharmony_ci this._rescc['max-age'] || 2161cb0ef41Sopenharmony_ci this._resHeaders.expires 2171cb0ef41Sopenharmony_ci ); 2181cb0ef41Sopenharmony_ci } 2191cb0ef41Sopenharmony_ci 2201cb0ef41Sopenharmony_ci _assertRequestHasHeaders(req) { 2211cb0ef41Sopenharmony_ci if (!req || !req.headers) { 2221cb0ef41Sopenharmony_ci throw Error('Request headers missing'); 2231cb0ef41Sopenharmony_ci } 2241cb0ef41Sopenharmony_ci } 2251cb0ef41Sopenharmony_ci 2261cb0ef41Sopenharmony_ci satisfiesWithoutRevalidation(req) { 2271cb0ef41Sopenharmony_ci this._assertRequestHasHeaders(req); 2281cb0ef41Sopenharmony_ci 2291cb0ef41Sopenharmony_ci // When presented with a request, a cache MUST NOT reuse a stored response, unless: 2301cb0ef41Sopenharmony_ci // the presented request does not contain the no-cache pragma (Section 5.4), nor the no-cache cache directive, 2311cb0ef41Sopenharmony_ci // unless the stored response is successfully validated (Section 4.3), and 2321cb0ef41Sopenharmony_ci const requestCC = parseCacheControl(req.headers['cache-control']); 2331cb0ef41Sopenharmony_ci if (requestCC['no-cache'] || /no-cache/.test(req.headers.pragma)) { 2341cb0ef41Sopenharmony_ci return false; 2351cb0ef41Sopenharmony_ci } 2361cb0ef41Sopenharmony_ci 2371cb0ef41Sopenharmony_ci if (requestCC['max-age'] && this.age() > requestCC['max-age']) { 2381cb0ef41Sopenharmony_ci return false; 2391cb0ef41Sopenharmony_ci } 2401cb0ef41Sopenharmony_ci 2411cb0ef41Sopenharmony_ci if ( 2421cb0ef41Sopenharmony_ci requestCC['min-fresh'] && 2431cb0ef41Sopenharmony_ci this.timeToLive() < 1000 * requestCC['min-fresh'] 2441cb0ef41Sopenharmony_ci ) { 2451cb0ef41Sopenharmony_ci return false; 2461cb0ef41Sopenharmony_ci } 2471cb0ef41Sopenharmony_ci 2481cb0ef41Sopenharmony_ci // the stored response is either: 2491cb0ef41Sopenharmony_ci // fresh, or allowed to be served stale 2501cb0ef41Sopenharmony_ci if (this.stale()) { 2511cb0ef41Sopenharmony_ci const allowsStale = 2521cb0ef41Sopenharmony_ci requestCC['max-stale'] && 2531cb0ef41Sopenharmony_ci !this._rescc['must-revalidate'] && 2541cb0ef41Sopenharmony_ci (true === requestCC['max-stale'] || 2551cb0ef41Sopenharmony_ci requestCC['max-stale'] > this.age() - this.maxAge()); 2561cb0ef41Sopenharmony_ci if (!allowsStale) { 2571cb0ef41Sopenharmony_ci return false; 2581cb0ef41Sopenharmony_ci } 2591cb0ef41Sopenharmony_ci } 2601cb0ef41Sopenharmony_ci 2611cb0ef41Sopenharmony_ci return this._requestMatches(req, false); 2621cb0ef41Sopenharmony_ci } 2631cb0ef41Sopenharmony_ci 2641cb0ef41Sopenharmony_ci _requestMatches(req, allowHeadMethod) { 2651cb0ef41Sopenharmony_ci // The presented effective request URI and that of the stored response match, and 2661cb0ef41Sopenharmony_ci return ( 2671cb0ef41Sopenharmony_ci (!this._url || this._url === req.url) && 2681cb0ef41Sopenharmony_ci this._host === req.headers.host && 2691cb0ef41Sopenharmony_ci // the request method associated with the stored response allows it to be used for the presented request, and 2701cb0ef41Sopenharmony_ci (!req.method || 2711cb0ef41Sopenharmony_ci this._method === req.method || 2721cb0ef41Sopenharmony_ci (allowHeadMethod && 'HEAD' === req.method)) && 2731cb0ef41Sopenharmony_ci // selecting header fields nominated by the stored response (if any) match those presented, and 2741cb0ef41Sopenharmony_ci this._varyMatches(req) 2751cb0ef41Sopenharmony_ci ); 2761cb0ef41Sopenharmony_ci } 2771cb0ef41Sopenharmony_ci 2781cb0ef41Sopenharmony_ci _allowsStoringAuthenticated() { 2791cb0ef41Sopenharmony_ci // following Cache-Control response directives (Section 5.2.2) have such an effect: must-revalidate, public, and s-maxage. 2801cb0ef41Sopenharmony_ci return ( 2811cb0ef41Sopenharmony_ci this._rescc['must-revalidate'] || 2821cb0ef41Sopenharmony_ci this._rescc.public || 2831cb0ef41Sopenharmony_ci this._rescc['s-maxage'] 2841cb0ef41Sopenharmony_ci ); 2851cb0ef41Sopenharmony_ci } 2861cb0ef41Sopenharmony_ci 2871cb0ef41Sopenharmony_ci _varyMatches(req) { 2881cb0ef41Sopenharmony_ci if (!this._resHeaders.vary) { 2891cb0ef41Sopenharmony_ci return true; 2901cb0ef41Sopenharmony_ci } 2911cb0ef41Sopenharmony_ci 2921cb0ef41Sopenharmony_ci // A Vary header field-value of "*" always fails to match 2931cb0ef41Sopenharmony_ci if (this._resHeaders.vary === '*') { 2941cb0ef41Sopenharmony_ci return false; 2951cb0ef41Sopenharmony_ci } 2961cb0ef41Sopenharmony_ci 2971cb0ef41Sopenharmony_ci const fields = this._resHeaders.vary 2981cb0ef41Sopenharmony_ci .trim() 2991cb0ef41Sopenharmony_ci .toLowerCase() 3001cb0ef41Sopenharmony_ci .split(/\s*,\s*/); 3011cb0ef41Sopenharmony_ci for (const name of fields) { 3021cb0ef41Sopenharmony_ci if (req.headers[name] !== this._reqHeaders[name]) return false; 3031cb0ef41Sopenharmony_ci } 3041cb0ef41Sopenharmony_ci return true; 3051cb0ef41Sopenharmony_ci } 3061cb0ef41Sopenharmony_ci 3071cb0ef41Sopenharmony_ci _copyWithoutHopByHopHeaders(inHeaders) { 3081cb0ef41Sopenharmony_ci const headers = {}; 3091cb0ef41Sopenharmony_ci for (const name in inHeaders) { 3101cb0ef41Sopenharmony_ci if (hopByHopHeaders[name]) continue; 3111cb0ef41Sopenharmony_ci headers[name] = inHeaders[name]; 3121cb0ef41Sopenharmony_ci } 3131cb0ef41Sopenharmony_ci // 9.1. Connection 3141cb0ef41Sopenharmony_ci if (inHeaders.connection) { 3151cb0ef41Sopenharmony_ci const tokens = inHeaders.connection.trim().split(/\s*,\s*/); 3161cb0ef41Sopenharmony_ci for (const name of tokens) { 3171cb0ef41Sopenharmony_ci delete headers[name]; 3181cb0ef41Sopenharmony_ci } 3191cb0ef41Sopenharmony_ci } 3201cb0ef41Sopenharmony_ci if (headers.warning) { 3211cb0ef41Sopenharmony_ci const warnings = headers.warning.split(/,/).filter(warning => { 3221cb0ef41Sopenharmony_ci return !/^\s*1[0-9][0-9]/.test(warning); 3231cb0ef41Sopenharmony_ci }); 3241cb0ef41Sopenharmony_ci if (!warnings.length) { 3251cb0ef41Sopenharmony_ci delete headers.warning; 3261cb0ef41Sopenharmony_ci } else { 3271cb0ef41Sopenharmony_ci headers.warning = warnings.join(',').trim(); 3281cb0ef41Sopenharmony_ci } 3291cb0ef41Sopenharmony_ci } 3301cb0ef41Sopenharmony_ci return headers; 3311cb0ef41Sopenharmony_ci } 3321cb0ef41Sopenharmony_ci 3331cb0ef41Sopenharmony_ci responseHeaders() { 3341cb0ef41Sopenharmony_ci const headers = this._copyWithoutHopByHopHeaders(this._resHeaders); 3351cb0ef41Sopenharmony_ci const age = this.age(); 3361cb0ef41Sopenharmony_ci 3371cb0ef41Sopenharmony_ci // A cache SHOULD generate 113 warning if it heuristically chose a freshness 3381cb0ef41Sopenharmony_ci // lifetime greater than 24 hours and the response's age is greater than 24 hours. 3391cb0ef41Sopenharmony_ci if ( 3401cb0ef41Sopenharmony_ci age > 3600 * 24 && 3411cb0ef41Sopenharmony_ci !this._hasExplicitExpiration() && 3421cb0ef41Sopenharmony_ci this.maxAge() > 3600 * 24 3431cb0ef41Sopenharmony_ci ) { 3441cb0ef41Sopenharmony_ci headers.warning = 3451cb0ef41Sopenharmony_ci (headers.warning ? `${headers.warning}, ` : '') + 3461cb0ef41Sopenharmony_ci '113 - "rfc7234 5.5.4"'; 3471cb0ef41Sopenharmony_ci } 3481cb0ef41Sopenharmony_ci headers.age = `${Math.round(age)}`; 3491cb0ef41Sopenharmony_ci headers.date = new Date(this.now()).toUTCString(); 3501cb0ef41Sopenharmony_ci return headers; 3511cb0ef41Sopenharmony_ci } 3521cb0ef41Sopenharmony_ci 3531cb0ef41Sopenharmony_ci /** 3541cb0ef41Sopenharmony_ci * Value of the Date response header or current time if Date was invalid 3551cb0ef41Sopenharmony_ci * @return timestamp 3561cb0ef41Sopenharmony_ci */ 3571cb0ef41Sopenharmony_ci date() { 3581cb0ef41Sopenharmony_ci const serverDate = Date.parse(this._resHeaders.date); 3591cb0ef41Sopenharmony_ci if (isFinite(serverDate)) { 3601cb0ef41Sopenharmony_ci return serverDate; 3611cb0ef41Sopenharmony_ci } 3621cb0ef41Sopenharmony_ci return this._responseTime; 3631cb0ef41Sopenharmony_ci } 3641cb0ef41Sopenharmony_ci 3651cb0ef41Sopenharmony_ci /** 3661cb0ef41Sopenharmony_ci * Value of the Age header, in seconds, updated for the current time. 3671cb0ef41Sopenharmony_ci * May be fractional. 3681cb0ef41Sopenharmony_ci * 3691cb0ef41Sopenharmony_ci * @return Number 3701cb0ef41Sopenharmony_ci */ 3711cb0ef41Sopenharmony_ci age() { 3721cb0ef41Sopenharmony_ci let age = this._ageValue(); 3731cb0ef41Sopenharmony_ci 3741cb0ef41Sopenharmony_ci const residentTime = (this.now() - this._responseTime) / 1000; 3751cb0ef41Sopenharmony_ci return age + residentTime; 3761cb0ef41Sopenharmony_ci } 3771cb0ef41Sopenharmony_ci 3781cb0ef41Sopenharmony_ci _ageValue() { 3791cb0ef41Sopenharmony_ci return toNumberOrZero(this._resHeaders.age); 3801cb0ef41Sopenharmony_ci } 3811cb0ef41Sopenharmony_ci 3821cb0ef41Sopenharmony_ci /** 3831cb0ef41Sopenharmony_ci * Value of applicable max-age (or heuristic equivalent) in seconds. This counts since response's `Date`. 3841cb0ef41Sopenharmony_ci * 3851cb0ef41Sopenharmony_ci * For an up-to-date value, see `timeToLive()`. 3861cb0ef41Sopenharmony_ci * 3871cb0ef41Sopenharmony_ci * @return Number 3881cb0ef41Sopenharmony_ci */ 3891cb0ef41Sopenharmony_ci maxAge() { 3901cb0ef41Sopenharmony_ci if (!this.storable() || this._rescc['no-cache']) { 3911cb0ef41Sopenharmony_ci return 0; 3921cb0ef41Sopenharmony_ci } 3931cb0ef41Sopenharmony_ci 3941cb0ef41Sopenharmony_ci // Shared responses with cookies are cacheable according to the RFC, but IMHO it'd be unwise to do so by default 3951cb0ef41Sopenharmony_ci // so this implementation requires explicit opt-in via public header 3961cb0ef41Sopenharmony_ci if ( 3971cb0ef41Sopenharmony_ci this._isShared && 3981cb0ef41Sopenharmony_ci (this._resHeaders['set-cookie'] && 3991cb0ef41Sopenharmony_ci !this._rescc.public && 4001cb0ef41Sopenharmony_ci !this._rescc.immutable) 4011cb0ef41Sopenharmony_ci ) { 4021cb0ef41Sopenharmony_ci return 0; 4031cb0ef41Sopenharmony_ci } 4041cb0ef41Sopenharmony_ci 4051cb0ef41Sopenharmony_ci if (this._resHeaders.vary === '*') { 4061cb0ef41Sopenharmony_ci return 0; 4071cb0ef41Sopenharmony_ci } 4081cb0ef41Sopenharmony_ci 4091cb0ef41Sopenharmony_ci if (this._isShared) { 4101cb0ef41Sopenharmony_ci if (this._rescc['proxy-revalidate']) { 4111cb0ef41Sopenharmony_ci return 0; 4121cb0ef41Sopenharmony_ci } 4131cb0ef41Sopenharmony_ci // if a response includes the s-maxage directive, a shared cache recipient MUST ignore the Expires field. 4141cb0ef41Sopenharmony_ci if (this._rescc['s-maxage']) { 4151cb0ef41Sopenharmony_ci return toNumberOrZero(this._rescc['s-maxage']); 4161cb0ef41Sopenharmony_ci } 4171cb0ef41Sopenharmony_ci } 4181cb0ef41Sopenharmony_ci 4191cb0ef41Sopenharmony_ci // If a response includes a Cache-Control field with the max-age directive, a recipient MUST ignore the Expires field. 4201cb0ef41Sopenharmony_ci if (this._rescc['max-age']) { 4211cb0ef41Sopenharmony_ci return toNumberOrZero(this._rescc['max-age']); 4221cb0ef41Sopenharmony_ci } 4231cb0ef41Sopenharmony_ci 4241cb0ef41Sopenharmony_ci const defaultMinTtl = this._rescc.immutable ? this._immutableMinTtl : 0; 4251cb0ef41Sopenharmony_ci 4261cb0ef41Sopenharmony_ci const serverDate = this.date(); 4271cb0ef41Sopenharmony_ci if (this._resHeaders.expires) { 4281cb0ef41Sopenharmony_ci const expires = Date.parse(this._resHeaders.expires); 4291cb0ef41Sopenharmony_ci // A cache recipient MUST interpret invalid date formats, especially the value "0", as representing a time in the past (i.e., "already expired"). 4301cb0ef41Sopenharmony_ci if (Number.isNaN(expires) || expires < serverDate) { 4311cb0ef41Sopenharmony_ci return 0; 4321cb0ef41Sopenharmony_ci } 4331cb0ef41Sopenharmony_ci return Math.max(defaultMinTtl, (expires - serverDate) / 1000); 4341cb0ef41Sopenharmony_ci } 4351cb0ef41Sopenharmony_ci 4361cb0ef41Sopenharmony_ci if (this._resHeaders['last-modified']) { 4371cb0ef41Sopenharmony_ci const lastModified = Date.parse(this._resHeaders['last-modified']); 4381cb0ef41Sopenharmony_ci if (isFinite(lastModified) && serverDate > lastModified) { 4391cb0ef41Sopenharmony_ci return Math.max( 4401cb0ef41Sopenharmony_ci defaultMinTtl, 4411cb0ef41Sopenharmony_ci ((serverDate - lastModified) / 1000) * this._cacheHeuristic 4421cb0ef41Sopenharmony_ci ); 4431cb0ef41Sopenharmony_ci } 4441cb0ef41Sopenharmony_ci } 4451cb0ef41Sopenharmony_ci 4461cb0ef41Sopenharmony_ci return defaultMinTtl; 4471cb0ef41Sopenharmony_ci } 4481cb0ef41Sopenharmony_ci 4491cb0ef41Sopenharmony_ci timeToLive() { 4501cb0ef41Sopenharmony_ci const age = this.maxAge() - this.age(); 4511cb0ef41Sopenharmony_ci const staleIfErrorAge = age + toNumberOrZero(this._rescc['stale-if-error']); 4521cb0ef41Sopenharmony_ci const staleWhileRevalidateAge = age + toNumberOrZero(this._rescc['stale-while-revalidate']); 4531cb0ef41Sopenharmony_ci return Math.max(0, age, staleIfErrorAge, staleWhileRevalidateAge) * 1000; 4541cb0ef41Sopenharmony_ci } 4551cb0ef41Sopenharmony_ci 4561cb0ef41Sopenharmony_ci stale() { 4571cb0ef41Sopenharmony_ci return this.maxAge() <= this.age(); 4581cb0ef41Sopenharmony_ci } 4591cb0ef41Sopenharmony_ci 4601cb0ef41Sopenharmony_ci _useStaleIfError() { 4611cb0ef41Sopenharmony_ci return this.maxAge() + toNumberOrZero(this._rescc['stale-if-error']) > this.age(); 4621cb0ef41Sopenharmony_ci } 4631cb0ef41Sopenharmony_ci 4641cb0ef41Sopenharmony_ci useStaleWhileRevalidate() { 4651cb0ef41Sopenharmony_ci return this.maxAge() + toNumberOrZero(this._rescc['stale-while-revalidate']) > this.age(); 4661cb0ef41Sopenharmony_ci } 4671cb0ef41Sopenharmony_ci 4681cb0ef41Sopenharmony_ci static fromObject(obj) { 4691cb0ef41Sopenharmony_ci return new this(undefined, undefined, { _fromObject: obj }); 4701cb0ef41Sopenharmony_ci } 4711cb0ef41Sopenharmony_ci 4721cb0ef41Sopenharmony_ci _fromObject(obj) { 4731cb0ef41Sopenharmony_ci if (this._responseTime) throw Error('Reinitialized'); 4741cb0ef41Sopenharmony_ci if (!obj || obj.v !== 1) throw Error('Invalid serialization'); 4751cb0ef41Sopenharmony_ci 4761cb0ef41Sopenharmony_ci this._responseTime = obj.t; 4771cb0ef41Sopenharmony_ci this._isShared = obj.sh; 4781cb0ef41Sopenharmony_ci this._cacheHeuristic = obj.ch; 4791cb0ef41Sopenharmony_ci this._immutableMinTtl = 4801cb0ef41Sopenharmony_ci obj.imm !== undefined ? obj.imm : 24 * 3600 * 1000; 4811cb0ef41Sopenharmony_ci this._status = obj.st; 4821cb0ef41Sopenharmony_ci this._resHeaders = obj.resh; 4831cb0ef41Sopenharmony_ci this._rescc = obj.rescc; 4841cb0ef41Sopenharmony_ci this._method = obj.m; 4851cb0ef41Sopenharmony_ci this._url = obj.u; 4861cb0ef41Sopenharmony_ci this._host = obj.h; 4871cb0ef41Sopenharmony_ci this._noAuthorization = obj.a; 4881cb0ef41Sopenharmony_ci this._reqHeaders = obj.reqh; 4891cb0ef41Sopenharmony_ci this._reqcc = obj.reqcc; 4901cb0ef41Sopenharmony_ci } 4911cb0ef41Sopenharmony_ci 4921cb0ef41Sopenharmony_ci toObject() { 4931cb0ef41Sopenharmony_ci return { 4941cb0ef41Sopenharmony_ci v: 1, 4951cb0ef41Sopenharmony_ci t: this._responseTime, 4961cb0ef41Sopenharmony_ci sh: this._isShared, 4971cb0ef41Sopenharmony_ci ch: this._cacheHeuristic, 4981cb0ef41Sopenharmony_ci imm: this._immutableMinTtl, 4991cb0ef41Sopenharmony_ci st: this._status, 5001cb0ef41Sopenharmony_ci resh: this._resHeaders, 5011cb0ef41Sopenharmony_ci rescc: this._rescc, 5021cb0ef41Sopenharmony_ci m: this._method, 5031cb0ef41Sopenharmony_ci u: this._url, 5041cb0ef41Sopenharmony_ci h: this._host, 5051cb0ef41Sopenharmony_ci a: this._noAuthorization, 5061cb0ef41Sopenharmony_ci reqh: this._reqHeaders, 5071cb0ef41Sopenharmony_ci reqcc: this._reqcc, 5081cb0ef41Sopenharmony_ci }; 5091cb0ef41Sopenharmony_ci } 5101cb0ef41Sopenharmony_ci 5111cb0ef41Sopenharmony_ci /** 5121cb0ef41Sopenharmony_ci * Headers for sending to the origin server to revalidate stale response. 5131cb0ef41Sopenharmony_ci * Allows server to return 304 to allow reuse of the previous response. 5141cb0ef41Sopenharmony_ci * 5151cb0ef41Sopenharmony_ci * Hop by hop headers are always stripped. 5161cb0ef41Sopenharmony_ci * Revalidation headers may be added or removed, depending on request. 5171cb0ef41Sopenharmony_ci */ 5181cb0ef41Sopenharmony_ci revalidationHeaders(incomingReq) { 5191cb0ef41Sopenharmony_ci this._assertRequestHasHeaders(incomingReq); 5201cb0ef41Sopenharmony_ci const headers = this._copyWithoutHopByHopHeaders(incomingReq.headers); 5211cb0ef41Sopenharmony_ci 5221cb0ef41Sopenharmony_ci // This implementation does not understand range requests 5231cb0ef41Sopenharmony_ci delete headers['if-range']; 5241cb0ef41Sopenharmony_ci 5251cb0ef41Sopenharmony_ci if (!this._requestMatches(incomingReq, true) || !this.storable()) { 5261cb0ef41Sopenharmony_ci // revalidation allowed via HEAD 5271cb0ef41Sopenharmony_ci // not for the same resource, or wasn't allowed to be cached anyway 5281cb0ef41Sopenharmony_ci delete headers['if-none-match']; 5291cb0ef41Sopenharmony_ci delete headers['if-modified-since']; 5301cb0ef41Sopenharmony_ci return headers; 5311cb0ef41Sopenharmony_ci } 5321cb0ef41Sopenharmony_ci 5331cb0ef41Sopenharmony_ci /* MUST send that entity-tag in any cache validation request (using If-Match or If-None-Match) if an entity-tag has been provided by the origin server. */ 5341cb0ef41Sopenharmony_ci if (this._resHeaders.etag) { 5351cb0ef41Sopenharmony_ci headers['if-none-match'] = headers['if-none-match'] 5361cb0ef41Sopenharmony_ci ? `${headers['if-none-match']}, ${this._resHeaders.etag}` 5371cb0ef41Sopenharmony_ci : this._resHeaders.etag; 5381cb0ef41Sopenharmony_ci } 5391cb0ef41Sopenharmony_ci 5401cb0ef41Sopenharmony_ci // Clients MAY issue simple (non-subrange) GET requests with either weak validators or strong validators. Clients MUST NOT use weak validators in other forms of request. 5411cb0ef41Sopenharmony_ci const forbidsWeakValidators = 5421cb0ef41Sopenharmony_ci headers['accept-ranges'] || 5431cb0ef41Sopenharmony_ci headers['if-match'] || 5441cb0ef41Sopenharmony_ci headers['if-unmodified-since'] || 5451cb0ef41Sopenharmony_ci (this._method && this._method != 'GET'); 5461cb0ef41Sopenharmony_ci 5471cb0ef41Sopenharmony_ci /* SHOULD send the Last-Modified value in non-subrange cache validation requests (using If-Modified-Since) if only a Last-Modified value has been provided by the origin server. 5481cb0ef41Sopenharmony_ci Note: This implementation does not understand partial responses (206) */ 5491cb0ef41Sopenharmony_ci if (forbidsWeakValidators) { 5501cb0ef41Sopenharmony_ci delete headers['if-modified-since']; 5511cb0ef41Sopenharmony_ci 5521cb0ef41Sopenharmony_ci if (headers['if-none-match']) { 5531cb0ef41Sopenharmony_ci const etags = headers['if-none-match'] 5541cb0ef41Sopenharmony_ci .split(/,/) 5551cb0ef41Sopenharmony_ci .filter(etag => { 5561cb0ef41Sopenharmony_ci return !/^\s*W\//.test(etag); 5571cb0ef41Sopenharmony_ci }); 5581cb0ef41Sopenharmony_ci if (!etags.length) { 5591cb0ef41Sopenharmony_ci delete headers['if-none-match']; 5601cb0ef41Sopenharmony_ci } else { 5611cb0ef41Sopenharmony_ci headers['if-none-match'] = etags.join(',').trim(); 5621cb0ef41Sopenharmony_ci } 5631cb0ef41Sopenharmony_ci } 5641cb0ef41Sopenharmony_ci } else if ( 5651cb0ef41Sopenharmony_ci this._resHeaders['last-modified'] && 5661cb0ef41Sopenharmony_ci !headers['if-modified-since'] 5671cb0ef41Sopenharmony_ci ) { 5681cb0ef41Sopenharmony_ci headers['if-modified-since'] = this._resHeaders['last-modified']; 5691cb0ef41Sopenharmony_ci } 5701cb0ef41Sopenharmony_ci 5711cb0ef41Sopenharmony_ci return headers; 5721cb0ef41Sopenharmony_ci } 5731cb0ef41Sopenharmony_ci 5741cb0ef41Sopenharmony_ci /** 5751cb0ef41Sopenharmony_ci * Creates new CachePolicy with information combined from the previews response, 5761cb0ef41Sopenharmony_ci * and the new revalidation response. 5771cb0ef41Sopenharmony_ci * 5781cb0ef41Sopenharmony_ci * Returns {policy, modified} where modified is a boolean indicating 5791cb0ef41Sopenharmony_ci * whether the response body has been modified, and old cached body can't be used. 5801cb0ef41Sopenharmony_ci * 5811cb0ef41Sopenharmony_ci * @return {Object} {policy: CachePolicy, modified: Boolean} 5821cb0ef41Sopenharmony_ci */ 5831cb0ef41Sopenharmony_ci revalidatedPolicy(request, response) { 5841cb0ef41Sopenharmony_ci this._assertRequestHasHeaders(request); 5851cb0ef41Sopenharmony_ci if(this._useStaleIfError() && isErrorResponse(response)) { // I consider the revalidation request unsuccessful 5861cb0ef41Sopenharmony_ci return { 5871cb0ef41Sopenharmony_ci modified: false, 5881cb0ef41Sopenharmony_ci matches: false, 5891cb0ef41Sopenharmony_ci policy: this, 5901cb0ef41Sopenharmony_ci }; 5911cb0ef41Sopenharmony_ci } 5921cb0ef41Sopenharmony_ci if (!response || !response.headers) { 5931cb0ef41Sopenharmony_ci throw Error('Response headers missing'); 5941cb0ef41Sopenharmony_ci } 5951cb0ef41Sopenharmony_ci 5961cb0ef41Sopenharmony_ci // These aren't going to be supported exactly, since one CachePolicy object 5971cb0ef41Sopenharmony_ci // doesn't know about all the other cached objects. 5981cb0ef41Sopenharmony_ci let matches = false; 5991cb0ef41Sopenharmony_ci if (response.status !== undefined && response.status != 304) { 6001cb0ef41Sopenharmony_ci matches = false; 6011cb0ef41Sopenharmony_ci } else if ( 6021cb0ef41Sopenharmony_ci response.headers.etag && 6031cb0ef41Sopenharmony_ci !/^\s*W\//.test(response.headers.etag) 6041cb0ef41Sopenharmony_ci ) { 6051cb0ef41Sopenharmony_ci // "All of the stored responses with the same strong validator are selected. 6061cb0ef41Sopenharmony_ci // If none of the stored responses contain the same strong validator, 6071cb0ef41Sopenharmony_ci // then the cache MUST NOT use the new response to update any stored responses." 6081cb0ef41Sopenharmony_ci matches = 6091cb0ef41Sopenharmony_ci this._resHeaders.etag && 6101cb0ef41Sopenharmony_ci this._resHeaders.etag.replace(/^\s*W\//, '') === 6111cb0ef41Sopenharmony_ci response.headers.etag; 6121cb0ef41Sopenharmony_ci } else if (this._resHeaders.etag && response.headers.etag) { 6131cb0ef41Sopenharmony_ci // "If the new response contains a weak validator and that validator corresponds 6141cb0ef41Sopenharmony_ci // to one of the cache's stored responses, 6151cb0ef41Sopenharmony_ci // then the most recent of those matching stored responses is selected for update." 6161cb0ef41Sopenharmony_ci matches = 6171cb0ef41Sopenharmony_ci this._resHeaders.etag.replace(/^\s*W\//, '') === 6181cb0ef41Sopenharmony_ci response.headers.etag.replace(/^\s*W\//, ''); 6191cb0ef41Sopenharmony_ci } else if (this._resHeaders['last-modified']) { 6201cb0ef41Sopenharmony_ci matches = 6211cb0ef41Sopenharmony_ci this._resHeaders['last-modified'] === 6221cb0ef41Sopenharmony_ci response.headers['last-modified']; 6231cb0ef41Sopenharmony_ci } else { 6241cb0ef41Sopenharmony_ci // If the new response does not include any form of validator (such as in the case where 6251cb0ef41Sopenharmony_ci // a client generates an If-Modified-Since request from a source other than the Last-Modified 6261cb0ef41Sopenharmony_ci // response header field), and there is only one stored response, and that stored response also 6271cb0ef41Sopenharmony_ci // lacks a validator, then that stored response is selected for update. 6281cb0ef41Sopenharmony_ci if ( 6291cb0ef41Sopenharmony_ci !this._resHeaders.etag && 6301cb0ef41Sopenharmony_ci !this._resHeaders['last-modified'] && 6311cb0ef41Sopenharmony_ci !response.headers.etag && 6321cb0ef41Sopenharmony_ci !response.headers['last-modified'] 6331cb0ef41Sopenharmony_ci ) { 6341cb0ef41Sopenharmony_ci matches = true; 6351cb0ef41Sopenharmony_ci } 6361cb0ef41Sopenharmony_ci } 6371cb0ef41Sopenharmony_ci 6381cb0ef41Sopenharmony_ci if (!matches) { 6391cb0ef41Sopenharmony_ci return { 6401cb0ef41Sopenharmony_ci policy: new this.constructor(request, response), 6411cb0ef41Sopenharmony_ci // Client receiving 304 without body, even if it's invalid/mismatched has no option 6421cb0ef41Sopenharmony_ci // but to reuse a cached body. We don't have a good way to tell clients to do 6431cb0ef41Sopenharmony_ci // error recovery in such case. 6441cb0ef41Sopenharmony_ci modified: response.status != 304, 6451cb0ef41Sopenharmony_ci matches: false, 6461cb0ef41Sopenharmony_ci }; 6471cb0ef41Sopenharmony_ci } 6481cb0ef41Sopenharmony_ci 6491cb0ef41Sopenharmony_ci // use other header fields provided in the 304 (Not Modified) response to replace all instances 6501cb0ef41Sopenharmony_ci // of the corresponding header fields in the stored response. 6511cb0ef41Sopenharmony_ci const headers = {}; 6521cb0ef41Sopenharmony_ci for (const k in this._resHeaders) { 6531cb0ef41Sopenharmony_ci headers[k] = 6541cb0ef41Sopenharmony_ci k in response.headers && !excludedFromRevalidationUpdate[k] 6551cb0ef41Sopenharmony_ci ? response.headers[k] 6561cb0ef41Sopenharmony_ci : this._resHeaders[k]; 6571cb0ef41Sopenharmony_ci } 6581cb0ef41Sopenharmony_ci 6591cb0ef41Sopenharmony_ci const newResponse = Object.assign({}, response, { 6601cb0ef41Sopenharmony_ci status: this._status, 6611cb0ef41Sopenharmony_ci method: this._method, 6621cb0ef41Sopenharmony_ci headers, 6631cb0ef41Sopenharmony_ci }); 6641cb0ef41Sopenharmony_ci return { 6651cb0ef41Sopenharmony_ci policy: new this.constructor(request, newResponse, { 6661cb0ef41Sopenharmony_ci shared: this._isShared, 6671cb0ef41Sopenharmony_ci cacheHeuristic: this._cacheHeuristic, 6681cb0ef41Sopenharmony_ci immutableMinTimeToLive: this._immutableMinTtl, 6691cb0ef41Sopenharmony_ci }), 6701cb0ef41Sopenharmony_ci modified: false, 6711cb0ef41Sopenharmony_ci matches: true, 6721cb0ef41Sopenharmony_ci }; 6731cb0ef41Sopenharmony_ci } 6741cb0ef41Sopenharmony_ci}; 675