0cfa6cec9de84ae52ceaa59440fda4fed7275a93
[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), [promise = WTFMove(promise)](FetchResponse* result) mutable {
55         if (!result) {
56             promise->resolve();
57             return;
58         }
59         promise->resolve<IDLInterface<FetchResponse>>(*result);
60     });
61 }
62
63 void Cache::doMatch(RequestInfo&& info, CacheQueryOptions&& options, MatchCallback&& callback)
64 {
65     RefPtr<FetchRequest> request;
66     if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
67         request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
68         if (request->method() != "GET" && !options.ignoreMethod) {
69             callback(nullptr);
70             return;
71         }
72     } else {
73         if (UNLIKELY(!scriptExecutionContext()))
74             return;
75         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
76     }
77
78     queryCache(request.releaseNonNull(), WTFMove(options), [callback = WTFMove(callback)](const Vector<CacheStorageRecord>& records) mutable {
79         if (records.isEmpty()) {
80             callback(nullptr);
81             return;
82         }
83         callback(records[0].response->cloneForJS().ptr());
84     });
85 }
86
87 void Cache::matchAll(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, MatchAllPromise&& promise)
88 {
89     RefPtr<FetchRequest> request;
90     if (info) {
91         if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
92             request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
93             if (request->method() != "GET" && !options.ignoreMethod) {
94                 promise.resolve({ });
95                 return;
96             }
97         } else {
98             if (UNLIKELY(!scriptExecutionContext()))
99                 return;
100             request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
101         }
102     }
103
104     if (!request) {
105         retrieveRecords([this, promise = WTFMove(promise)]() mutable {
106             Vector<Ref<FetchResponse>> responses;
107             responses.reserveInitialCapacity(m_records.size());
108             for (auto& record : m_records)
109                 responses.uncheckedAppend(record.response->cloneForJS());
110             promise.resolve(responses);
111         });
112         return;
113     }
114     queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
115         Vector<Ref<FetchResponse>> responses;
116         responses.reserveInitialCapacity(records.size());
117         for (auto& record : records)
118             responses.uncheckedAppend(record.response->cloneForJS());
119         promise.resolve(responses);
120     });
121 }
122
123 void Cache::add(RequestInfo&&, DOMPromiseDeferred<void>&& promise)
124 {
125     promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
126 }
127
128 void Cache::addAll(Vector<RequestInfo>&&, DOMPromiseDeferred<void>&& promise)
129 {
130     promise.reject(Exception { NotSupportedError, ASCIILiteral("Not implemented")});
131 }
132
133 void Cache::put(RequestInfo&& info, Ref<FetchResponse>&& response, DOMPromiseDeferred<void>&& promise)
134 {
135     RefPtr<FetchRequest> request;
136     if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
137         request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
138         if (request->method() != "GET") {
139             promise.reject(Exception { TypeError, ASCIILiteral("Request method is not GET") });
140             return;
141         }
142     } else {
143         if (UNLIKELY(!scriptExecutionContext()))
144             return;
145         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
146     }
147
148     if (!protocolIsInHTTPFamily(request->url())) {
149         promise.reject(Exception { TypeError, ASCIILiteral("Request url is not HTTP/HTTPS") });
150         return;
151     }
152
153     // FIXME: This is inefficient, we should be able to split and trim whitespaces at the same time.
154     auto varyValue = response->headers().internalHeaders().get(WebCore::HTTPHeaderName::Vary);
155     Vector<String> varyHeaderNames;
156     varyValue.split(',', false, varyHeaderNames);
157     for (auto& name : varyHeaderNames) {
158         if (stripLeadingAndTrailingHTTPSpaces(name) == "*") {
159             promise.reject(Exception { TypeError, ASCIILiteral("Response has a '*' Vary header value") });
160             return;
161         }
162     }
163
164     if (response->status() == 206) {
165         promise.reject(Exception { TypeError, ASCIILiteral("Response is a 206 partial") });
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     if (response->isDisturbed()) {
176         promise.reject(Exception { TypeError, ASCIILiteral("Response is disturbed or locked") });
177         return;
178     }
179
180     if (response->isLoading()) {
181         setPendingActivity(this);
182         response->consumeBodyWhenLoaded([promise = WTFMove(promise), request = request.releaseNonNull(), response = WTFMove(response), this](ExceptionOr<RefPtr<SharedBuffer>>&& result) mutable {
183             if (result.hasException())
184                 promise.reject(result.releaseException());
185             else {
186                 CacheStorageConnection::ResponseBody body;
187                 if (auto buffer = result.releaseReturnValue())
188                     body = buffer.releaseNonNull();
189                 batchPutOperation(request.get(), response.get(), WTFMove(body), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
190                     promise.settle(WTFMove(result));
191                 });
192             }
193             unsetPendingActivity(this);
194         });
195         return;
196     }
197
198     batchPutOperation(*request, response.get(), response->consumeBody(), [promise = WTFMove(promise)](ExceptionOr<void>&& result) mutable {
199         promise.settle(WTFMove(result));
200     });
201 }
202
203 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
204 {
205     RefPtr<FetchRequest> request;
206     if (WTF::holds_alternative<RefPtr<FetchRequest>>(info)) {
207         request = WTF::get<RefPtr<FetchRequest>>(info).releaseNonNull();
208         if (request->method() != "GET" && !options.ignoreMethod) {
209             promise.resolve(false);
210             return;
211         }
212     } else {
213         if (UNLIKELY(!scriptExecutionContext()))
214             return;
215         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
216     }
217
218     batchDeleteOperation(*request, WTFMove(options), [promise = WTFMove(promise)](ExceptionOr<bool>&& result) mutable {
219         promise.settle(WTFMove(result));
220     });
221 }
222
223 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
224 {
225     RefPtr<FetchRequest> request;
226     if (info) {
227         if (WTF::holds_alternative<RefPtr<FetchRequest>>(info.value())) {
228             request = WTF::get<RefPtr<FetchRequest>>(info.value()).releaseNonNull();
229             if (request->method() != "GET" && !options.ignoreMethod) {
230                 promise.resolve(Vector<Ref<FetchRequest>> { });
231                 return;
232             }
233         } else {
234             if (UNLIKELY(!scriptExecutionContext()))
235                 return;
236             request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
237         }
238     }
239
240     if (!request) {
241         retrieveRecords([this, promise = WTFMove(promise)]() mutable {
242             Vector<Ref<FetchRequest>> requests;
243             requests.reserveInitialCapacity(m_records.size());
244             for (auto& record : m_records)
245                 requests.uncheckedAppend(record.request.copyRef());
246             promise.resolve(requests);
247         });
248         return;
249     }
250
251     queryCache(request.releaseNonNull(), WTFMove(options), [promise = WTFMove(promise)](const Vector<CacheStorageRecord>& records) mutable {
252         Vector<Ref<FetchRequest>> requests;
253         requests.reserveInitialCapacity(records.size());
254         for (auto& record : records)
255             requests.uncheckedAppend(record.request.copyRef());
256         promise.resolve(requests);
257     });
258 }
259
260 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
261 {
262     setPendingActivity(this);
263     m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
264         if (!m_isStopped) {
265             updateRecords(WTFMove(records));
266             callback();
267         }
268         unsetPendingActivity(this);
269     });
270 }
271
272 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
273 {
274     retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
275         callback(queryCacheWithTargetStorage(request.get(), options, m_records));
276     });
277 }
278
279 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
280 {
281     // We need to pass the resource request with all correct headers hence why we call resourceRequest().
282     return CacheStorageConnection::queryCacheMatch(request.resourceRequest(), cachedRequest.resourceRequest(), cachedResponse, options);
283 }
284
285 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
286 {
287     if (!options.ignoreMethod && request.method() != "GET")
288         return { };
289
290     Vector<CacheStorageRecord> records;
291     for (auto& record : targetStorage) {
292         if (queryCacheMatch(request, record.request.get(), record.response->resourceResponse(), options))
293             records.append({ record.identifier, record.request.copyRef(), record.response.copyRef() });
294     }
295     return records;
296 }
297
298 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(ExceptionOr<bool>&&)>&& callback)
299 {
300     setPendingActivity(this);
301     m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
302         if (!m_isStopped)
303             callback(CacheStorageConnection::exceptionOrResult(!records.isEmpty(), error));
304
305         unsetPendingActivity(this);
306     });
307 }
308
309 static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody)
310 {
311     // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
312     ResourceResponse cachedResponse = response.resourceResponse();
313     for (auto& header : response.headers().internalHeaders())
314         cachedResponse.setHTTPHeaderField(header.key, header.value);
315
316     ResourceRequest cachedRequest = request.internalRequest();
317     cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
318
319     ASSERT(!cachedRequest.isNull());
320     ASSERT(!cachedResponse.isNull());
321
322     return { 0,
323         request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
324         response.headers().guard(), WTFMove(cachedResponse), WTFMove(responseBody)
325     };
326 }
327
328 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, CacheStorageConnection::ResponseBody&& responseBody, WTF::Function<void(ExceptionOr<void>&&)>&& callback)
329 {
330     Vector<CacheStorageConnection::Record> records;
331     records.append(toConnectionRecord(request, response, WTFMove(responseBody)));
332
333     setPendingActivity(this);
334     m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
335         if (!m_isStopped)
336             callback(CacheStorageConnection::errorToException(error));
337
338         unsetPendingActivity(this);
339     });
340 }
341
342 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
343 {
344     ASSERT(scriptExecutionContext());
345     Vector<CacheStorageRecord> newRecords;
346
347     for (auto& record : records) {
348         size_t index = m_records.findMatching([&](const auto& item) { return item.identifier == record.identifier; });
349         if (index != notFound)
350             newRecords.append(WTFMove(m_records[index]));
351         else {
352             auto requestHeaders = FetchHeaders::create(record.requestHeadersGuard, HTTPHeaderMap { record.request.httpHeaderFields() });
353             FetchRequest::InternalRequest internalRequest = { WTFMove(record.request), WTFMove(record.options), WTFMove(record.referrer) };
354             auto request = FetchRequest::create(*scriptExecutionContext(), std::nullopt, WTFMove(requestHeaders), WTFMove(internalRequest));
355
356             auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
357             auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
358             response->setBodyData(WTFMove(record.responseBody));
359
360             newRecords.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
361         }
362     }
363     m_records = WTFMove(newRecords);
364 }
365
366 void Cache::stop()
367 {
368     m_isStopped = true;
369 }
370
371 const char* Cache::activeDOMObjectName() const
372 {
373     return "Cache";
374 }
375
376 bool Cache::canSuspendForDocumentSuspension() const
377 {
378     return m_records.isEmpty() && !hasPendingActivity();
379 }
380
381
382 } // namespace WebCore