37ee858f37d385c27db5d5d49b349bd5ddacbdda
[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)](CacheStorageConnection::Error error) mutable {
176         if (error != CacheStorageConnection::Error::None) {
177             promise.reject(CacheStorageConnection::exceptionFromError(error));
178             return;
179         }
180         promise.resolve();
181     });
182 }
183
184 void Cache::remove(RequestInfo&& info, CacheQueryOptions&& options, DOMPromiseDeferred<IDLBoolean>&& promise)
185 {
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);
191             return;
192         }
193     } else {
194         if (UNLIKELY(!scriptExecutionContext()))
195             return;
196         request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info), { }).releaseReturnValue();
197     }
198
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));
202             return;
203         }
204         promise.resolve(didDelete);
205     });
206 }
207
208 void Cache::keys(std::optional<RequestInfo>&& info, CacheQueryOptions&& options, KeysPromise&& promise)
209 {
210     RefPtr<FetchRequest> request;
211     if (info) {
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>> { });
216                 return;
217             }
218         } else {
219             if (UNLIKELY(!scriptExecutionContext()))
220                 return;
221             request = FetchRequest::create(*scriptExecutionContext(), WTFMove(info.value()), { }).releaseReturnValue();
222         }
223     }
224
225     if (!request) {
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);
232         });
233         return;
234     }
235
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);
242     });
243 }
244
245 void Cache::retrieveRecords(WTF::Function<void()>&& callback)
246 {
247     setPendingActivity(this);
248     m_connection->retrieveRecords(m_identifier, [this, callback = WTFMove(callback)](Vector<CacheStorageConnection::Record>&& records) {
249         if (!m_isStopped) {
250             updateRecords(WTFMove(records));
251             callback();
252         }
253         unsetPendingActivity(this);
254     });
255 }
256
257 void Cache::queryCache(Ref<FetchRequest>&& request, CacheQueryOptions&& options, WTF::Function<void(const Vector<CacheStorageRecord>&)>&& callback)
258 {
259     retrieveRecords([this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)]() mutable {
260         callback(queryCacheWithTargetStorage(request.get(), options, m_records));
261     });
262 }
263
264 static inline bool queryCacheMatch(const FetchRequest& request, const FetchRequest& cachedRequest, const ResourceResponse& cachedResponse, const CacheQueryOptions& options)
265 {
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);
268 }
269
270 Vector<CacheStorageRecord> Cache::queryCacheWithTargetStorage(const FetchRequest& request, const CacheQueryOptions& options, const Vector<CacheStorageRecord>& targetStorage)
271 {
272     if (!options.ignoreMethod && request.method() != "GET")
273         return { };
274
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() });
279     }
280     return records;
281 }
282
283 void Cache::batchDeleteOperation(const FetchRequest& request, CacheQueryOptions&& options, WTF::Function<void(bool didRemoval, CacheStorageConnection::Error error)>&& callback)
284 {
285     setPendingActivity(this);
286     m_connection->batchDeleteOperation(m_identifier, request.internalRequest(), WTFMove(options), [this, callback = WTFMove(callback)](Vector<uint64_t>&& records, CacheStorageConnection::Error error) {
287         if (!m_isStopped) {
288             if (error == CacheStorageConnection::Error::None)
289                 m_records.removeAllMatching([&](const auto& item) { return records.contains(item.identifier); });
290
291             callback(!records.isEmpty(), error);
292         }
293         unsetPendingActivity(this);
294     });
295 }
296
297 static inline CacheStorageConnection::Record toConnectionRecord(const FetchRequest& request, FetchResponse& response)
298 {
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);
303
304     ResourceRequest cachedRequest = request.internalRequest();
305     cachedRequest.setHTTPHeaderFields(request.headers().internalHeaders());
306
307     return { 0,
308         request.headers().guard(), WTFMove(cachedRequest), request.fetchOptions(), request.internalRequestReferrer(),
309         response.headers().guard(), WTFMove(cachedResponse)
310     };
311 }
312
313 void Cache::batchPutOperation(const FetchRequest& request, FetchResponse& response, WTF::Function<void(CacheStorageConnection::Error)>&& callback)
314 {
315     Vector<CacheStorageConnection::Record> records;
316     records.append(toConnectionRecord(request, response));
317
318     setPendingActivity(this);
319     m_connection->batchPutOperation(m_identifier, WTFMove(records), [this, callback = WTFMove(callback)](Vector<uint64_t>&&, CacheStorageConnection::Error error) {
320         if (!m_isStopped)
321             callback(error);
322
323         unsetPendingActivity(this);
324     });
325 }
326
327 void Cache::updateRecords(Vector<CacheStorageConnection::Record>&& records)
328 {
329     ASSERT(scriptExecutionContext());
330     Vector<CacheStorageRecord> newRecords;
331
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]));
336         else {
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));
340
341             auto responseHeaders = FetchHeaders::create(record.responseHeadersGuard, HTTPHeaderMap { record.response.httpHeaderFields() });
342             auto response = FetchResponse::create(*scriptExecutionContext(), std::nullopt, WTFMove(responseHeaders), WTFMove(record.response));
343
344             newRecords.append(CacheStorageRecord { record.identifier, WTFMove(request), WTFMove(response) });
345         }
346     }
347     m_records = WTFMove(newRecords);
348 }
349
350 void Cache::stop()
351 {
352     m_isStopped = true;
353 }
354
355 const char* Cache::activeDOMObjectName() const
356 {
357     return "Cache";
358 }
359
360 bool Cache::canSuspendForDocumentSuspension() const
361 {
362     return m_records.isEmpty() && !hasPendingActivity();
363 }
364
365
366 } // namespace WebCore