[Cache API] Add response body storage
[WebKit-https.git] / Source / WebCore / Modules / cache / Cache.cpp
1 /*
2  * Copyright (C) 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 "Cache.h"
28
29 #include "CacheQueryOptions.h"
30 #include "FetchResponse.h"
31 #include "HTTPParsers.h"
32 #include "JSFetchRequest.h"
33 #include "JSFetchResponse.h"
34 #include "ScriptExecutionContext.h"
35 #include "URL.h"
36
37 namespace WebCore {
38
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))
44 {
45     suspendIfNeeded();
46 }
47
48 Cache::~Cache()
49 {
50 }
51
52 void Cache::match(RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
53 {
54     doMatch(WTFMove(info), WTFMove(options), WTFMove(promise), MatchType::OnlyFirst);
55 }
56
57 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
58 {
59     doMatch(WTFMove(info), WTFMove(options), WTFMove(promise), MatchType::All);
60 }
61
62 void Cache::doMatch(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise, MatchType matchType)
63 {
64     RefPtr<FetchRequest> request;
65     if (info) {
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) {
70                     promise->resolve();
71                     return;
72                 }
73                 promise->resolve<IDLSequence<IDLInterface<FetchResponse>>>(Vector<Ref<FetchResponse>> { });
74                 return;
75             }
76         } else {
77             if (UNLIKELY(!scriptExecutionContext()))
78                 return;
79             request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
80         }
81     }
82
83     if (!request) {
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);
91         });
92         return;
93     }
94     queryCache(request.releaseNonNull(), WTFMove(options), [matchType, promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
95         if (matchType == MatchType::OnlyFirst) {
96             if (records.size()) {
97                 promise->resolve<IDLInterface<FetchResponse>>(records[0].response->cloneForJS());
98                 return;
99             }
100             promise->resolve();
101             return;
102         }
103
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);
109     });
110 }
111
112 void Cache::add(RequestInfo&&, DOMPromiseDeferred<void>&& promise)
113 {
114     promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
115 }
116
117 void Cache::addAll(Vector<RequestInfo>&&, DOMPromiseDeferred<void>&& promise)
118 {
119     promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
120 }
121
122 void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
123 {
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") });
129             return;
130         }
131     } else {
132         if (UNLIKELY(!scriptExecutionContext()))
133             return;
134         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
135     }
136
137     if (!protocolIsInHTTPFamily(request->url())) {
138         promise.reject(Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") });
139         return;
140     }
141
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") });
149             return;
150         }
151     }
152
153     if (response->isDisturbed()) {
154         promise.reject(Exception { TypeError, ASCIILiteral("Response is disturbed or locked") });
155         return;
156     }
157
158     if (response->status() == 206) {
159         promise.reject(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
160         return;
161     }
162
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") });
166         return;
167     }
168
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") });
172         return;
173     }
174
175     batchPutOperation(*request, response.get(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
176         promise.settle(WTFMove(result));
177     });
178 }
179
180 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
181 {
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);
187             return;
188         }
189     } else {
190         if (UNLIKELY(!scriptExecutionContext()))
191             return;
192         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
193     }
194
195     batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
196         promise.settle(WTFMove(result));
197     });
198 }
199
200 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
201 {
202     RefPtr<FetchRequest> request;
203     if (info) {
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>> { });
208                 return;
209             }
210         } else {
211             if (UNLIKELY(!scriptExecutionContext()))
212                 return;
213             request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
214         }
215     }
216
217     if (!request) {
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);
224         });
225         return;
226     }
227
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);
234     });
235 }
236
237 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
238 {
239     setPendingActivity(this);
240     m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
241         if (!m_isStopped) {
242             updateRecords(WTFMove(records));
243             callback();
244         }
245         unsetPendingActivity(this);
246     });
247 }
248
249 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
250 {
251     retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
252         callback(queryCacheWithTargetStorage(request.get(), options, m_records));
253     });
254 }
255
256 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
257 {
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);
260 }
261
262 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
263 {
264     if (!options.ignoreMethod && request.method() != "GET")
265         return { };
266
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() });
271     }
272     return records;
273 }
274
275 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(ExceptionOr<bool>&&)>&& callback)
276 {
277     setPendingActivity(this);
278     m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
279         if (!m_isStopped)
280             callback(CacheStorageConnection::exceptionOrResult(!records.isEmpty(), error));
281
282         unsetPendingActivity(this);
283     });
284 }
285
286 static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response)
287 {
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);
292
293     ResourceRequest cachedRequest = request.internalRequest();
294     cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
295
296     return { 0,
297         request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
298         response.headers().guard(), WTFMove(cachedResponse), response.consumeBody()
299     };
300 }
301
302 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
303 {
304     Vector<CacheStorageConnection::Record> records;
305     records.append(toConnectionRecord(request, response));
306
307     setPendingActivity(this);
308     m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
309         if (!m_isStopped)
310             callback(CacheStorageConnection::errorToException(error));
311
312         unsetPendingActivity(this);
313     });
314 }
315
316 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
317 {
318     ASSERT(scriptExecutionContext());
319     Vector<CacheStorageRecord> newRecords;
320
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]));
325         else {
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));
329
330             auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
331             auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
332             response->setBodyData(WTFMove(record.responseBody));
333
334             newRecords.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
335         }
336     }
337     m_records = WTFMove(newRecords);
338 }
339
340 void Cache::stop()
341 {
342     m_isStopped = true;
343 }
344
345 const char* Cache::activeDOMObjectName() const
346 {
347     return "Cache";
348 }
349
350 bool Cache::canSuspendForDocumentSuspension() const
351 {
352     return m_records.isEmpty() && !hasPendingActivity();
353 }
354
355
356 } // namespace WebCore