Implement Cache API partitioning based on ClientOrigin
[WebKit-https.git] / Source / WebCore / Modules / cache / DOMCacheStorage.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 "DOMCacheStorage.h"
28
29 #include "CacheQueryOptions.h"
30 #include "ClientOrigin.h"
31 #include "JSDOMCache.h"
32 #include "JSFetchResponse.h"
33 #include "ScriptExecutionContext.h"
34
35
36 namespace WebCore {
37 using namespace WebCore::DOMCacheEngine;
38
39 DOMCacheStorage::DOMCacheStorage(ScriptExecutionContext& context, Ref<CacheStorageConnection>&& connection)
40     : ActiveDOMObject(&context)
41     , m_connection(WTFMove(connection))
42 {
43     suspendIfNeeded();
44 }
45
46 std::optional<ClientOrigin> DOMCacheStorage::origin() const
47 {
48     auto* origin = scriptExecutionContext() ? scriptExecutionContext()->securityOrigin() : nullptr;
49     if (!origin)
50         return std::nullopt;
51
52     return ClientOrigin { SecurityOriginData::fromSecurityOrigin(scriptExecutionContext()->topOrigin()), SecurityOriginData::fromSecurityOrigin(*origin) };
53 }
54
55 static void doSequentialMatch(size_t index, Vector<Ref<DOMCache>>&& caches, DOMCache::RequestInfo&& info, CacheQueryOptions&& options, DOMCache::MatchCallback&& completionHandler)
56 {
57     if (index >= caches.size()) {
58         completionHandler(nullptr);
59         return;
60     }
61
62     caches[index]->doMatch(WTFMove(info), WTFMove(options), [caches = WTFMove(caches), info, options, completionHandler = WTFMove(completionHandler), index](ExceptionOr<FetchResponse*>&& result) mutable {
63         if (result.hasException()) {
64             completionHandler(result.releaseException());
65             return;
66         }
67         if (result.returnValue()) {
68             completionHandler(result.returnValue());
69             return;
70         }
71         doSequentialMatch(++index, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
72     });
73 }
74
75 static inline void startSequentialMatch(Vector<Ref<DOMCache>>&& caches, DOMCache::RequestInfo&& info, CacheQueryOptions&& options, DOMCache::MatchCallback&& completionHandler)
76 {
77     doSequentialMatch(0, WTFMove(caches), WTFMove(info), WTFMove(options), WTFMove(completionHandler));
78 }
79
80 static inline Ref<DOMCache> copyCache(const Ref<DOMCache>& cache)
81 {
82     return cache.copyRef();
83 }
84
85 void DOMCacheStorage::match(DOMCache::RequestInfo&& info, CacheQueryOptions&& options, Ref<DeferredPromise>&& promise)
86 {
87     retrieveCaches([this, info = WTFMove(info), options = WTFMove(options), promise = WTFMove(promise)](std::optional<Exception>&& exception) mutable {
88         if (exception) {
89             promise->reject(WTFMove(exception.value()));
90             return;
91         }
92
93         if (!options.cacheName.isNull()) {
94             auto position = m_caches.findMatching([&](auto& item) { return item->name() == options.cacheName; });
95             if (position != notFound) {
96                 m_caches[position]->match(WTFMove(info), WTFMove(options), WTFMove(promise));
97                 return;
98             }
99             promise->resolve();
100             return;
101         }
102
103         setPendingActivity(this);
104         startSequentialMatch(WTF::map(m_caches, copyCache), WTFMove(info), WTFMove(options), [this, promise = WTFMove(promise)](ExceptionOr<FetchResponse*>&& result) mutable {
105             if (!m_isStopped) {
106                 if (result.hasException()) {
107                     promise->reject(result.releaseException());
108                     return;
109                 }
110                 if (!result.returnValue())
111                     promise->resolve();
112                 else
113                     promise->resolve<IDLInterface<FetchResponse>>(*result.returnValue());
114             }
115             unsetPendingActivity(this);
116         });
117     });
118 }
119
120 void DOMCacheStorage::has(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
121 {
122     retrieveCaches([this, name, promise = WTFMove(promise)](std::optional<Exception>&& exception) mutable {
123         if (exception) {
124             promise.reject(WTFMove(exception.value()));
125             return;
126         }
127         promise.resolve(m_caches.findMatching([&](auto& item) { return item->name() == name; }) != notFound);
128     });
129 }
130
131 Ref<DOMCache> DOMCacheStorage::findCacheOrCreate(CacheInfo&& info)
132 {
133    auto position = m_caches.findMatching([&] (const auto& cache) { return info.identifier == cache->identifier(); });
134    if (position != notFound)
135        return m_caches[position].copyRef();
136    return DOMCache::create(*scriptExecutionContext(), WTFMove(info.name), info.identifier, m_connection.copyRef());
137 }
138
139 void DOMCacheStorage::retrieveCaches(WTF::Function<void(std::optional<Exception>&&)>&& callback)
140 {
141     auto origin = this->origin();
142     if (!origin)
143         return;
144
145     m_connection->retrieveCaches(*origin, m_updateCounter, [this, callback = WTFMove(callback), pendingActivity = makePendingActivity(*this)](CacheInfosOrError&& result) mutable {
146         if (!m_isStopped) {
147             if (!result.has_value()) {
148                 callback(DOMCacheEngine::errorToException(result.error()));
149                 return;
150             }
151
152             auto& cachesInfo = result.value();
153
154             if (m_updateCounter != cachesInfo.updateCounter) {
155                 m_updateCounter = cachesInfo.updateCounter;
156
157                 m_caches = WTF::map(WTFMove(cachesInfo.infos), [this] (CacheInfo&& info) {
158                     return findCacheOrCreate(WTFMove(info));
159                 });
160             }
161             callback(std::nullopt);
162         }
163     });
164 }
165
166 static void logConsolePersistencyError(ScriptExecutionContext* context, const String& cacheName)
167 {
168     if (!context)
169         return;
170
171     context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, makeString("There was an error making ", cacheName, " persistent on the filesystem"));
172 }
173
174 void DOMCacheStorage::open(const String& name, DOMPromiseDeferred<IDLInterface<DOMCache>>&& promise)
175 {
176     retrieveCaches([this, name, promise = WTFMove(promise)](std::optional<Exception>&& exception) mutable {
177         if (exception) {
178             promise.reject(WTFMove(exception.value()));
179             return;
180         }
181         doOpen(name, WTFMove(promise));
182     });
183 }
184
185 void DOMCacheStorage::doOpen(const String& name, DOMPromiseDeferred<IDLInterface<DOMCache>>&& promise)
186 {
187     auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
188     if (position != notFound) {
189         auto& cache = m_caches[position];
190         promise.resolve(DOMCache::create(*scriptExecutionContext(), String { cache->name() }, cache->identifier(), m_connection.copyRef()));
191         return;
192     }
193
194     m_connection->open(*origin(), name, [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
195         if (!m_isStopped) {
196             if (!result.has_value())
197                 promise.reject(DOMCacheEngine::errorToException(result.error()));
198             else {
199                 if (result.value().hadStorageError)
200                     logConsolePersistencyError(scriptExecutionContext(), name);
201
202                 auto cache = DOMCache::create(*scriptExecutionContext(), String { name }, result.value().identifier, m_connection.copyRef());
203                 promise.resolve(cache);
204                 m_caches.append(WTFMove(cache));
205             }
206         }
207     });
208 }
209
210 void DOMCacheStorage::remove(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
211 {
212     retrieveCaches([this, name, promise = WTFMove(promise)](std::optional<Exception>&& exception) mutable {
213         if (exception) {
214             promise.reject(WTFMove(exception.value()));
215             return;
216         }
217         doRemove(name, WTFMove(promise));
218     });
219 }
220
221 void DOMCacheStorage::doRemove(const String& name, DOMPromiseDeferred<IDLBoolean>&& promise)
222 {
223     auto position = m_caches.findMatching([&](auto& item) { return item->name() == name; });
224     if (position == notFound) {
225         promise.resolve(false);
226         return;
227     }
228
229     m_connection->remove(m_caches[position]->identifier(), [this, name, promise = WTFMove(promise), pendingActivity = makePendingActivity(*this)](const CacheIdentifierOrError& result) mutable {
230         if (!m_isStopped) {
231             if (!result.has_value())
232                 promise.reject(DOMCacheEngine::errorToException(result.error()));
233             else {
234                 if (result.value().hadStorageError)
235                     logConsolePersistencyError(scriptExecutionContext(), name);
236                 promise.resolve(true);
237             }
238         }
239     });
240     m_caches.remove(position);
241 }
242
243 void DOMCacheStorage::keys(KeysPromise&& promise)
244 {
245     retrieveCaches([this, promise = WTFMove(promise)](std::optional<Exception>&& exception) mutable {
246         if (exception) {
247             promise.reject(WTFMove(exception.value()));
248             return;
249         }
250
251         promise.resolve(WTF::map(m_caches, [] (const auto& cache) {
252             return cache->name();
253         }));
254     });
255 }
256
257 void DOMCacheStorage::stop()
258 {
259     m_isStopped = true;
260 }
261
262 const char* DOMCacheStorage::activeDOMObjectName() const
263 {
264     return "CacheStorage";
265 }
266
267 bool DOMCacheStorage::canSuspendForDocumentSuspension() const
268 {
269     return !hasPendingActivity();
270 }
271
272 } // namespace WebCore