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)](CacheStorageConnection::Error error) mutable {
176 if (error != CacheStorageConnection::Error::None) {
177 promise.reject(CacheStorageConnection::exceptionFromError(error));
184 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
186 RefPtr<FetchRequest> request;
187 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
188 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
189 if (request->method() != "GET" && !options.ignoreMethod) {
190 promise.resolve(false);
194 if (UNLIKELY(!scriptExecutionContext()))
196 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
199 batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](bool didDelete, CacheStorageConnection::Error error) mutable {
200 if (error != CacheStorageConnection::Error::None) {
201 promise.reject(CacheStorageConnection::exceptionFromError(error));
204 promise.resolve(didDelete);
208 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
210 RefPtr<FetchRequest> request;
212 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
213 request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
214 if (request->method() != "GET" && !options.ignoreMethod) {
215 promise.resolve(Vector<Ref<FetchRequest>> { });
219 if (UNLIKELY(!scriptExecutionContext()))
221 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
226 retrieveRecords([this, promise = WTFMove(promise)]() mutable {
227 Vector<Ref<FetchRequest>> requests;
228 requests.reserveInitialCapacity(m_records.size());
229 for (auto& record : m_records)
230 requests.uncheckedAppend(record.request.copyRef());
231 promise.resolve(requests);
236 queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
237 Vector<Ref<FetchRequest>> requests;
238 requests.reserveInitialCapacity(records.size());
239 for (auto& record : records)
240 requests.uncheckedAppend(record.request.copyRef());
241 promise.resolve(requests);
245 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
247 setPendingActivity(this);
248 m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
250 updateRecords(WTFMove(records));
253 unsetPendingActivity(this);
257 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
259 retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
260 callback(queryCacheWithTargetStorage(request.get(), options, m_records));
264 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
266 // We need to pass the resource request with all correct headers hence why we call resourceRequest().
267 return CacheStorageConnection::queryCacheMatch(request.resourceRequest(), cachedRequest.resourceRequest(), cachedResponse, options);
270 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
272 if (!options.ignoreMethod && request.method() != "GET")
275 Vector<CacheStorageRecord> records;
276 for (auto& record : targetStorage) {
277 if (queryCacheMatch(request, record.request.get(), record.response->resourceResponse(), options))
278 records.append({ record.identifier, record.request.copyRef(), record.response.copyRef() });
283 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(bool didRemoval, CacheStorageConnection::Error error)>&& callback)
285 setPendingActivity(this);
286 m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
288 if (error == CacheStorageConnection::Error::None)
289 m_records.removeAllMatching([&](const auto& item) { return records.contains(item.identifier); });
291 callback(!records.isEmpty(), error);
293 unsetPendingActivity(this);
297 static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response)
299 // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
300 ResourceResponse cachedResponse = response.resourceResponse();
301 for (auto& header : response.headers().internalHeaders())
302 cachedResponse.setHTTPHeaderField(header.key, header.value);
304 ResourceRequest cachedRequest = request.internalRequest();
305 cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
308 request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
309 response.headers().guard(), WTFMove(cachedResponse)
313 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, WTF::Function<void(CacheStorageConnection::Error)>&& callback)
315 Vector<CacheStorageConnection::Record> records;
316 records.append(toConnectionRecord(request, response));
318 setPendingActivity(this);
319 m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
323 unsetPendingActivity(this);
327 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
329 ASSERT(scriptExecutionContext());
330 Vector<CacheStorageRecord> newRecords;
332 for (auto& record : records) {
333 size_t index = m_records.findMatching([&](const auto& item) { return item.identifier == record.identifier; });
334 if (index != notFound)
335 newRecords.append(WTFMove(m_records[index]));
337 auto requestHeaders = FetchHeaders::create(record.requestHeadersGuard, HTTPHeaderMap { record.request.httpHeaderFields() });
338 FetchRequest::InternalRequest internalRequest = { WTFMove(record.request), WTFMove(record.options), WTFMove(record.referrer) };
339 auto request = FetchRequest::create(*scriptExecutionContext(), std::nullopt, WTFMove(requestHeaders), WTFMove(internalRequest));
341 auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
342 auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
344 newRecords.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
347 m_records = WTFMove(newRecords);
355 const char* Cache::activeDOMObjectName() const
360 bool Cache::canSuspendForDocumentSuspension() const
362 return m_records.isEmpty() && !hasPendingActivity();
366 } // namespace WebCore