[Cache API] Support cache names 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/StringHash.h>
36
37 using namespace WebCore::DOMCache;
38 using namespace WebKit::NetworkCache;
39
40 namespace WebKit {
41
42 namespace CacheStorage {
43
44 static HashMap<PAL::SessionID, RefPtr<Engine>>& globalEngineMap()
45 {
46     static NeverDestroyed<HashMap<PAL::SessionID, RefPtr<Engine>>> map;
47
48     return map;
49 }
50
51 Engine& Engine::from(PAL::SessionID sessionID)
52 {
53     auto addResult = globalEngineMap().add(sessionID, nullptr);
54     if (addResult.isNewEntry)
55         addResult.iterator->value = Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID));
56     return *addResult.iterator->value;
57 }
58
59 void Engine::destroyEngine(PAL::SessionID sessionID)
60 {
61     ASSERT(sessionID != PAL::SessionID::defaultSessionID());
62     globalEngineMap().remove(sessionID);
63 }
64
65 Engine& Engine::defaultEngine()
66 {
67     auto sessionID = PAL::SessionID::defaultSessionID();
68     static NeverDestroyed<Ref<Engine>> defaultEngine = { Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID)) };
69     return defaultEngine.get();
70 }
71
72 Engine::Engine(String&& rootPath)
73     : m_rootPath(WTFMove(rootPath))
74 {
75     if (!m_rootPath.isNull())
76         m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
77 }
78
79 void Engine::open(const String& origin, const String& cacheName, CacheIdentifierCallback&& callback)
80 {
81     readCachesFromDisk(origin, [this, cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
82         if (!cachesOrError.hasValue()) {
83             callback(makeUnexpected(cachesOrError.error()));
84             return;
85         }
86
87         Caches& caches = cachesOrError.value();
88
89         if (auto* cache = caches.find(cacheName)) {
90             callback(cache->identifier);
91             return;
92         }
93
94         caches.open(String { cacheName }, [callback = WTFMove(callback)](const CacheIdentifierOrError& result) mutable {
95             callback(result);
96         });
97     });
98 }
99
100 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
101 {
102     Caches* cachesToModify = nullptr;
103     for (auto& caches : m_caches.values()) {
104         auto* cacheToRemove = caches->find(cacheIdentifier);
105         if (cacheToRemove) {
106             cachesToModify = caches.ptr();
107             break;
108         }
109     }
110     if (!cachesToModify) {
111         auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier == cacheIdentifier; });
112         if (position == notFound) {
113             callback(makeUnexpected(Error::Internal));
114             return;
115         }
116         callback(cacheIdentifier);
117         return;
118     }
119
120     cachesToModify->remove(cacheIdentifier, [cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) {
121         if (error) {
122             callback(makeUnexpected(error.value()));
123             return;
124         }
125         callback(cacheIdentifier);
126     });
127 }
128
129 void Engine::retrieveCaches(const String& origin, CacheInfosCallback&& callback)
130 {
131     readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
132         if (!cachesOrError.hasValue()) {
133             callback(makeUnexpected(cachesOrError.error()));
134             return;
135         }
136
137         callback(cachesOrError.value().get().cacheInfos());
138     });
139 }
140
141 void Engine::retrieveRecords(uint64_t cacheIdentifier, RecordsCallback&& callback)
142 {
143     readCache(cacheIdentifier, [callback = WTFMove(callback)](CacheOrError&& result) mutable {
144         if (!result.hasValue()) {
145             callback(makeUnexpected(result.error()));
146             return;
147         }
148         // FIXME: Pass records by reference.
149         auto& records = result.value().get().records;
150
151         Vector<Record> copy;
152         copy.reserveInitialCapacity(records.size());
153         for (auto& record : records)
154             copy.uncheckedAppend(record.copy());
155
156         callback(WTFMove(copy));
157     });
158 }
159
160 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
161 {
162     readCache(cacheIdentifier, [this, cacheIdentifier, records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
163         if (!result.hasValue()) {
164             callback(makeUnexpected(result.error()));
165             return;
166         }
167
168         Cache& cache = result.value();
169
170         WebCore::CacheQueryOptions options;
171         Vector<uint64_t> recordIdentifiers;
172         recordIdentifiers.reserveInitialCapacity(records.size());
173         for (auto& record : records) {
174             auto matchingRecords = Engine::queryCache(cache.records, record.request, options);
175             if (matchingRecords.isEmpty()) {
176                 record.identifier = ++cache.nextRecordIdentifier;
177                 recordIdentifiers.uncheckedAppend(record.identifier);
178                 cache.records.append(WTFMove(record));
179             } else {
180                 auto identifier = matchingRecords[0];
181                 auto position = cache.records.findMatching([&](const auto& item) { return item.identifier == identifier; });
182                 ASSERT(position != notFound);
183                 if (position != notFound) {
184                     auto& existingRecord = cache.records[position];
185                     recordIdentifiers.uncheckedAppend(identifier);
186                     existingRecord.responseHeadersGuard = record.responseHeadersGuard;
187                     existingRecord.response = WTFMove(record.response);
188                     existingRecord.responseBody = WTFMove(record.responseBody);
189                     ++existingRecord.updateResponseCounter;
190                 }
191             }
192         }
193         writeCacheRecords(cacheIdentifier, WTFMove(recordIdentifiers), [callback = WTFMove(callback)](RecordIdentifiersOrError&& result) mutable {
194             callback(WTFMove(result));
195         });
196     });
197 }
198
199 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
200 {
201     readCache(cacheIdentifier, [this, cacheIdentifier, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
202         if (!result.hasValue()) {
203             callback(makeUnexpected(result.error()));
204             return;
205         }
206
207         auto& currentRecords = result.value().get().records;
208
209         auto recordsToRemove = queryCache(currentRecords, request, options);
210         if (recordsToRemove.isEmpty()) {
211             callback({ });
212             return;
213         }
214
215         Vector<Record> recordsToKeep;
216         for (auto& record : currentRecords) {
217             if (recordsToRemove.findMatching([&](auto item) { return item == record.identifier; }) == notFound)
218                 recordsToKeep.append(record.copy());
219         }
220         removeCacheRecords(cacheIdentifier, WTFMove(recordsToRemove), [this, cacheIdentifier, recordsToKeep = WTFMove(recordsToKeep), callback = WTFMove(callback)](RecordIdentifiersOrError&& result) mutable {
221             if (!result.hasValue()) {
222                 callback(makeUnexpected(result.error()));
223                 return;
224             }
225
226             auto* writtenCache = cache(cacheIdentifier);
227             if (!writtenCache) {
228                 callback(makeUnexpected(Error::Internal));
229                 return;
230             }
231             writtenCache->records = WTFMove(recordsToKeep);
232
233             callback(WTFMove(result.value()));
234         });
235     });
236 }
237
238 void Engine::initialize(Function<void(std::optional<Error>&&)>&& callback)
239 {
240     if (m_salt) {
241         callback(std::nullopt);
242         return;
243     }
244
245     if (!shouldPersist()) {
246         m_salt = makeSalt();
247         callback(std::nullopt);
248         return;
249     }
250
251     String saltPath = WebCore::pathByAppendingComponent(m_rootPath, ASCIILiteral("salt"));
252     m_ioQueue->dispatch([protectedThis = makeRef(*this), this, callback = WTFMove(callback), saltPath = WTFMove(saltPath)] () mutable {
253         WebCore::makeAllDirectories(m_rootPath);
254         RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, salt = readOrMakeSalt(saltPath), callback = WTFMove(callback)]() mutable {
255             if (!salt) {
256                 callback(Error::WriteDisk);
257                 return;
258             }
259             m_salt = WTFMove(salt);
260             callback(std::nullopt);
261         });
262     });
263 }
264
265 void Engine::readCachesFromDisk(const String& origin, CachesCallback&& callback)
266 {
267     auto& caches = m_caches.ensure(origin, [&origin, this] {
268         return Caches::create(*this, origin);
269     }).iterator->value;
270
271     if (caches->isInitialized()) {
272         callback(std::reference_wrapper<Caches> { caches.get() });
273         return;
274     }
275
276     initialize([this, origin, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
277         if (error) {
278             callback(makeUnexpected(error.value()));
279             return;
280         }
281
282         auto caches = m_caches.get(origin);
283         ASSERT(caches);
284
285         caches->initialize([callback = WTFMove(callback), caches](std::optional<Error>&& error) mutable {
286             if (error) {
287                 callback(makeUnexpected(error.value()));
288                 return;
289             }
290
291             callback(std::reference_wrapper<Caches> { *caches });
292         });
293     });
294 }
295
296 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
297 {
298     // FIXME: Implement reading.
299     auto* cache = this->cache(cacheIdentifier);
300     if (!cache) {
301         callback(makeUnexpected(Error::Internal));
302         return;
303     }
304     callback(std::reference_wrapper<Cache> { *cache });
305 }
306
307 void Engine::writeCacheRecords(uint64_t cacheIdentifier, Vector<uint64_t>&& recordsIdentifiers, RecordIdentifiersCallback&& callback)
308 {
309     // FIXME: Implement writing.
310     callback(WTFMove(recordsIdentifiers));
311 }
312
313 void Engine::removeCacheRecords(uint64_t cacheIdentifier, Vector<uint64_t>&& recordsIdentifiers, RecordIdentifiersCallback&& callback)
314 {
315     // FIXME: Implement writing.
316     callback(WTFMove(recordsIdentifiers));
317 }
318
319 Cache* Engine::cache(uint64_t cacheIdentifier)
320 {
321     Cache* result = nullptr;
322     for (auto& caches : m_caches.values()) {
323         if ((result = caches->find(cacheIdentifier)))
324             break;
325     }
326     if (!result) {
327         auto position = m_removedCaches.findMatching([&](const auto& item) { return item.identifier == cacheIdentifier; });
328         if (position != notFound)
329             result = &m_removedCaches[position];
330     }
331     return result;
332 }
333
334 Vector<uint64_t> Engine::queryCache(const Vector<Record>& records, const WebCore::ResourceRequest& request, const WebCore::CacheQueryOptions& options)
335 {
336     if (!options.ignoreMethod && request.httpMethod() != "GET")
337         return { };
338
339     Vector<uint64_t> results;
340     for (const auto& record : records) {
341         if (WebCore::DOMCache::queryCacheMatch(request, record.request, record.response, options))
342             results.append(record.identifier);
343     }
344     return results;
345 }
346
347 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCache::CompletionCallback&& callback)
348 {
349     if (!shouldPersist()) {
350         callback(std::nullopt);
351         return;
352     }
353
354     m_ioQueue->dispatch([callback = WTFMove(callback), data = WTFMove(data), filename = filename.isolatedCopy()] () mutable {
355         auto channel = IOChannel::open(filename, IOChannel::Type::Create);
356         channel->write(0, data, nullptr, [callback = WTFMove(callback)](int error) mutable {
357             ASSERT(RunLoop::isMain());
358             if (error) {
359                 callback(Error::WriteDisk);
360                 return;
361             }
362             callback(std::nullopt);
363         });
364     });
365 }
366
367 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
368 {
369     if (!shouldPersist()) {
370         callback(Data { }, 0);
371         return;
372     }
373
374     m_ioQueue->dispatch([callback = WTFMove(callback), filename = filename.isolatedCopy()]() mutable {
375         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
376         if (channel->fileDescriptor() < 0) {
377             RunLoop::main().dispatch([callback = WTFMove(callback)]() mutable {
378                 callback(Data { }, 0);
379             });
380             return;
381         }
382
383         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [callback = WTFMove(callback)](const Data& data, int error) mutable {
384             // FIXME: We should do the decoding in the background thread.
385             ASSERT(RunLoop::isMain());
386             callback(data, error);
387         });
388     });
389 }
390
391 void Engine::removeFile(const String& filename)
392 {
393     if (!shouldPersist())
394         return;
395
396     m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
397         WebCore::deleteFile(filename);
398     });
399 }
400
401 void Engine::clearMemoryRepresentation(const String& origin)
402 {
403     readCachesFromDisk(origin, [](CachesOrError&& result) {
404         if (!result.hasValue())
405             return;
406         result.value().get().clearMemoryRepresentation();
407     });
408 }
409
410 } // namespace CacheStorage
411
412 } // namespace WebKit