Add Cache API support of records persistency
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / CacheStorageEngine.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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CacheStorageEngine.h"
28
29 #include "NetworkCacheIOChannel.h"
30 #include "NetworkProcess.h"
31 #include <WebCore/CacheQueryOptions.h>
32 #include <pal/SessionID.h>
33 #include <wtf/MainThread.h>
34 #include <wtf/NeverDestroyed.h>
35 #include <wtf/text/StringBuilder.h>
36 #include <wtf/text/StringHash.h>
37
38 using namespace WebCore::DOMCacheEngine;
39 using namespace WebKit::NetworkCache;
40
41 namespace WebKit {
42
43 namespace CacheStorage {
44
45 static HashMap<PAL::SessionID, RefPtr<Engine>>& globalEngineMap()
46 {
47     static NeverDestroyed<HashMap<PAL::SessionID, RefPtr<Engine>>> map;
48
49     return map;
50 }
51
52 Engine::~Engine()
53 {
54     for (auto& caches : m_caches.values())
55         caches->detach();
56 }
57
58 Engine& Engine::from(PAL::SessionID sessionID)
59 {
60     auto addResult = globalEngineMap().add(sessionID, nullptr);
61     if (addResult.isNewEntry)
62         addResult.iterator->value = Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID));
63     return *addResult.iterator->value;
64 }
65
66 void Engine::destroyEngine(PAL::SessionID sessionID)
67 {
68     ASSERT(sessionID != PAL::SessionID::defaultSessionID());
69     globalEngineMap().remove(sessionID);
70 }
71
72 Engine& Engine::defaultEngine()
73 {
74     auto sessionID = PAL::SessionID::defaultSessionID();
75     static NeverDestroyed<Ref<Engine>> defaultEngine = { Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID)) };
76     return defaultEngine.get();
77 }
78
79 Engine::Engine(String&& rootPath)
80     : m_rootPath(WTFMove(rootPath))
81 {
82     if (!m_rootPath.isNull())
83         m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
84 }
85
86 void Engine::open(const String& origin, const String& cacheName, CacheIdentifierCallback&& callback)
87 {
88     readCachesFromDisk(origin, [this, cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
89         if (!cachesOrError.hasValue()) {
90             callback(makeUnexpected(cachesOrError.error()));
91             return;
92         }
93
94         cachesOrError.value().get().open(cacheName, WTFMove(callback));
95     });
96 }
97
98 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
99 {
100     Caches* cachesToModify = nullptr;
101     for (auto& caches : m_caches.values()) {
102         auto* cacheToRemove = caches->find(cacheIdentifier);
103         if (cacheToRemove) {
104             cachesToModify = caches.ptr();
105             break;
106         }
107     }
108     if (!cachesToModify) {
109         callback(makeUnexpected(Error::Internal));
110         return;
111     }
112
113     cachesToModify->remove(cacheIdentifier, WTFMove(callback));
114 }
115
116 void Engine::retrieveCaches(const String& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
117 {
118     readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
119         if (!cachesOrError.hasValue()) {
120             callback(makeUnexpected(cachesOrError.error()));
121             return;
122         }
123
124         callback(cachesOrError.value().get().cacheInfos(updateCounter));
125     });
126 }
127
128 void Engine::retrieveRecords(uint64_t cacheIdentifier, WebCore::URL&& url, RecordsCallback&& callback)
129 {
130     readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
131         if (!result.hasValue()) {
132             callback(makeUnexpected(result.error()));
133             return;
134         }
135         result.value().get().retrieveRecords(url, WTFMove(callback));
136     });
137 }
138
139 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
140 {
141     readCache(cacheIdentifier, [this, records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
142         if (!result.hasValue()) {
143             callback(makeUnexpected(result.error()));
144             return;
145         }
146
147         result.value().get().put(WTFMove(records), WTFMove(callback));
148     });
149 }
150
151 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
152 {
153     readCache(cacheIdentifier, [this, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
154         if (!result.hasValue()) {
155             callback(makeUnexpected(result.error()));
156             return;
157         }
158
159         result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
160     });
161 }
162
163 void Engine::initialize(Function<void(std::optional<Error>&&)>&& callback)
164 {
165     if (m_salt) {
166         callback(std::nullopt);
167         return;
168     }
169
170     if (!shouldPersist()) {
171         callback(std::nullopt);
172         return;
173     }
174
175     String saltPath = WebCore::pathByAppendingComponent(m_rootPath, ASCIILiteral("salt"));
176     m_ioQueue->dispatch([protectedThis = makeRef(*this), this, callback = WTFMove(callback), saltPath = WTFMove(saltPath)] () mutable {
177         WebCore::makeAllDirectories(m_rootPath);
178         RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, salt = readOrMakeSalt(saltPath), callback = WTFMove(callback)]() mutable {
179             if (!salt) {
180                 callback(Error::WriteDisk);
181                 return;
182             }
183             m_salt = WTFMove(salt);
184             callback(std::nullopt);
185         });
186     });
187 }
188
189 void Engine::readCachesFromDisk(const String& origin, CachesCallback&& callback)
190 {
191     initialize([this, origin, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
192         auto& caches = m_caches.ensure(origin, [&origin, this] {
193             return Caches::create(*this, String { origin });
194         }).iterator->value;
195
196         if (caches->isInitialized()) {
197             callback(std::reference_wrapper<Caches> { caches.get() });
198             return;
199         }
200
201         if (error) {
202             callback(makeUnexpected(error.value()));
203             return;
204         }
205
206         caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](std::optional<Error>&& error) mutable {
207             if (error) {
208                 callback(makeUnexpected(error.value()));
209                 return;
210             }
211
212             callback(std::reference_wrapper<Caches> { caches.get() });
213         });
214     });
215 }
216
217 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
218 {
219     auto* cache = this->cache(cacheIdentifier);
220     if (!cache) {
221         callback(makeUnexpected(Error::Internal));
222         return;
223     }
224     if (!cache->isOpened()) {
225         cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
226             if (error) {
227                 callback(makeUnexpected(error.value()));
228                 return;
229             }
230
231             auto* cache = this->cache(cacheIdentifier);
232             if (!cache) {
233                 callback(makeUnexpected(Error::Internal));
234                 return;
235             }
236             ASSERT(cache->isOpened());
237             callback(std::reference_wrapper<Cache> { *cache });
238         });
239         return;
240     }
241     callback(std::reference_wrapper<Cache> { *cache });
242 }
243
244 Cache* Engine::cache(uint64_t cacheIdentifier)
245 {
246     Cache* result = nullptr;
247     for (auto& caches : m_caches.values()) {
248         if ((result = caches->find(cacheIdentifier)))
249             break;
250     }
251     return result;
252 }
253
254 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
255 {
256     if (!shouldPersist()) {
257         callback(std::nullopt);
258         return;
259     }
260
261     m_ioQueue->dispatch([callback = WTFMove(callback), data = WTFMove(data), filename = filename.isolatedCopy()] () mutable {
262         auto channel = IOChannel::open(filename, IOChannel::Type::Create);
263         channel->write(0, data, nullptr, [callback = WTFMove(callback)](int error) mutable {
264             ASSERT(RunLoop::isMain());
265             if (error) {
266                 callback(Error::WriteDisk);
267                 return;
268             }
269             callback(std::nullopt);
270         });
271     });
272 }
273
274 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
275 {
276     if (!shouldPersist()) {
277         callback(Data { }, 0);
278         return;
279     }
280
281     m_ioQueue->dispatch([callback = WTFMove(callback), filename = filename.isolatedCopy()]() mutable {
282         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
283         if (channel->fileDescriptor() < 0) {
284             RunLoop::main().dispatch([callback = WTFMove(callback)]() mutable {
285                 callback(Data { }, 0);
286             });
287             return;
288         }
289
290         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [callback = WTFMove(callback)](const Data& data, int error) mutable {
291             // FIXME: We should do the decoding in the background thread.
292             ASSERT(RunLoop::isMain());
293             callback(data, error);
294         });
295     });
296 }
297
298 void Engine::removeFile(const String& filename)
299 {
300     if (!shouldPersist())
301         return;
302
303     m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
304         WebCore::deleteFile(filename);
305     });
306 }
307
308 void Engine::removeCaches(const String& origin)
309 {
310     ASSERT(m_caches.contains(origin));
311     m_caches.remove(origin);
312 }
313
314 void Engine::clearMemoryRepresentation(const String& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
315 {
316     readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
317         if (!result.hasValue()) {
318             callback(result.error());
319             return;
320         }
321         result.value().get().clearMemoryRepresentation();
322         callback(std::nullopt);
323     });
324 }
325
326 void Engine::lock(uint64_t cacheIdentifier)
327 {
328     auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
329         return 0;
330     }).iterator->value;
331
332     ++counter;
333 }
334
335 void Engine::unlock(uint64_t cacheIdentifier)
336 {
337     auto lockCount = m_cacheLocks.find(cacheIdentifier);
338     ASSERT(lockCount != m_cacheLocks.end());
339     if (lockCount == m_cacheLocks.end())
340         return;
341
342     ASSERT(lockCount->value);
343     if (--lockCount->value)
344         return;
345
346     auto* cache = this->cache(cacheIdentifier);
347     if (!cache)
348         return;
349
350     cache->dispose();
351 }
352
353 String Engine::representation()
354 {
355     bool isFirst = true;
356     StringBuilder builder;
357     builder.append("[");
358     for (auto& keyValue : m_caches) {
359         if (!isFirst)
360             builder.append(",");
361         isFirst = false;
362
363         builder.append("\n{ \"origin\" : \"");
364         builder.append(keyValue.key);
365         builder.append("\", \"caches\" : ");
366         keyValue.value->appendRepresentation(builder);
367         builder.append("}");
368     }
369     builder.append("\n]");
370     return builder.toString();
371 }
372
373 } // namespace CacheStorage
374
375 } // namespace WebKit