2 * Copyright (C) 2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "CacheStorageEngine.h"
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>
41 using namespace WebCore::DOMCacheEngine;
42 using namespace WebKit::NetworkCache;
46 namespace CacheStorage {
48 static HashMap<PAL::SessionID, RefPtr<Engine>>& globalEngineMap()
50 static NeverDestroyed<HashMap<PAL::SessionID, RefPtr<Engine>>> map;
55 String Engine::cachesRootPath(const WebCore::ClientOrigin& origin)
60 Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt());
61 return WebCore::FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString());
66 for (auto& caches : m_caches.values())
70 Engine& Engine::from(PAL::SessionID sessionID)
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;
78 void Engine::destroyEngine(PAL::SessionID sessionID)
80 ASSERT(sessionID != PAL::SessionID::defaultSessionID());
81 globalEngineMap().remove(sessionID);
84 void Engine::fetchEntries(PAL::SessionID sessionID, bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
86 from(sessionID).fetchEntries(shouldComputeSize, WTFMove(completionHandler));
89 Engine& Engine::defaultEngine()
91 auto sessionID = PAL::SessionID::defaultSessionID();
92 static NeverDestroyed<Ref<Engine>> defaultEngine = { Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID)) };
93 return defaultEngine.get();
96 Engine::Engine(String&& rootPath)
97 : m_rootPath(WTFMove(rootPath))
99 if (!m_rootPath.isNull())
100 m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
103 void Engine::open(const WebCore::ClientOrigin& origin, const String& cacheName, CacheIdentifierCallback&& callback)
105 readCachesFromDisk(origin, [cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
106 if (!cachesOrError.has_value()) {
107 callback(makeUnexpected(cachesOrError.error()));
111 cachesOrError.value().get().open(cacheName, WTFMove(callback));
115 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
117 Caches* cachesToModify = nullptr;
118 for (auto& caches : m_caches.values()) {
119 auto* cacheToRemove = caches->find(cacheIdentifier);
121 cachesToModify = caches.get();
125 if (!cachesToModify) {
126 callback(makeUnexpected(Error::Internal));
130 cachesToModify->remove(cacheIdentifier, WTFMove(callback));
133 void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
135 readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
136 if (!cachesOrError.has_value()) {
137 callback(makeUnexpected(cachesOrError.error()));
141 callback(cachesOrError.value().get().cacheInfos(updateCounter));
145 void Engine::retrieveRecords(uint64_t cacheIdentifier, WebCore::URL&& url, RecordsCallback&& callback)
147 readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
148 if (!result.has_value()) {
149 callback(makeUnexpected(result.error()));
152 result.value().get().retrieveRecords(url, WTFMove(callback));
156 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
158 readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
159 if (!result.has_value()) {
160 callback(makeUnexpected(result.error()));
164 result.value().get().put(WTFMove(records), WTFMove(callback));
168 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
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()));
176 result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
180 void Engine::initialize(Function<void(std::optional<Error>&&)>&& callback)
183 callback(std::nullopt);
187 if (!shouldPersist()) {
188 callback(std::nullopt);
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 {
197 callback(Error::WriteDisk);
200 m_salt = WTFMove(salt);
201 callback(std::nullopt);
206 void Engine::readCachesFromDisk(const WebCore::ClientOrigin& origin, CachesCallback&& callback)
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());
214 if (caches->isInitialized()) {
215 callback(std::reference_wrapper<Caches> { *caches });
220 callback(makeUnexpected(error.value()));
224 caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](std::optional<Error>&& error) mutable {
226 callback(makeUnexpected(error.value()));
230 callback(std::reference_wrapper<Caches> { *caches });
235 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
237 auto* cache = this->cache(cacheIdentifier);
239 callback(makeUnexpected(Error::Internal));
242 if (!cache->isOpened()) {
243 cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
245 callback(makeUnexpected(error.value()));
249 auto* cache = this->cache(cacheIdentifier);
251 callback(makeUnexpected(Error::Internal));
254 ASSERT(cache->isOpened());
255 callback(std::reference_wrapper<Cache> { *cache });
259 callback(std::reference_wrapper<Cache> { *cache });
262 Cache* Engine::cache(uint64_t cacheIdentifier)
264 Cache* result = nullptr;
265 for (auto& caches : m_caches.values()) {
266 if ((result = caches->find(cacheIdentifier)))
272 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
274 if (!shouldPersist()) {
275 callback(std::nullopt);
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());
284 callback(Error::WriteDisk);
287 callback(std::nullopt);
292 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
294 if (!shouldPersist()) {
295 callback(Data { }, 0);
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);
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);
316 void Engine::removeFile(const String& filename)
318 if (!shouldPersist())
321 m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
322 WebCore::FileSystem::deleteFile(filename);
326 class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
328 static Ref<ReadOriginsTaskCounter> create(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
330 return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback)));
333 ~ReadOriginsTaskCounter()
335 m_callback(WTFMove(m_entries));
338 void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size)
340 m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size });
344 explicit ReadOriginsTaskCounter(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
345 : m_callback(WTFMove(callback))
349 WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
350 Vector<WebsiteData::Entry> m_entries;
353 void Engine::fetchEntries(bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
355 if (!shouldPersist()) {
356 auto entries = WTF::map(m_caches, [] (auto& pair) {
357 return WebsiteData::Entry { pair.value->origin().clientOrigin, WebsiteDataType::DOMCache, 0 };
359 completionHandler(WTFMove(entries));
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))
367 Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (auto&& origin) mutable {
368 ASSERT(RunLoop::isMain());
372 if (!shouldComputeSize) {
373 taskCounter->addOrigin(WTFMove(origin->topOrigin), 0);
374 taskCounter->addOrigin(WTFMove(origin->clientOrigin), 0);
378 protectedThis->readCachesFromDisk(origin.value(), [origin = origin.value(), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable {
379 if (!result.has_value())
381 taskCounter->addOrigin(WTFMove(origin.topOrigin), 0);
382 taskCounter->addOrigin(WTFMove(origin.clientOrigin), result.value().get().storageSize());
388 void Engine::clearAllCaches(CallbackAggregator& taskHandler)
390 for (auto& caches : m_caches.values())
391 caches->clear([taskHandler = makeRef(taskHandler)] { });
393 if (!shouldPersist())
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);
404 void Engine::clearCachesForOrigin(const WebCore::SecurityOriginData& origin, CallbackAggregator& taskHandler)
406 for (auto& keyValue : m_caches) {
407 if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin)
408 keyValue.value->clear([taskHandler = makeRef(taskHandler)] { });
411 if (!shouldPersist())
414 for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
415 if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
417 Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [this, protectedThis = makeRef(*this), origin, taskHandler = makeRef(taskHandler)] (std::optional<WebCore::ClientOrigin>&& folderOrigin) mutable {
420 if (folderOrigin->topOrigin != origin && folderOrigin->clientOrigin != origin)
423 m_ioQueue->dispatch([path = cachesRootPath(*folderOrigin), taskHandler = WTFMove(taskHandler)] {
424 deleteDirectoryRecursively(path);
430 void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
432 readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
433 if (!result.has_value()) {
434 callback(result.error());
437 result.value().get().clearMemoryRepresentation();
438 callback(std::nullopt);
442 void Engine::lock(uint64_t cacheIdentifier)
444 auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
451 void Engine::unlock(uint64_t cacheIdentifier)
453 auto lockCount = m_cacheLocks.find(cacheIdentifier);
454 if (lockCount == m_cacheLocks.end())
457 ASSERT(lockCount->value);
458 if (--lockCount->value)
461 auto* cache = this->cache(cacheIdentifier);
468 String Engine::representation()
471 StringBuilder builder;
473 for (auto& keyValue : m_caches) {
478 builder.append("\n{ \"origin\" : { \"topOrigin\" : \"");
479 builder.append(keyValue.key.topOrigin.toString());
480 builder.append("\", \"clientOrigin\": \"");
481 builder.append(keyValue.key.clientOrigin.toString());
482 builder.append("\" }, \"caches\" : ");
483 keyValue.value->appendRepresentation(builder);
486 builder.append("\n]");
487 return builder.toString();
490 } // namespace CacheStorage
492 } // namespace WebKit