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