84c4ffeeb3195a67692f64f42b005bd764398af3
[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 String& origin)
56 {
57     if (!shouldPersist())
58         return { };
59
60     Key key(origin, { }, { }, { }, salt());
61     return WebCore::FileSystem::pathByAppendingComponent(rootPath(), key.partitionHashAsString());
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 String& 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.ptr();
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 String& 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 String& 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             return Caches::create(*this, String { origin }, cachesRootPath(origin), NetworkProcess::singleton().cacheStoragePerOriginQuota());
211         }).iterator->value;
212
213         if (caches->isInitialized()) {
214             callback(std::reference_wrapper<Caches> { caches.get() });
215             return;
216         }
217
218         if (error) {
219             callback(makeUnexpected(error.value()));
220             return;
221         }
222
223         caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](std::optional<Error>&& error) mutable {
224             if (error) {
225                 callback(makeUnexpected(error.value()));
226                 return;
227             }
228
229             callback(std::reference_wrapper<Caches> { caches.get() });
230         });
231     });
232 }
233
234 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
235 {
236     auto* cache = this->cache(cacheIdentifier);
237     if (!cache) {
238         callback(makeUnexpected(Error::Internal));
239         return;
240     }
241     if (!cache->isOpened()) {
242         cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
243             if (error) {
244                 callback(makeUnexpected(error.value()));
245                 return;
246             }
247
248             auto* cache = this->cache(cacheIdentifier);
249             if (!cache) {
250                 callback(makeUnexpected(Error::Internal));
251                 return;
252             }
253             ASSERT(cache->isOpened());
254             callback(std::reference_wrapper<Cache> { *cache });
255         });
256         return;
257     }
258     callback(std::reference_wrapper<Cache> { *cache });
259 }
260
261 Cache* Engine::cache(uint64_t cacheIdentifier)
262 {
263     Cache* result = nullptr;
264     for (auto& caches : m_caches.values()) {
265         if ((result = caches->find(cacheIdentifier)))
266             break;
267     }
268     return result;
269 }
270
271 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
272 {
273     if (!shouldPersist()) {
274         callback(std::nullopt);
275         return;
276     }
277
278     m_ioQueue->dispatch([callback = WTFMove(callback), data = WTFMove(data), filename = filename.isolatedCopy()] () mutable {
279         auto channel = IOChannel::open(filename, IOChannel::Type::Create);
280         channel->write(0, data, nullptr, [callback = WTFMove(callback)](int error) mutable {
281             ASSERT(RunLoop::isMain());
282             if (error) {
283                 callback(Error::WriteDisk);
284                 return;
285             }
286             callback(std::nullopt);
287         });
288     });
289 }
290
291 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
292 {
293     if (!shouldPersist()) {
294         callback(Data { }, 0);
295         return;
296     }
297
298     m_ioQueue->dispatch([callback = WTFMove(callback), filename = filename.isolatedCopy()]() mutable {
299         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
300         if (channel->fileDescriptor() < 0) {
301             RunLoop::main().dispatch([callback = WTFMove(callback)]() mutable {
302                 callback(Data { }, 0);
303             });
304             return;
305         }
306
307         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [callback = WTFMove(callback)](const Data& data, int error) mutable {
308             // FIXME: We should do the decoding in the background thread.
309             ASSERT(RunLoop::isMain());
310             callback(data, error);
311         });
312     });
313 }
314
315 void Engine::removeFile(const String& filename)
316 {
317     if (!shouldPersist())
318         return;
319
320     m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
321         WebCore::FileSystem::deleteFile(filename);
322     });
323 }
324
325 void Engine::removeCaches(const String& origin)
326 {
327     ASSERT(m_caches.contains(origin));
328     m_caches.remove(origin);
329 }
330
331 class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
332 public:
333     static Ref<ReadOriginsTaskCounter> create(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
334     {
335         return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback)));
336     }
337
338     ~ReadOriginsTaskCounter()
339     {
340         m_callback(WTFMove(m_entries));
341     }
342
343     void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size)
344     {
345         m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size });
346     }
347
348 private:
349     explicit ReadOriginsTaskCounter(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
350         : m_callback(WTFMove(callback))
351     {
352     }
353
354     WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
355     Vector<WebsiteData::Entry> m_entries;
356 };
357
358 void Engine::fetchEntries(bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
359 {
360     if (!shouldPersist()) {
361         auto entries = WTF::map(m_caches, [] (auto& pair) {
362             return WebsiteData::Entry { pair.value->origin(), WebsiteDataType::DOMCache, 0 };
363         });
364         completionHandler(WTFMove(entries));
365         return;
366     }
367
368     auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler));
369     for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
370         if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
371             continue;
372         Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (std::optional<WebCore::SecurityOriginData>&& origin) mutable {
373             ASSERT(RunLoop::isMain());
374             if (!origin)
375                 return;
376
377             if (!shouldComputeSize) {
378                 taskCounter->addOrigin(WTFMove(origin.value()), 0);
379                 return;
380             }
381
382             auto cacheOrigin = origin->securityOrigin()->toString();
383             protectedThis->readCachesFromDisk(cacheOrigin, [origin = WTFMove(origin.value()), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable {
384                 if (!result.has_value())
385                     return;
386                 taskCounter->addOrigin(WTFMove(origin), result.value().get().storageSize());
387
388             });
389         });
390     }
391 }
392
393 void Engine::clearAllCaches(CallbackAggregator& taskHandler)
394 {
395     for (auto& caches : m_caches.values())
396         caches->clear([taskHandler = makeRef(taskHandler)] { });
397
398     if (!shouldPersist())
399         return;
400
401     m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), taskHandler = makeRef(taskHandler)] {
402         for (auto& filename : WebCore::FileSystem::listDirectory(path, "*")) {
403             if (WebCore::FileSystem::fileIsDirectory(filename, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
404                 deleteDirectoryRecursively(filename);
405         }
406     });
407 }
408
409 void Engine::clearCachesForOrigin(const String& origin, CallbackAggregator& taskHandler)
410 {
411     if (auto caches = m_caches.get(origin)) {
412         caches->clear([taskHandler = makeRef(taskHandler)] { });
413         return;
414     }
415
416     if (!shouldPersist())
417         return;
418
419     m_ioQueue->dispatch([filename = cachesRootPath(origin), taskHandler = makeRef(taskHandler)] {
420         deleteDirectoryRecursively(filename);
421     });
422 }
423
424 void Engine::clearMemoryRepresentation(const String& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
425 {
426     readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
427         if (!result.has_value()) {
428             callback(result.error());
429             return;
430         }
431         result.value().get().clearMemoryRepresentation();
432         callback(std::nullopt);
433     });
434 }
435
436 void Engine::lock(uint64_t cacheIdentifier)
437 {
438     auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
439         return 0;
440     }).iterator->value;
441
442     ++counter;
443 }
444
445 void Engine::unlock(uint64_t cacheIdentifier)
446 {
447     auto lockCount = m_cacheLocks.find(cacheIdentifier);
448     if (lockCount == m_cacheLocks.end())
449         return;
450
451     ASSERT(lockCount->value);
452     if (--lockCount->value)
453         return;
454
455     auto* cache = this->cache(cacheIdentifier);
456     if (!cache)
457         return;
458
459     cache->dispose();
460 }
461
462 String Engine::representation()
463 {
464     bool isFirst = true;
465     StringBuilder builder;
466     builder.append("[");
467     for (auto& keyValue : m_caches) {
468         if (!isFirst)
469             builder.append(",");
470         isFirst = false;
471
472         builder.append("\n{ \"origin\" : \"");
473         builder.append(keyValue.key);
474         builder.append("\", \"caches\" : ");
475         keyValue.value->appendRepresentation(builder);
476         builder.append("}");
477     }
478     builder.append("\n]");
479     return builder.toString();
480 }
481
482 } // namespace CacheStorage
483
484 } // namespace WebKit