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 static CacheStorageConnection::Record toConnectionRecord(const FetchRequest&, FetchResponse&, CacheStorageConnection::ResponseBody&&);
41 Cache::Cache(ScriptExecutionContext& context, String&& name, uint64_t identifier, Ref<CacheStorageConnection>&& connection)
42 : ActiveDOMObject(&context)
43 , m_name(WTFMove(name))
44 , m_identifier(identifier)
45 , m_connection(WTFMove(connection))
54 void Cache::match(RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
56 doMatch(WTFMove(info), WTFMove(options), [promise = WTFMove(promise)](FetchResponse* result) mutable {
61 promise->resolve<IDLInterface<FetchResponse>>(*result);
65 void Cache::doMatch(RequestInfo&& info, CacheQueryOptions&& options, MatchCallback&& callback)
67 if (UNLIKELY(!scriptExecutionContext()))
70 auto requestOrException = requestFromInfo(WTFMove(info), options.ignoreMethod);
71 if (requestOrException.hasException()) {
75 auto request = requestOrException.releaseReturnValue();
77 queryCache(request.get(), WTFMove(options), [callback = WTFMove(callback)](const Vector<CacheStorageRecord>& records) mutable {
78 if (records.isEmpty()) {
82 callback(records[0].response->cloneForJS().ptr());
86 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, MatchAllPromise&& promise)
88 if (UNLIKELY(!scriptExecutionContext()))
91 RefPtr<FetchRequest> request;
93 auto requestOrException = requestFromInfo(WTFMove(info.value()), options.ignoreMethod);
94 if (requestOrException.hasException()) {
98 request = requestOrException.releaseReturnValue();
102 retrieveRecords([this, promise = WTFMove(promise)]() mutable {
103 Vector<Ref<FetchResponse>> responses;
104 responses.reserveInitialCapacity(m_records.size());
105 for (auto& record : m_records)
106 responses.uncheckedAppend(record.response->cloneForJS());
107 promise.resolve(responses);
111 queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
112 Vector<Ref<FetchResponse>> responses;
113 responses.reserveInitialCapacity(records.size());
114 for (auto& record : records)
115 responses.uncheckedAppend(record.response->cloneForJS());
116 promise.resolve(responses);
120 void Cache::add(RequestInfo&& info, DOMPromiseDeferred<void>&& promise)
122 addAll(Vector<RequestInfo> { WTFMove(info) }, WTFMove(promise));
125 static inline bool hasResponseVaryStarHeaderValue(const FetchResponse& response)
127 auto varyValue = response.headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
128 bool hasStar = false;
129 varyValue.split(',', false, [&](StringView view) {
130 if (!hasStar && stripLeadingAndTrailingHTTPSpaces(view) == "*")
136 class FetchTasksHandler : public RefCounted<FetchTasksHandler> {
138 explicit FetchTasksHandler(Function<void(ExceptionOr<Vector<CacheStorageConnection::Record>>&&)>&& callback)
139 : m_callback(WTFMove(callback))
146 m_callback(WTFMove(m_records));
149 const Vector<CacheStorageConnection::Record>& records() const { return m_records; }
151 size_t addRecord(CacheStorageConnection::Record&& record)
154 m_records.append(WTFMove(record));
155 return m_records.size() - 1;
158 void addResponseBody(size_t position, Ref<SharedBuffer>&& data)
161 m_records[position].responseBody = WTFMove(data);
164 bool isDone() const { return !m_callback; }
166 void error(Exception&& exception)
168 if (auto callback = WTFMove(m_callback))
169 callback(WTFMove(exception));
173 Vector<CacheStorageConnection::Record> m_records;
174 Function<void(ExceptionOr<Vector<CacheStorageConnection::Record>>&&)> m_callback;
177 ExceptionOr<Ref<FetchRequest>> Cache::requestFromInfo(RequestInfo&& info, bool ignoreMethod)
179 RefPtr<FetchRequest> request;
180 if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
181 request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
182 if (request->method() != "GET" && !ignoreMethod)
183 return Exception { TypeError, ASCIILiteral("Request method is not GET") };
185 request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
187 if (!protocolIsInHTTPFamily(request->url()))
188 return Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") };
190 return request.releaseNonNull();
193 void Cache::addAll(Vector<RequestInfo>&& infos, DOMPromiseDeferred<void>&& promise)
195 if (UNLIKELY(!scriptExecutionContext()))
198 Vector<Ref<FetchRequest>> requests;
199 requests.reserveInitialCapacity(infos.size());
200 for (auto& info : infos) {
201 bool ignoreMethod = false;
202 auto requestOrException = requestFromInfo(WTFMove(info), ignoreMethod);
203 if (requestOrException.hasException()) {
204 promise.reject(requestOrException.releaseException());
207 requests.uncheckedAppend(requestOrException.releaseReturnValue());
210 auto taskHandler = adoptRef(*new FetchTasksHandler([protectedThis = makeRef(*this), this, promise = WTFMove(promise)](ExceptionOr<Vector<CacheStorageConnection::Record>>&& result) mutable {
211 if (result.hasException()) {
212 promise.reject(result.releaseException());
215 batchPutOperation(result.releaseReturnValue(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
216 promise.settle(WTFMove(result));
220 for (auto& request : requests) {
221 auto& requestReference = request.get();
222 FetchResponse::fetch(*scriptExecutionContext(), requestReference, [this, request = WTFMove(request), taskHandler = taskHandler.copyRef()](ExceptionOr<FetchResponse&>&& result) mutable {
224 if (taskHandler->isDone())
227 if (result.hasException()) {
228 taskHandler->error(result.releaseException());
232 auto& response = result.releaseReturnValue();
234 if (!response.ok()) {
235 taskHandler->error(Exception { TypeError, ASCIILiteral("Response is not OK") });
239 if (hasResponseVaryStarHeaderValue(response)) {
240 taskHandler->error(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
244 if (response.status() == 206) {
245 taskHandler->error(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
249 CacheQueryOptions options;
250 for (const auto& record : taskHandler->records()) {
251 if (CacheStorageConnection::queryCacheMatch(request->resourceRequest(), record.request, record.response, options)) {
252 taskHandler->error(Exception { InvalidStateError, ASCIILiteral("addAll cannot store several matching requests")});
256 size_t recordPosition = taskHandler->addRecord(toConnectionRecord(request.get(), response, nullptr));
258 response.consumeBodyWhenLoaded([taskHandler = WTFMove(taskHandler), recordPosition](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
259 if (taskHandler->isDone())
262 if (result.hasException()) {
263 taskHandler->error(result.releaseException());
266 if (auto value = result.releaseReturnValue())
267 taskHandler->addResponseBody(recordPosition, value.releaseNonNull());
273 void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
275 if (UNLIKELY(!scriptExecutionContext()))
278 bool ignoreMethod = false;
279 auto requestOrException = requestFromInfo(WTFMove(info), ignoreMethod);
280 if (requestOrException.hasException()) {
281 promise.reject(requestOrException.releaseException());
284 auto request = requestOrException.releaseReturnValue();
286 if (hasResponseVaryStarHeaderValue(response.get())) {
287 promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
291 if (response->status() == 206) {
292 promise.reject(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
296 // FIXME: Add support for ReadableStream.
297 if (response->isReadableStreamBody()) {
298 promise.reject(Exception { NotSupportedError, ASCIILiteral("Caching a Response with data stored in a ReadableStream is not yet supported") });
302 if (response->isDisturbed()) {
303 promise.reject(Exception { TypeError, ASCIILiteral("Response is disturbed or locked") });
307 if (response->isLoading()) {
308 setPendingActivity(this);
309 response->consumeBodyWhenLoaded([promise = WTFMove(promise), request = WTFMove(request), response = WTFMove(response), this](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
310 if (result.hasException())
311 promise.reject(result.releaseException());
313 CacheStorageConnection::ResponseBody body;
314 if (auto buffer = result.releaseReturnValue())
315 body = buffer.releaseNonNull();
316 batchPutOperation(request.get(), response.get(), WTFMove(body), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
317 promise.settle(WTFMove(result));
320 unsetPendingActivity(this);
325 batchPutOperation(request.get(), response.get(), response->consumeBody(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
326 promise.settle(WTFMove(result));
330 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
332 if (UNLIKELY(!scriptExecutionContext()))
335 auto requestOrException = requestFromInfo(WTFMove(info), options.ignoreMethod);
336 if (requestOrException.hasException()) {
337 promise.resolve(false);
341 batchDeleteOperation(requestOrException.releaseReturnValue(), WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
342 promise.settle(WTFMove(result));
346 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
348 if (UNLIKELY(!scriptExecutionContext()))
351 RefPtr<FetchRequest> request;
353 auto requestOrException = requestFromInfo(WTFMove(info.value()), options.ignoreMethod);
354 if (requestOrException.hasException()) {
355 promise.resolve(Vector<Ref<FetchRequest>> { });
358 request = requestOrException.releaseReturnValue();
362 retrieveRecords([this, promise = WTFMove(promise)]() mutable {
363 Vector<Ref<FetchRequest>> requests;
364 requests.reserveInitialCapacity(m_records.size());
365 for (auto& record : m_records)
366 requests.uncheckedAppend(record.request.copyRef());
367 promise.resolve(requests);
372 queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
373 Vector<Ref<FetchRequest>> requests;
374 requests.reserveInitialCapacity(records.size());
375 for (auto& record : records)
376 requests.uncheckedAppend(record.request.copyRef());
377 promise.resolve(requests);
381 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
383 setPendingActivity(this);
384 m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
386 updateRecords(WTFMove(records));
389 unsetPendingActivity(this);
393 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
395 retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
396 callback(queryCacheWithTargetStorage(request.get(), options, m_records));
400 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
402 // We need to pass the resource request with all correct headers hence why we call resourceRequest().
403 return CacheStorageConnection::queryCacheMatch(request.resourceRequest(), cachedRequest.resourceRequest(), cachedResponse, options);
406 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
408 if (!options.ignoreMethod && request.method() != "GET")
411 Vector<CacheStorageRecord> records;
412 for (auto& record : targetStorage) {
413 if (queryCacheMatch(request, record.request.get(), record.response->resourceResponse(), options))
414 records.append({ record.identifier, record.updateResponseCounter, record.request.copyRef(), record.response.copyRef() });
419 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(ExceptionOr<bool>&&)>&& callback)
421 setPendingActivity(this);
422 m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
424 callback(CacheStorageConnection::exceptionOrResult(!records.isEmpty(), error));
426 unsetPendingActivity(this);
430 CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody)
432 // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
433 ResourceResponse cachedResponse = response.resourceResponse();
434 for (auto& header : response.headers().internalHeaders())
435 cachedResponse.setHTTPHeaderField(header.key, header.value);
437 ResourceRequest cachedRequest = request.internalRequest();
438 cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
440 ASSERT(!cachedRequest.isNull());
441 ASSERT(!cachedResponse.isNull());
444 request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
445 response.headers().guard(), WTFMove(cachedResponse), WTFMove(responseBody)
449 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
451 Vector<CacheStorageConnection::Record> records;
452 records.append(toConnectionRecord(request, response, WTFMove(responseBody)));
454 batchPutOperation(WTFMove(records), WTFMove(callback));
457 void Cache::batchPutOperation(Vector<CacheStorageConnection::Record>&& records, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
459 setPendingActivity(this);
460 m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
462 callback(CacheStorageConnection::errorToException(error));
464 unsetPendingActivity(this);
468 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
470 ASSERT(scriptExecutionContext());
471 Vector<CacheStorageRecord> newRecords;
473 for (auto& record : records) {
474 size_t index = m_records.findMatching([&](const auto& item) { return item.identifier == record.identifier; });
475 if (index != notFound) {
476 auto& current = m_records[index];
477 if (current.updateResponseCounter != record.updateResponseCounter) {
478 auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
479 auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
480 response->setBodyData(WTFMove(record.responseBody));
482 current.response = WTFMove(response);
483 current.updateResponseCounter = record.updateResponseCounter;
485 newRecords.append(WTFMove(current));
487 auto requestHeaders = FetchHeaders::create(record.requestHeadersGuard, HTTPHeaderMap { record.request.httpHeaderFields() });
488 FetchRequest::InternalRequest internalRequest = { WTFMove(record.request), WTFMove(record.options), WTFMove(record.referrer) };
489 auto request = FetchRequest::create(*scriptExecutionContext(), std::nullopt, WTFMove(requestHeaders), WTFMove(internalRequest));
491 auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
492 auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
493 response->setBodyData(WTFMove(record.responseBody));
495 newRecords.append(CacheStorageRecord { record.identifier, record.updateResponseCounter, WTFMove(request), WTFMove(response) });
498 m_records = WTFMove(newRecords);
506 const char* Cache::activeDOMObjectName() const
511 bool Cache::canSuspendForDocumentSuspension() const
513 return m_records.isEmpty() && !hasPendingActivity();
517 } // namespace WebCore