1
2/*
3 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "DOMCacheEngine.h"
29
30#include "CacheQueryOptions.h"
31#include "Exception.h"
32#include "HTTPParsers.h"
33
34namespace WebCore {
35
36namespace DOMCacheEngine {
37
38static inline Exception errorToException(Error error)
39{
40 switch (error) {
41 case Error::NotImplemented:
42 return Exception { NotSupportedError, "Not implemented"_s };
43 case Error::ReadDisk:
44 return Exception { TypeError, "Failed reading data from the file system"_s };
45 case Error::WriteDisk:
46 return Exception { TypeError, "Failed writing data to the file system"_s };
47 case Error::QuotaExceeded:
48 return Exception { QuotaExceededError, "Quota exceeded"_s };
49 case Error::Internal:
50 return Exception { TypeError, "Internal error"_s };
51 default:
52 ASSERT_NOT_REACHED();
53 return Exception { TypeError, "Connection stopped"_s };
54 }
55}
56
57Exception convertToExceptionAndLog(ScriptExecutionContext* context, Error error)
58{
59 auto exception = errorToException(error);
60 if (context)
61 context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("Cache API operation failed: ", exception.message()));
62 return exception;
63}
64
65static inline bool matchURLs(const ResourceRequest& request, const URL& cachedURL, const CacheQueryOptions& options)
66{
67 ASSERT(options.ignoreMethod || request.httpMethod() == "GET");
68
69 URL requestURL = request.url();
70 URL cachedRequestURL = cachedURL;
71
72 if (options.ignoreSearch) {
73 if (requestURL.hasQuery())
74 requestURL.setQuery({ });
75 if (cachedRequestURL.hasQuery())
76 cachedRequestURL.setQuery({ });
77 }
78 return equalIgnoringFragmentIdentifier(requestURL, cachedRequestURL);
79}
80
81bool queryCacheMatch(const ResourceRequest& request, const ResourceRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
82{
83 if (!matchURLs(request, cachedRequest.url(), options))
84 return false;
85
86 if (options.ignoreVary)
87 return true;
88
89 String varyValue = cachedResponse.httpHeaderField(WebCore::HTTPHeaderName::Vary);
90 if (varyValue.isNull())
91 return true;
92
93 bool isVarying = false;
94 varyValue.split(',', [&](StringView view) {
95 if (isVarying)
96 return;
97 auto nameView = stripLeadingAndTrailingHTTPSpaces(view);
98 if (nameView == "*") {
99 isVarying = true;
100 return;
101 }
102 auto name = nameView.toString();
103 isVarying = cachedRequest.httpHeaderField(name) != request.httpHeaderField(name);
104 });
105
106 return !isVarying;
107}
108
109bool queryCacheMatch(const ResourceRequest& request, const URL& url, bool hasVaryStar, const HashMap<String, String>& varyHeaders, const CacheQueryOptions& options)
110{
111 if (!matchURLs(request, url, options))
112 return false;
113
114 if (options.ignoreVary)
115 return true;
116
117 if (hasVaryStar)
118 return false;
119
120 for (const auto& pair : varyHeaders) {
121 if (pair.value != request.httpHeaderField(pair.key))
122 return false;
123 }
124 return true;
125}
126
127ResponseBody isolatedResponseBody(const ResponseBody& body)
128{
129 return WTF::switchOn(body, [](const Ref<FormData>& formData) {
130 return formData->isolatedCopy();
131 }, [](const Ref<SharedBuffer>& buffer) {
132 return buffer->copy();
133 }, [](const std::nullptr_t&) {
134 return DOMCacheEngine::ResponseBody { };
135 });
136}
137
138ResponseBody copyResponseBody(const ResponseBody& body)
139{
140 return WTF::switchOn(body, [](const Ref<FormData>& formData) {
141 return formData.copyRef();
142 }, [](const Ref<SharedBuffer>& buffer) {
143 return buffer.copyRef();
144 }, [](const std::nullptr_t&) {
145 return DOMCacheEngine::ResponseBody { };
146 });
147}
148
149Record Record::copy() const
150{
151 return Record { identifier, updateResponseCounter, requestHeadersGuard, request, options, referrer, responseHeadersGuard, response, copyResponseBody(responseBody), responseBodySize };
152}
153
154static inline CacheInfo isolateCacheInfo(const CacheInfo& info)
155{
156 return CacheInfo { info.identifier, info.name.isolatedCopy() };
157}
158
159CacheInfos CacheInfos::isolatedCopy()
160{
161 return { WTF::map(infos, isolateCacheInfo), updateCounter };
162}
163
164} // namespace DOMCacheEngine
165
166} // namespace WebCore
167
168