Document no longer needs to store a SessionID
[WebKit-https.git] / Source / WebCore / platform / network / CacheValidation.cpp
1 /*
2  * Copyright (C) 2014-2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CacheValidation.h"
28
29 #include "CookieJar.h"
30 #include "HTTPHeaderMap.h"
31 #include "NetworkStorageSession.h"
32 #include "ResourceRequest.h"
33 #include "ResourceResponse.h"
34 #include "SameSiteInfo.h"
35 #include <wtf/Optional.h>
36 #include <wtf/Vector.h>
37 #include <wtf/text/StringView.h>
38 #include <wtf/text/WTFString.h>
39
40 namespace WebCore {
41
42 // These response headers are not copied from a revalidated response to the
43 // cached response headers. For compatibility, this list is based on Chromium's
44 // net/http/http_response_headers.cc.
45 static const char* const headersToIgnoreAfterRevalidation[] = {
46     "allow",
47     "connection",
48     "etag",
49     "keep-alive",
50     "last-modified",
51     "proxy-authenticate",
52     "proxy-connection",
53     "trailer",
54     "transfer-encoding",
55     "upgrade",
56     "www-authenticate",
57     "x-frame-options",
58     "x-xss-protection",
59 };
60
61 // Some header prefixes mean "Don't copy this header from a 304 response.".
62 // Rather than listing all the relevant headers, we can consolidate them into
63 // this list, also grabbed from Chromium's net/http/http_response_headers.cc.
64 static const char* const headerPrefixesToIgnoreAfterRevalidation[] = {
65     "content-",
66     "x-content-",
67     "x-webkit-"
68 };
69
70 static inline bool shouldUpdateHeaderAfterRevalidation(const String& header)
71 {
72     for (auto& headerToIgnore : headersToIgnoreAfterRevalidation) {
73         if (equalIgnoringASCIICase(header, headerToIgnore))
74             return false;
75     }
76     for (auto& prefixToIgnore : headerPrefixesToIgnoreAfterRevalidation) {
77         // FIXME: Would be more efficient if we added an overload of
78         // startsWithIgnoringASCIICase that takes a const char*.
79         if (header.startsWithIgnoringASCIICase(prefixToIgnore))
80             return false;
81     }
82     return true;
83 }
84
85 void updateResponseHeadersAfterRevalidation(ResourceResponse& response, const ResourceResponse& validatingResponse)
86 {
87     // Freshening stored response upon validation:
88     // http://tools.ietf.org/html/rfc7234#section-4.3.4
89     for (const auto& header : validatingResponse.httpHeaderFields()) {
90         // Entity headers should not be sent by servers when generating a 304
91         // response; misconfigured servers send them anyway. We shouldn't allow
92         // such headers to update the original request. We'll base this on the
93         // list defined by RFC2616 7.1, with a few additions for extension headers
94         // we care about.
95         if (!shouldUpdateHeaderAfterRevalidation(header.key))
96             continue;
97         response.setHTTPHeaderField(header.key, header.value);
98     }
99 }
100
101 Seconds computeCurrentAge(const ResourceResponse& response, WallTime responseTime)
102 {
103     // Age calculation:
104     // http://tools.ietf.org/html/rfc7234#section-4.2.3
105     // No compensation for latency as that is not terribly important in practice.
106     auto dateValue = response.date();
107     auto apparentAge = dateValue ? std::max(0_us, responseTime - *dateValue) : 0_us;
108     auto ageValue = response.age().valueOr(0_us);
109     auto correctedInitialAge = std::max(apparentAge, ageValue);
110     auto residentTime = WallTime::now() - responseTime;
111     return correctedInitialAge + residentTime;
112 }
113
114 Seconds computeFreshnessLifetimeForHTTPFamily(const ResourceResponse& response, WallTime responseTime)
115 {
116     if (!response.url().protocolIsInHTTPFamily())
117         return 0_us;
118
119     // Freshness Lifetime:
120     // http://tools.ietf.org/html/rfc7234#section-4.2.1
121     auto maxAge = response.cacheControlMaxAge();
122     if (maxAge)
123         return *maxAge;
124
125     auto date = response.date();
126     auto effectiveDate = date.valueOr(responseTime);
127     if (auto expires = response.expires())
128         return *expires - effectiveDate;
129
130     // Implicit lifetime.
131     switch (response.httpStatusCode()) {
132     case 301: // Moved Permanently
133     case 410: // Gone
134         // These are semantically permanent and so get long implicit lifetime.
135         return 24_h * 365;
136     default:
137         // Heuristic Freshness:
138         // http://tools.ietf.org/html/rfc7234#section-4.2.2
139         if (auto lastModified = response.lastModified())
140             return (effectiveDate - *lastModified) * 0.1;
141         return 0_us;
142     }
143 }
144
145 void updateRedirectChainStatus(RedirectChainCacheStatus& redirectChainCacheStatus, const ResourceResponse& response)
146 {
147     if (redirectChainCacheStatus.status == RedirectChainCacheStatus::Status::NotCachedRedirection)
148         return;
149     if (response.cacheControlContainsNoStore() || response.cacheControlContainsNoCache() || response.cacheControlContainsMustRevalidate()) {
150         redirectChainCacheStatus.status = RedirectChainCacheStatus::Status::NotCachedRedirection;
151         return;
152     }
153
154     redirectChainCacheStatus.status = RedirectChainCacheStatus::Status::CachedRedirection;
155     auto responseTimestamp = WallTime::now();
156     // Store the nearest end of cache validity date
157     auto endOfValidity = responseTimestamp + computeFreshnessLifetimeForHTTPFamily(response, responseTimestamp) - computeCurrentAge(response, responseTimestamp);
158     redirectChainCacheStatus.endOfValidity = std::min(redirectChainCacheStatus.endOfValidity, endOfValidity);
159 }
160
161 bool redirectChainAllowsReuse(RedirectChainCacheStatus redirectChainCacheStatus, ReuseExpiredRedirectionOrNot reuseExpiredRedirection)
162 {
163     switch (redirectChainCacheStatus.status) {
164     case RedirectChainCacheStatus::Status::NoRedirection:
165         return true;
166     case RedirectChainCacheStatus::Status::NotCachedRedirection:
167         return false;
168     case RedirectChainCacheStatus::Status::CachedRedirection:
169         return reuseExpiredRedirection || WallTime::now() <= redirectChainCacheStatus.endOfValidity;
170     }
171     ASSERT_NOT_REACHED();
172     return false;
173 }
174
175 inline bool isCacheHeaderSeparator(UChar c)
176 {
177     // http://tools.ietf.org/html/rfc7230#section-3.2.6
178     switch (c) {
179     case '(':
180     case ')':
181     case '<':
182     case '>':
183     case '@':
184     case ',':
185     case ';':
186     case ':':
187     case '\\':
188     case '"':
189     case '/':
190     case '[':
191     case ']':
192     case '?':
193     case '=':
194     case '{':
195     case '}':
196     case ' ':
197     case '\t':
198         return true;
199     default:
200         return false;
201     }
202 }
203
204 inline bool isControlCharacterOrSpace(UChar character)
205 {
206     return character <= ' ' || character == 127;
207 }
208
209 inline StringView trimToNextSeparator(StringView string)
210 {
211     return string.substring(0, string.find(isCacheHeaderSeparator));
212 }
213
214 static Vector<std::pair<String, String>> parseCacheHeader(const String& header)
215 {
216     Vector<std::pair<String, String>> result;
217
218     String safeHeaderString = header.removeCharacters(isControlCharacterOrSpace);
219     StringView safeHeader = safeHeaderString;
220     unsigned max = safeHeader.length();
221     unsigned pos = 0;
222     while (pos < max) {
223         size_t nextCommaPosition = safeHeader.find(',', pos);
224         size_t nextEqualSignPosition = safeHeader.find('=', pos);
225         if (nextEqualSignPosition == notFound && nextCommaPosition == notFound) {
226             // Add last directive to map with empty string as value
227             result.append({ trimToNextSeparator(safeHeader.substring(pos, max - pos)).toString(), emptyString() });
228             return result;
229         }
230         if (nextCommaPosition != notFound && (nextCommaPosition < nextEqualSignPosition || nextEqualSignPosition == notFound)) {
231             // Add directive to map with empty string as value
232             result.append({ trimToNextSeparator(safeHeader.substring(pos, nextCommaPosition - pos)).toString(), emptyString() });
233             pos += nextCommaPosition - pos + 1;
234             continue;
235         }
236         // Get directive name, parse right hand side of equal sign, then add to map
237         String directive = trimToNextSeparator(safeHeader.substring(pos, nextEqualSignPosition - pos)).toString();
238         pos += nextEqualSignPosition - pos + 1;
239
240         StringView value = safeHeader.substring(pos, max - pos);
241         if (value[0] == '"') {
242             // The value is a quoted string
243             size_t nextDoubleQuotePosition = value.find('"', 1);
244             if (nextDoubleQuotePosition == notFound) {
245                 // Parse error; just use the rest as the value
246                 result.append({ directive, trimToNextSeparator(value.substring(1)).toString() });
247                 return result;
248             }
249             // Store the value as a quoted string without quotes
250             result.append({ directive, value.substring(1, nextDoubleQuotePosition - 1).toString() });
251             pos += (safeHeader.find('"', pos) - pos) + nextDoubleQuotePosition + 1;
252             // Move past next comma, if there is one
253             size_t nextCommaPosition2 = safeHeader.find(',', pos);
254             if (nextCommaPosition2 == notFound)
255                 return result; // Parse error if there is anything left with no comma
256             pos += nextCommaPosition2 - pos + 1;
257             continue;
258         }
259         // The value is a token until the next comma
260         size_t nextCommaPosition2 = value.find(',');
261         if (nextCommaPosition2 == notFound) {
262             // The rest is the value; no change to value needed
263             result.append({ directive, trimToNextSeparator(value).toString() });
264             return result;
265         }
266         // The value is delimited by the next comma
267         result.append({ directive, trimToNextSeparator(value.substring(0, nextCommaPosition2)).toString() });
268         pos += (safeHeader.find(',', pos) - pos) + 1;
269     }
270     return result;
271 }
272
273 CacheControlDirectives parseCacheControlDirectives(const HTTPHeaderMap& headers)
274 {
275     CacheControlDirectives result;
276
277     String cacheControlValue = headers.get(HTTPHeaderName::CacheControl);
278     if (!cacheControlValue.isEmpty()) {
279         auto directives = parseCacheHeader(cacheControlValue);
280
281         size_t directivesSize = directives.size();
282         for (size_t i = 0; i < directivesSize; ++i) {
283             // A no-cache directive with a value is only meaningful for proxy caches.
284             // It should be ignored by a browser level cache.
285             // http://tools.ietf.org/html/rfc7234#section-5.2.2.2
286             if (equalLettersIgnoringASCIICase(directives[i].first, "no-cache") && directives[i].second.isEmpty())
287                 result.noCache = true;
288             else if (equalLettersIgnoringASCIICase(directives[i].first, "no-store"))
289                 result.noStore = true;
290             else if (equalLettersIgnoringASCIICase(directives[i].first, "must-revalidate"))
291                 result.mustRevalidate = true;
292             else if (equalLettersIgnoringASCIICase(directives[i].first, "max-age")) {
293                 if (result.maxAge) {
294                     // First max-age directive wins if there are multiple ones.
295                     continue;
296                 }
297                 bool ok;
298                 double maxAge = directives[i].second.toDouble(&ok);
299                 if (ok)
300                     result.maxAge = Seconds { maxAge };
301             } else if (equalLettersIgnoringASCIICase(directives[i].first, "max-stale")) {
302                 // https://tools.ietf.org/html/rfc7234#section-5.2.1.2
303                 if (result.maxStale) {
304                     // First max-stale directive wins if there are multiple ones.
305                     continue;
306                 }
307                 if (directives[i].second.isEmpty()) {
308                     // if no value is assigned to max-stale, then the client is willing to accept a stale response of any age.
309                     result.maxStale = Seconds::infinity();
310                     continue;
311                 }
312                 bool ok;
313                 double maxStale = directives[i].second.toDouble(&ok);
314                 if (ok)
315                     result.maxStale = Seconds { maxStale };
316             } else if (equalLettersIgnoringASCIICase(directives[i].first, "immutable"))
317                 result.immutable = true;
318         }
319     }
320
321     if (!result.noCache) {
322         // Handle Pragma: no-cache
323         // This is deprecated and equivalent to Cache-control: no-cache
324         // Don't bother tokenizing the value; handling that exactly right is not important.
325         result.noCache = headers.get(HTTPHeaderName::Pragma).containsIgnoringASCIICase("no-cache");
326     }
327
328     return result;
329 }
330
331 static String cookieRequestHeaderFieldValue(const NetworkStorageSession& session, const ResourceRequest& request)
332 {
333     return session.cookieRequestHeaderFieldValue(request.firstPartyForCookies(), SameSiteInfo::create(request), request.url(), WTF::nullopt, WTF::nullopt, request.url().protocolIs("https") ? IncludeSecureCookies::Yes : IncludeSecureCookies::No).first;
334 }
335
336 static String cookieRequestHeaderFieldValue(const CookieJar* cookieJar, const ResourceRequest& request)
337 {
338     if (!cookieJar)
339         return { };
340
341     return cookieJar->cookieRequestHeaderFieldValue(request.firstPartyForCookies(), SameSiteInfo::create(request), request.url(), WTF::nullopt, WTF::nullopt, request.url().protocolIs("https") ? IncludeSecureCookies::Yes : IncludeSecureCookies::No).first;
342 }
343
344 static String headerValueForVary(const ResourceRequest& request, const String& headerName, Function<String()>&& cookieRequestHeaderFieldValueFunction)
345 {
346     // Explicit handling for cookies is needed because they are added magically by the networking layer.
347     // FIXME: The value might have changed between making the request and retrieving the cookie here.
348     // We could fetch the cookie when making the request but that seems overkill as the case is very rare and it
349     // is a blocking operation. This should be sufficient to cover reasonable cases.
350     if (headerName == httpHeaderNameString(HTTPHeaderName::Cookie))
351         return cookieRequestHeaderFieldValueFunction();
352     return request.httpHeaderField(headerName);
353 }
354
355 static Vector<std::pair<String, String>> collectVaryingRequestHeadersInternal(const ResourceResponse& response, Function<String(const String& headerName)>&& headerValueForVaryFunction)
356 {
357     String varyValue = response.httpHeaderField(HTTPHeaderName::Vary);
358     if (varyValue.isEmpty())
359         return { };
360     Vector<String> varyingHeaderNames = varyValue.split(',');
361     Vector<std::pair<String, String>> varyingRequestHeaders;
362     varyingRequestHeaders.reserveCapacity(varyingHeaderNames.size());
363     for (auto& varyHeaderName : varyingHeaderNames) {
364         String headerName = varyHeaderName.stripWhiteSpace();
365         String headerValue = headerValueForVaryFunction(headerName);
366         varyingRequestHeaders.append(std::make_pair(headerName, headerValue));
367     }
368     return varyingRequestHeaders;
369 }
370
371 Vector<std::pair<String, String>> collectVaryingRequestHeaders(NetworkStorageSession* storageSession, const ResourceRequest& request, const ResourceResponse& response)
372 {
373     if (!storageSession)
374         return { };
375     return collectVaryingRequestHeadersInternal(response, [&] (const String& headerName) {
376         return headerValueForVary(request, headerName, [&] {
377             return cookieRequestHeaderFieldValue(*storageSession, request);
378         });
379     });
380 }
381
382 Vector<std::pair<String, String>> collectVaryingRequestHeaders(const CookieJar* cookieJar, const ResourceRequest& request, const ResourceResponse& response)
383 {
384     return collectVaryingRequestHeadersInternal(response, [&] (const String& headerName) {
385         return headerValueForVary(request, headerName, [&] {
386             return cookieRequestHeaderFieldValue(cookieJar, request);
387         });
388     });
389 }
390
391 static bool verifyVaryingRequestHeadersInternal(const Vector<std::pair<String, String>>& varyingRequestHeaders, Function<String(const String&)>&& headerValueForVary)
392 {
393     for (auto& varyingRequestHeader : varyingRequestHeaders) {
394         // FIXME: Vary: * in response would ideally trigger a cache delete instead of a store.
395         if (varyingRequestHeader.first == "*")
396             return false;
397         if (headerValueForVary(varyingRequestHeader.first) != varyingRequestHeader.second)
398             return false;
399     }
400     return true;
401 }
402
403 bool verifyVaryingRequestHeaders(NetworkStorageSession* storageSession, const Vector<std::pair<String, String>>& varyingRequestHeaders, const ResourceRequest& request)
404 {
405     if (!storageSession)
406         return false;
407     return verifyVaryingRequestHeadersInternal(varyingRequestHeaders, [&] (const String& headerName) {
408         return headerValueForVary(request, headerName, [&] {
409             return cookieRequestHeaderFieldValue(*storageSession, request);
410         });
411     });
412 }
413
414 bool verifyVaryingRequestHeaders(const CookieJar* cookieJar, const Vector<std::pair<String, String>>& varyingRequestHeaders, const ResourceRequest& request)
415 {
416     return verifyVaryingRequestHeadersInternal(varyingRequestHeaders, [&] (const String& headerName) {
417         return headerValueForVary(request, headerName, [&] {
418             return cookieRequestHeaderFieldValue(cookieJar, request);
419         });
420     });
421 }
422
423 // http://tools.ietf.org/html/rfc7231#page-48
424 bool isStatusCodeCacheableByDefault(int statusCode)
425 {
426     switch (statusCode) {
427     case 200: // OK
428     case 203: // Non-Authoritative Information
429     case 204: // No Content
430     case 206: // Partial Content
431     case 300: // Multiple Choices
432     case 301: // Moved Permanently
433     case 404: // Not Found
434     case 405: // Method Not Allowed
435     case 410: // Gone
436     case 414: // URI Too Long
437     case 501: // Not Implemented
438         return true;
439     default:
440         return false;
441     }
442 }
443
444 bool isStatusCodePotentiallyCacheable(int statusCode)
445 {
446     switch (statusCode) {
447     case 201: // Created
448     case 202: // Accepted
449     case 205: // Reset Content
450     case 302: // Found
451     case 303: // See Other
452     case 307: // Temporary redirect
453     case 403: // Forbidden
454     case 406: // Not Acceptable
455     case 415: // Unsupported Media Type
456         return true;
457     default:
458         return false;
459     }
460 }
461
462 }