Add Cache Storage engine assertion following on bug 181887
[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 "NetworkCacheFileSystem.h"
30 #include "NetworkCacheIOChannel.h"
31 #include "NetworkProcess.h"
32 #include "WebsiteDataType.h"
33 #include <WebCore/CacheQueryOptions.h>
34 #include <WebCore/SecurityOrigin.h>
35 #include <pal/SessionID.h>
36 #include <wtf/CallbackAggregator.h>
37 #include <wtf/NeverDestroyed.h>
38 #include <wtf/text/StringBuilder.h>
39 #include <wtf/text/StringHash.h>
40
41 using namespace WebCore::DOMCacheEngine;
42 using namespace WebKit::NetworkCache;
43
44 namespace WebKit {
45
46 namespace CacheStorage {
47
48 static HashMap<PAL::SessionID, RefPtr<Engine>>& globalEngineMap()
49 {
50     static NeverDestroyed<HashMap<PAL::SessionID, RefPtr<Engine>>> map;
51
52     return map;
53 }
54
55 String Engine::cachesRootPath(const WebCore::ClientOrigin& origin)
56 {
57     if (!shouldPersist())
58         return { };
59
60     Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt());
61     return WebCore::FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString());
62 }
63
64 Engine::~Engine()
65 {
66     for (auto& caches : m_caches.values())
67         caches->detach();
68 }
69
70 Engine& Engine::from(PAL::SessionID sessionID)
71 {
72     auto addResult = globalEngineMap().add(sessionID, nullptr);
73     if (addResult.isNewEntry)
74         addResult.iterator->value = Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID));
75     return *addResult.iterator->value;
76 }
77
78 void Engine::destroyEngine(PAL::SessionID sessionID)
79 {
80     ASSERT(sessionID != PAL::SessionID::defaultSessionID());
81     globalEngineMap().remove(sessionID);
82 }
83
84 void Engine::fetchEntries(PAL::SessionID sessionID, bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
85 {
86     from(sessionID).fetchEntries(shouldComputeSize, WTFMove(completionHandler));
87 }
88
89 Engine& Engine::defaultEngine()
90 {
91     auto sessionID = PAL::SessionID::defaultSessionID();
92     static NeverDestroyed<Ref<Engine>> defaultEngine = { Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID)) };
93     return defaultEngine.get();
94 }
95
96 Engine::Engine(String&& rootPath)
97     : m_rootPath(WTFMove(rootPath))
98 {
99     if (!m_rootPath.isNull())
100         m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
101 }
102
103 void Engine::open(const WebCore::ClientOrigin& origin, const String& cacheName, CacheIdentifierCallback&& callback)
104 {
105     readCachesFromDisk(origin, [cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
106         if (!cachesOrError.has_value()) {
107             callback(makeUnexpected(cachesOrError.error()));
108             return;
109         }
110
111         cachesOrError.value().get().open(cacheName, WTFMove(callback));
112     });
113 }
114
115 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
116 {
117     Caches* cachesToModify = nullptr;
118     for (auto& caches : m_caches.values()) {
119         auto* cacheToRemove = caches->find(cacheIdentifier);
120         if (cacheToRemove) {
121             cachesToModify = caches.get();
122             break;
123         }
124     }
125     if (!cachesToModify) {
126         callback(makeUnexpected(Error::Internal));
127         return;
128     }
129
130     cachesToModify->remove(cacheIdentifier, WTFMove(callback));
131 }
132
133 void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
134 {
135     readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
136         if (!cachesOrError.has_value()) {
137             callback(makeUnexpected(cachesOrError.error()));
138             return;
139         }
140
141         callback(cachesOrError.value().get().cacheInfos(updateCounter));
142     });
143 }
144
145 void Engine::retrieveRecords(uint64_t cacheIdentifier, WebCore::URL&& url, RecordsCallback&& callback)
146 {
147     readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
148         if (!result.has_value()) {
149             callback(makeUnexpected(result.error()));
150             return;
151         }
152         result.value().get().retrieveRecords(url, WTFMove(callback));
153     });
154 }
155
156 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
157 {
158     readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
159         if (!result.has_value()) {
160             callback(makeUnexpected(result.error()));
161             return;
162         }
163
164         result.value().get().put(WTFMove(records), WTFMove(callback));
165     });
166 }
167
168 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
169 {
170     readCache(cacheIdentifier, [request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
171         if (!result.has_value()) {
172             callback(makeUnexpected(result.error()));
173             return;
174         }
175
176         result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
177     });
178 }
179
180 void Engine::initialize(Function<void(std::optional<Error>&&)>&& callback)
181 {
182     if (m_salt) {
183         callback(std::nullopt);
184         return;
185     }
186
187     if (!shouldPersist()) {
188         callback(std::nullopt);
189         return;
190     }
191
192     String saltPath = WebCore::FileSystem::pathByAppendingComponent(m_rootPath, ASCIILiteral("salt"));
193     m_ioQueue->dispatch([protectedThis = makeRef(*this), this, callback = WTFMove(callback), saltPath = WTFMove(saltPath)] () mutable {
194         WebCore::FileSystem::makeAllDirectories(m_rootPath);
195         RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, salt = readOrMakeSalt(saltPath), callback = WTFMove(callback)]() mutable {
196             if (!salt) {
197                 callback(Error::WriteDisk);
198                 return;
199             }
200             m_salt = WTFMove(salt);
201             callback(std::nullopt);
202         });
203     });
204 }
205
206 void Engine::readCachesFromDisk(const WebCore::ClientOrigin& origin, CachesCallback&& callback)
207 {
208     initialize([this, origin, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
209         auto& caches = m_caches.ensure(origin, [&origin, this] {
210             auto path = cachesRootPath(origin);
211             return Caches::create(*this, WebCore::ClientOrigin { origin }, WTFMove(path), NetworkProcess::singleton().cacheStoragePerOriginQuota());
212         }).iterator->value;
213
214         if (caches->isInitialized()) {
215             callback(std::reference_wrapper<Caches> { *caches });
216             return;
217         }
218
219         if (error) {
220             callback(makeUnexpected(error.value()));
221             return;
222         }
223
224         caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](std::optional<Error>&& error) mutable {
225             if (error) {
226                 callback(makeUnexpected(error.value()));
227                 return;
228             }
229
230             callback(std::reference_wrapper<Caches> { *caches });
231         });
232     });
233 }
234
235 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
236 {
237     auto* cache = this->cache(cacheIdentifier);
238     if (!cache) {
239         callback(makeUnexpected(Error::Internal));
240         return;
241     }
242     if (!cache->isOpened()) {
243         cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
244             if (error) {
245                 callback(makeUnexpected(error.value()));
246                 return;
247             }
248
249             auto* cache = this->cache(cacheIdentifier);
250             if (!cache) {
251                 callback(makeUnexpected(Error::Internal));
252                 return;
253             }
254             ASSERT(cache->isOpened());
255             callback(std::reference_wrapper<Cache> { *cache });
256         });
257         return;
258     }
259     callback(std::reference_wrapper<Cache> { *cache });
260 }
261
262 Cache* Engine::cache(uint64_t cacheIdentifier)
263 {
264     Cache* result = nullptr;
265     for (auto& caches : m_caches.values()) {
266         if ((result = caches->find(cacheIdentifier)))
267             break;
268     }
269     return result;
270 }
271
272 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
273 {
274     if (!shouldPersist()) {
275         callback(std::nullopt);
276         return;
277     }
278
279     m_ioQueue->dispatch([callback = WTFMove(callback), data = WTFMove(data), filename = filename.isolatedCopy()] () mutable {
280         auto channel = IOChannel::open(filename, IOChannel::Type::Create);
281         channel->write(0, data, nullptr, [callback = WTFMove(callback)](int error) mutable {
282             ASSERT(RunLoop::isMain());
283             if (error) {
284                 callback(Error::WriteDisk);
285                 return;
286             }
287             callback(std::nullopt);
288         });
289     });
290 }
291
292 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
293 {
294     if (!shouldPersist()) {
295         callback(Data { }, 0);
296         return;
297     }
298
299     m_ioQueue->dispatch([callback = WTFMove(callback), filename = filename.isolatedCopy()]() mutable {
300         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
301         if (channel->fileDescriptor() < 0) {
302             RunLoop::main().dispatch([callback = WTFMove(callback)]() mutable {
303                 callback(Data { }, 0);
304             });
305             return;
306         }
307
308         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [callback = WTFMove(callback)](const Data& data, int error) mutable {
309             // FIXME: We should do the decoding in the background thread.
310             ASSERT(RunLoop::isMain());
311             callback(data, error);
312         });
313     });
314 }
315
316 void Engine::removeFile(const String& filename)
317 {
318     if (!shouldPersist())
319         return;
320
321     m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
322         WebCore::FileSystem::deleteFile(filename);
323     });
324 }
325
326 class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
327 public:
328     static Ref<ReadOriginsTaskCounter> create(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
329     {
330         return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback)));
331     }
332
333     ~ReadOriginsTaskCounter()
334     {
335         m_callback(WTFMove(m_entries));
336     }
337
338     void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size)
339     {
340         m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size });
341     }
342
343 private:
344     explicit ReadOriginsTaskCounter(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
345         : m_callback(WTFMove(callback))
346     {
347     }
348
349     WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
350     Vector<WebsiteData::Entry> m_entries;
351 };
352
353 void Engine::fetchEntries(bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
354 {
355     if (!shouldPersist()) {
356         auto entries = WTF::map(m_caches, [] (auto& pair) {
357             return WebsiteData::Entry { pair.value->origin().clientOrigin, WebsiteDataType::DOMCache, 0 };
358         });
359         completionHandler(WTFMove(entries));
360         return;
361     }
362
363     auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler));
364     for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
365         if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
366             continue;
367         Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (auto&& origin) mutable {
368             ASSERT(RunLoop::isMain());
369             if (!origin)
370                 return;
371
372             if (!shouldComputeSize) {
373                 taskCounter->addOrigin(WTFMove(origin->topOrigin), 0);
374                 taskCounter->addOrigin(WTFMove(origin->clientOrigin), 0);
375                 return;
376             }
377
378             protectedThis->readCachesFromDisk(origin.value(), [origin = origin.value(), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable {
379                 if (!result.has_value())
380                     return;
381                 taskCounter->addOrigin(WTFMove(origin.topOrigin), 0);
382                 taskCounter->addOrigin(WTFMove(origin.clientOrigin), result.value().get().storageSize());
383             });
384         });
385     }
386 }
387
388 void Engine::clearAllCaches(CallbackAggregator& taskHandler)
389 {
390     for (auto& caches : m_caches.values())
391         caches->clear([taskHandler = makeRef(taskHandler)] { });
392
393     if (!shouldPersist())
394         return;
395
396     m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), taskHandler = makeRef(taskHandler)] {
397         for (auto& filename : WebCore::FileSystem::listDirectory(path, "*")) {
398             if (WebCore::FileSystem::fileIsDirectory(filename, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
399                 deleteDirectoryRecursively(filename);
400         }
401     });
402 }
403
404 void Engine::clearCachesForOrigin(const WebCore::SecurityOriginData& origin, CallbackAggregator& taskHandler)
405 {
406     for (auto& keyValue : m_caches) {
407         if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin)
408             keyValue.value->clear([taskHandler = makeRef(taskHandler)] { });
409     }
410
411     if (!shouldPersist())
412         return;
413
414     for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
415         if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
416             continue;
417         Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [this, protectedThis = makeRef(*this), origin, taskHandler = makeRef(taskHandler), folderPath] (std::optional<WebCore::ClientOrigin>&& folderOrigin) mutable {
418             if (!folderOrigin)
419                 return;
420             if (folderOrigin->topOrigin != origin && folderOrigin->clientOrigin != origin)
421                 return;
422
423             ASSERT(folderPath == cachesRootPath(*folderOrigin));
424             m_ioQueue->dispatch([path = folderPath.isolatedCopy(), taskHandler = WTFMove(taskHandler)] {
425                 deleteDirectoryRecursively(path);
426             });
427         });
428     }
429 }
430
431 void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
432 {
433     readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
434         if (!result.has_value()) {
435             callback(result.error());
436             return;
437         }
438         result.value().get().clearMemoryRepresentation();
439         callback(std::nullopt);
440     });
441 }
442
443 void Engine::lock(uint64_t cacheIdentifier)
444 {
445     auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
446         return 0;
447     }).iterator->value;
448
449     ++counter;
450 }
451
452 void Engine::unlock(uint64_t cacheIdentifier)
453 {
454     auto lockCount = m_cacheLocks.find(cacheIdentifier);
455     if (lockCount == m_cacheLocks.end())
456         return;
457
458     ASSERT(lockCount->value);
459     if (--lockCount->value)
460         return;
461
462     auto* cache = this->cache(cacheIdentifier);
463     if (!cache)
464         return;
465
466     cache->dispose();
467 }
468
469 String Engine::representation()
470 {
471     bool isFirst = true;
472     StringBuilder builder;
473     builder.append("[");
474     for (auto& keyValue : m_caches) {
475         if (!isFirst)
476             builder.append(",");
477         isFirst = false;
478
479         builder.append("\n{ \"origin\" : { \"topOrigin\" : \"");
480         builder.append(keyValue.key.topOrigin.toString());
481         builder.append("\", \"clientOrigin\": \"");
482         builder.append(keyValue.key.clientOrigin.toString());
483         builder.append("\" }, \"caches\" : ");
484         keyValue.value->appendRepresentation(builder);
485         builder.append("}");
486     }
487     builder.append("\n]");
488     return builder.toString();
489 }
490
491 } // namespace CacheStorage
492
493 } // namespace WebKit