2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "CacheQueryOptions.h"
30 #include "FetchResponse.h"
31 #include "HTTPParsers.h"
32 #include "JSFetchRequest.h"
33 #include "JSFetchResponse.h"
34 #include "ScriptExecutionContext.h"
39 Cache::Cache(ScriptExecutionContext& context, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&& connection)
40 : ActiveDOMObject(&context)
41 , m_name(WTFMove(name))
42 , m_identifier(identifier)
43 , m_connection(WTFMove(connection))
52 void Cache::match(RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
54 doMatch(WTFMove(info), WTFMove(options), WTFMove(promise), MatchType::OnlyFirst);
57 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
59 doMatch(WTFMove(info), WTFMove(options), WTFMove(promise), MatchType::All);
62 void Cache::doMatch(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise, MatchType matchType)
64 RefPtr<FetchRequest> request;
66 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
67 request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
68 if (request->method() != "GET" && !options.ignoreMethod) {
69 if (matchType == MatchType::OnlyFirst) {
73 promise->resolve<IDLSequence<IDLInterface<FetchResponse>>>(Vector<Ref<FetchResponse>> { });
77 if (UNLIKELY(!scriptExecutionContext()))
79 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
84 ASSERT(matchType == MatchType::All);
85 retrieveRecords([this, promise = WTFMove(promise)]() {
86 Vector<Ref<FetchResponse>> responses;
87 responses.reserveInitialCapacity(m_records.size());
88 for (auto& record : m_records)
89 responses.uncheckedAppend(record.response->cloneForJS());
90 promise->resolve<IDLSequence<IDLInterface<FetchResponse>>>(responses);
94 queryCache(request.releaseNonNull(), WTFMove(options), [matchType, promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
95 if (matchType == MatchType::OnlyFirst) {
97 promise->resolve<IDLInterface<FetchResponse>>(records[0].response->cloneForJS());
104 Vector<Ref<FetchResponse>> responses;
105 responses.reserveInitialCapacity(records.size());
106 for (auto& record : records)
107 responses.uncheckedAppend(record.response->cloneForJS());
108 promise->resolve<IDLSequence<IDLInterface<FetchResponse>>>(responses);
112 void Cache::add(RequestInfo&&, DOMPromiseDeferred<void>&& promise)
114 promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
117 void Cache::addAll(Vector<RequestInfo>&&, DOMPromiseDeferred<void>&& promise)
119 promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
122 void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
124 RefPtr<FetchRequest> request;
125 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
126 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
127 if (request->method() != "GET") {
128 promise.reject(Exception { TypeError, ASCIILiteral("Request method is not GET") });
132 if (UNLIKELY(!scriptExecutionContext()))
134 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
137 if (!protocolIsInHTTPFamily(request->url())) {
138 promise.reject(Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") });
142 // FIXME: This is inefficient, we should be able to split and trim whitespaces at the same time.
143 auto varyValue = response->headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
144 Vector<String> varyHeaderNames;
145 varyValue.split(',', false, varyHeaderNames);
146 for (auto& name : varyHeaderNames) {
147 if (stripLeadingAndTrailingHTTPSpaces(name) == "*") {
148 promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
153 if (response->isDisturbed()) {
154 promise.reject(Exception { TypeError, ASCIILiteral("Response is disturbed or locked") });
158 if (response->status() == 206) {
159 promise.reject(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
163 // FIXME: Add support for being loaded responses.
164 if (response->isLoading()) {
165 promise.reject(Exception { NotSupportedError, ASCIILiteral("Caching a loading Response is not yet supported") });
169 // FIXME: Add support for ReadableStream.
170 if (response->isReadableStreamBody()) {
171 promise.reject(Exception { NotSupportedError, ASCIILiteral("Caching a Response with data stored in a ReadableStream is not yet supported") });
175 batchPutOperation(*request, response.get(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
176 promise.settle(WTFMove(result));
180 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
182 RefPtr<FetchRequest> request;
183 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
184 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
185 if (request->method() != "GET" && !options.ignoreMethod) {
186 promise.resolve(false);
190 if (UNLIKELY(!scriptExecutionContext()))
192 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
195 batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
196 promise.settle(WTFMove(result));
200 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
202 RefPtr<FetchRequest> request;
204 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
205 request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
206 if (request->method() != "GET" && !options.ignoreMethod) {
207 promise.resolve(Vector<Ref<FetchRequest>> { });
211 if (UNLIKELY(!scriptExecutionContext()))
213 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
218 retrieveRecords([this, promise = WTFMove(promise)]() mutable {
219 Vector<Ref<FetchRequest>> requests;
220 requests.reserveInitialCapacity(m_records.size());
221 for (auto& record : m_records)
222 requests.uncheckedAppend(record.request.copyRef());
223 promise.resolve(requests);
228 queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
229 Vector<Ref<FetchRequest>> requests;
230 requests.reserveInitialCapacity(records.size());
231 for (auto& record : records)
232 requests.uncheckedAppend(record.request.copyRef());
233 promise.resolve(requests);
237 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
239 setPendingActivity(this);
240 m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
242 updateRecords(WTFMove(records));
245 unsetPendingActivity(this);
249 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
251 retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
252 callback(queryCacheWithTargetStorage(request.get(), options, m_records));
256 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
258 // We need to pass the resource request with all correct headers hence why we call resourceRequest().
259 return CacheStorageConnection::queryCacheMatch(request.resourceRequest(), cachedRequest.resourceRequest(), cachedResponse, options);
262 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
264 if (!options.ignoreMethod && request.method() != "GET")
267 Vector<CacheStorageRecord> records;
268 for (auto& record : targetStorage) {
269 if (queryCacheMatch(request, record.request.get(), record.response->resourceResponse(), options))
270 records.append({ record.identifier, record.request.copyRef(), record.response.copyRef() });
275 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(ExceptionOr<bool>&&)>&& callback)
277 setPendingActivity(this);
278 m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
280 callback(CacheStorageConnection::exceptionOrResult(!records.isEmpty(), error));
282 unsetPendingActivity(this);
286 static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response)
288 // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
289 ResourceResponse cachedResponse = response.resourceResponse();
290 for (auto& header : response.headers().internalHeaders())
291 cachedResponse.setHTTPHeaderField(header.key, header.value);
293 ResourceRequest cachedRequest = request.internalRequest();
294 cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
297 request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
298 response.headers().guard(), WTFMove(cachedResponse)
302 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
304 Vector<CacheStorageConnection::Record> records;
305 records.append(toConnectionRecord(request, response));
307 setPendingActivity(this);
308 m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
310 callback(CacheStorageConnection::errorToException(error));
312 unsetPendingActivity(this);
316 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
318 ASSERT(scriptExecutionContext());
319 Vector<CacheStorageRecord> newRecords;
321 for (auto& record : records) {
322 size_t index = m_records.findMatching([&](const auto& item) { return item.identifier == record.identifier; });
323 if (index != notFound)
324 newRecords.append(WTFMove(m_records[index]));
326 auto requestHeaders = FetchHeaders::create(record.requestHeadersGuard, HTTPHeaderMap { record.request.httpHeaderFields() });
327 FetchRequest::InternalRequest internalRequest = { WTFMove(record.request), WTFMove(record.options), WTFMove(record.referrer) };
328 auto request = FetchRequest::create(*scriptExecutionContext(), std::nullopt, WTFMove(requestHeaders), WTFMove(internalRequest));
330 auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
331 auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
333 newRecords.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
336 m_records = WTFMove(newRecords);
344 const char* Cache::activeDOMObjectName() const
349 bool Cache::canSuspendForDocumentSuspension() const
351 return m_records.isEmpty() && !hasPendingActivity();
355 } // namespace WebCore