| /* |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "CacheStorageEngine.h" |
| |
| #include "Logging.h" |
| #include "NetworkCacheFileSystem.h" |
| #include "NetworkCacheIOChannel.h" |
| #include "NetworkProcess.h" |
| #include "WebsiteDataType.h" |
| #include <WebCore/CacheQueryOptions.h> |
| #include <WebCore/SecurityOrigin.h> |
| #include <wtf/CallbackAggregator.h> |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/Scope.h> |
| #include <wtf/text/StringBuilder.h> |
| #include <wtf/text/StringHash.h> |
| |
| namespace WebKit { |
| |
| namespace CacheStorage { |
| |
| using namespace WebCore::DOMCacheEngine; |
| using namespace NetworkCache; |
| |
| static Lock globalSizeFileLock; |
| |
| String Engine::cachesRootPath(const WebCore::ClientOrigin& origin) |
| { |
| if (!shouldPersist() || !m_salt) |
| return { }; |
| |
| Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt()); |
| return FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString()); |
| } |
| |
| Engine::~Engine() |
| { |
| for (auto& caches : m_caches.values()) |
| caches->detach(); |
| |
| auto pendingClearCallbacks = WTFMove(m_pendingClearCallbacks); |
| for (auto& callback : pendingClearCallbacks) |
| callback(Error::Internal); |
| |
| auto initializationCallbacks = WTFMove(m_initializationCallbacks); |
| for (auto& callback : initializationCallbacks) |
| callback(Error::Internal); |
| |
| auto writeCallbacks = WTFMove(m_pendingWriteCallbacks); |
| for (auto& callback : writeCallbacks.values()) |
| callback(Error::Internal); |
| |
| auto readCallbacks = WTFMove(m_pendingReadCallbacks); |
| for (auto& callback : readCallbacks.values()) |
| callback(Data { }, 1); |
| } |
| |
| void Engine::from(NetworkProcess& networkProcess, PAL::SessionID sessionID, Function<void(Engine&)>&& callback) |
| { |
| if (auto* engine = networkProcess.findCacheEngine(sessionID)) { |
| callback(*engine); |
| return; |
| } |
| |
| networkProcess.cacheStorageRootPath(sessionID, [networkProcess = makeRef(networkProcess), sessionID, callback = WTFMove(callback)] (auto&& rootPath) mutable { |
| callback(networkProcess->ensureCacheEngine(sessionID, [&] { |
| return adoptRef(*new Engine { sessionID, networkProcess.get(), WTFMove(rootPath) }); |
| })); |
| }); |
| } |
| |
| void Engine::destroyEngine(NetworkProcess& networkProcess, PAL::SessionID sessionID) |
| { |
| #if !USE(SOUP) |
| // Soup based ports destroy the default session right before the process exits to avoid leaking |
| // network resources like the cookies database. |
| ASSERT(sessionID != PAL::SessionID::defaultSessionID()); |
| #endif |
| |
| networkProcess.removeCacheEngine(sessionID); |
| } |
| |
| void Engine::fetchEntries(NetworkProcess& networkProcess, PAL::SessionID sessionID, bool shouldComputeSize, CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler) |
| { |
| from(networkProcess, sessionID, [shouldComputeSize, completionHandler = WTFMove(completionHandler)] (auto& engine) mutable { |
| engine.fetchEntries(shouldComputeSize, WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::open(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, String&& cacheName, WebCore::DOMCacheEngine::CacheIdentifierCallback&& callback) |
| { |
| from(networkProcess, sessionID, [origin = WTFMove(origin), cacheName = WTFMove(cacheName), callback = WTFMove(callback)](auto& engine) mutable { |
| engine.open(origin, cacheName, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::remove(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, WebCore::DOMCacheEngine::CacheIdentifierCallback&& callback) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier, callback = WTFMove(callback)](auto& engine) mutable { |
| engine.remove(cacheIdentifier, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::retrieveCaches(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, uint64_t updateCounter, WebCore::DOMCacheEngine::CacheInfosCallback&& callback) |
| { |
| from(networkProcess, sessionID, [origin = WTFMove(origin), updateCounter, callback = WTFMove(callback)](auto& engine) mutable { |
| engine.retrieveCaches(origin, updateCounter, WTFMove(callback)); |
| }); |
| } |
| |
| |
| void Engine::retrieveRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, URL&& url, WebCore::DOMCacheEngine::RecordsCallback&& callback) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier, url = WTFMove(url), callback = WTFMove(callback)](auto& engine) mutable { |
| engine.retrieveRecords(cacheIdentifier, WTFMove(url), WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::putRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, Vector<WebCore::DOMCacheEngine::Record>&& records, WebCore::DOMCacheEngine::RecordIdentifiersCallback&& callback) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier, records = WTFMove(records), callback = WTFMove(callback)](auto& engine) mutable { |
| engine.putRecords(cacheIdentifier, WTFMove(records), WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::deleteMatchingRecords(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, WebCore::DOMCacheEngine::RecordIdentifiersCallback&& callback) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier, request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](auto& engine) mutable { |
| engine.deleteMatchingRecords(cacheIdentifier, WTFMove(request), WTFMove(options), WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::lock(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier](auto& engine) mutable { |
| engine.lock(cacheIdentifier); |
| }); |
| } |
| |
| void Engine::unlock(NetworkProcess& networkProcess, PAL::SessionID sessionID, uint64_t cacheIdentifier) |
| { |
| from(networkProcess, sessionID, [cacheIdentifier](auto& engine) mutable { |
| engine.unlock(cacheIdentifier); |
| }); |
| } |
| |
| void Engine::clearMemoryRepresentation(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::ClientOrigin&& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback) |
| { |
| from(networkProcess, sessionID, [origin = WTFMove(origin), callback = WTFMove(callback)](auto& engine) mutable { |
| engine.clearMemoryRepresentation(origin, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::representation(NetworkProcess& networkProcess, PAL::SessionID sessionID, CompletionHandler<void(String&&)>&& callback) |
| { |
| from(networkProcess, sessionID, [callback = WTFMove(callback)](auto& engine) mutable { |
| callback(engine.representation()); |
| }); |
| } |
| |
| void Engine::clearAllCaches(NetworkProcess& networkProcess, PAL::SessionID sessionID, CompletionHandler<void()>&& completionHandler) |
| { |
| from(networkProcess, sessionID, [completionHandler = WTFMove(completionHandler)](auto& engine) mutable { |
| engine.clearAllCaches(WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::clearCachesForOrigin(NetworkProcess& networkProcess, PAL::SessionID sessionID, WebCore::SecurityOriginData&& originData, CompletionHandler<void()>&& completionHandler) |
| { |
| from(networkProcess, sessionID, [originData = WTFMove(originData), completionHandler = WTFMove(completionHandler)](auto& engine) mutable { |
| engine.clearCachesForOrigin(originData, WTFMove(completionHandler)); |
| }); |
| } |
| |
| static uint64_t getDirectorySize(const String& directoryPath) |
| { |
| ASSERT(!isMainThread()); |
| |
| uint64_t directorySize = 0; |
| Deque<String> paths; |
| paths.append(directoryPath); |
| while (!paths.isEmpty()) { |
| auto path = paths.takeFirst(); |
| if (FileSystem::fileIsDirectory(path, FileSystem::ShouldFollowSymbolicLinks::No)) { |
| auto newPaths = FileSystem::listDirectory(path, "*"_s); |
| for (auto& newPath : newPaths) { |
| // Files in /Blobs directory are hard link. |
| auto fileName = FileSystem::lastComponentOfPathIgnoringTrailingSlash(newPath); |
| if (fileName == "Blobs") |
| continue; |
| paths.append(newPath); |
| } |
| continue; |
| } |
| |
| long long fileSize = 0; |
| FileSystem::getFileSize(path, fileSize); |
| directorySize += fileSize; |
| } |
| return directorySize; |
| } |
| |
| uint64_t Engine::diskUsage(const String& rootPath, const WebCore::ClientOrigin& origin) |
| { |
| ASSERT(!isMainThread()); |
| |
| if (rootPath.isEmpty()) |
| return 0; |
| |
| String saltPath = FileSystem::pathByAppendingComponent(rootPath, "salt"_s); |
| auto salt = readOrMakeSalt(saltPath); |
| if (!salt) |
| return 0; |
| |
| Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, *salt); |
| String directoryPath = FileSystem::pathByAppendingComponent(rootPath, key.hashAsString()); |
| |
| String sizeFilePath = Caches::cachesSizeFilename(directoryPath); |
| if (auto recordedSize = readSizeFile(sizeFilePath)) |
| return *recordedSize; |
| |
| return getDirectorySize(directoryPath); |
| } |
| |
| void Engine::requestSpace(const ClientOrigin& origin, uint64_t spaceRequested, CompletionHandler<void(WebCore::StorageQuotaManager::Decision)>&& callback) |
| { |
| ASSERT(isMainThread()); |
| |
| if (!m_networkProcess) |
| callback(WebCore::StorageQuotaManager::Decision::Deny); |
| |
| RefPtr<WebCore::StorageQuotaManager> storageQuotaManager = m_networkProcess->storageQuotaManager(m_sessionID, origin); |
| if (!storageQuotaManager) |
| callback(WebCore::StorageQuotaManager::Decision::Deny); |
| |
| storageQuotaManager->requestSpaceOnMainThread(spaceRequested, WTFMove(callback)); |
| } |
| |
| Engine::Engine(PAL::SessionID sessionID, NetworkProcess& process, String&& rootPath) |
| : m_sessionID(sessionID) |
| , m_networkProcess(makeWeakPtr(process)) |
| , m_rootPath(WTFMove(rootPath)) |
| { |
| if (!m_rootPath.isNull()) |
| m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background); |
| } |
| |
| void Engine::open(const WebCore::ClientOrigin& origin, const String& cacheName, CacheIdentifierCallback&& callback) |
| { |
| readCachesFromDisk(origin, [cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable { |
| if (!cachesOrError.has_value()) { |
| callback(makeUnexpected(cachesOrError.error())); |
| return; |
| } |
| |
| cachesOrError.value().get().open(cacheName, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback) |
| { |
| Caches* cachesToModify = nullptr; |
| for (auto& caches : m_caches.values()) { |
| auto* cacheToRemove = caches->find(cacheIdentifier); |
| if (cacheToRemove) { |
| cachesToModify = caches.get(); |
| break; |
| } |
| } |
| if (!cachesToModify) { |
| callback(makeUnexpected(Error::Internal)); |
| return; |
| } |
| |
| cachesToModify->remove(cacheIdentifier, WTFMove(callback)); |
| } |
| |
| void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback) |
| { |
| readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable { |
| if (!cachesOrError.has_value()) { |
| callback(makeUnexpected(cachesOrError.error())); |
| return; |
| } |
| |
| cachesOrError.value().get().cacheInfos(updateCounter, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::retrieveRecords(uint64_t cacheIdentifier, URL&& url, RecordsCallback&& callback) |
| { |
| readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable { |
| if (!result.has_value()) { |
| callback(makeUnexpected(result.error())); |
| return; |
| } |
| result.value().get().retrieveRecords(url, WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback) |
| { |
| readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable { |
| if (!result.has_value()) { |
| callback(makeUnexpected(result.error())); |
| return; |
| } |
| |
| result.value().get().put(WTFMove(records), WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback) |
| { |
| readCache(cacheIdentifier, [request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable { |
| if (!result.has_value()) { |
| callback(makeUnexpected(result.error())); |
| return; |
| } |
| |
| result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback)); |
| }); |
| } |
| |
| void Engine::initialize(CompletionCallback&& callback) |
| { |
| if (m_clearTaskCounter || !m_pendingClearCallbacks.isEmpty()) { |
| m_pendingClearCallbacks.append(WTFMove(callback)); |
| return; |
| } |
| |
| if (m_salt) { |
| callback(WTF::nullopt); |
| return; |
| } |
| |
| if (!shouldPersist()) { |
| m_salt = NetworkCache::Salt { }; |
| callback(WTF::nullopt); |
| return; |
| } |
| |
| bool shouldComputeSalt = m_initializationCallbacks.isEmpty(); |
| m_initializationCallbacks.append(WTFMove(callback)); |
| |
| if (!shouldComputeSalt) |
| return; |
| |
| m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), rootPath = m_rootPath.isolatedCopy()] () mutable { |
| FileSystem::makeAllDirectories(rootPath); |
| String saltPath = FileSystem::pathByAppendingComponent(rootPath, "salt"_s); |
| RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis), salt = readOrMakeSalt(saltPath)]() mutable { |
| if (!weakThis) |
| return; |
| |
| m_salt = WTFMove(salt); |
| |
| auto callbacks = WTFMove(m_initializationCallbacks); |
| for (auto& callback : callbacks) |
| callback(m_salt ? WTF::nullopt : makeOptional(Error::WriteDisk)); |
| }); |
| }); |
| } |
| |
| void Engine::readCachesFromDisk(const WebCore::ClientOrigin& origin, CachesCallback&& callback) |
| { |
| initialize([this, origin, callback = WTFMove(callback)](Optional<Error>&& error) mutable { |
| if (error) { |
| callback(makeUnexpected(error.value())); |
| return; |
| } |
| |
| auto& caches = m_caches.ensure(origin, [&origin, this] { |
| auto path = cachesRootPath(origin); |
| return Caches::create(*this, WebCore::ClientOrigin { origin }, WTFMove(path)); |
| }).iterator->value; |
| |
| if (caches->isInitialized()) { |
| callback(std::reference_wrapper<Caches> { *caches }); |
| return; |
| } |
| |
| caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](Optional<Error>&& error) mutable { |
| if (error) { |
| callback(makeUnexpected(error.value())); |
| return; |
| } |
| |
| callback(std::reference_wrapper<Caches> { *caches }); |
| }); |
| }); |
| } |
| |
| void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback) |
| { |
| auto* cache = this->cache(cacheIdentifier); |
| if (!cache) { |
| callback(makeUnexpected(Error::Internal)); |
| return; |
| } |
| if (!cache->isOpened()) { |
| cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](Optional<Error>&& error) mutable { |
| if (error) { |
| callback(makeUnexpected(error.value())); |
| return; |
| } |
| |
| auto* cache = this->cache(cacheIdentifier); |
| if (!cache) { |
| callback(makeUnexpected(Error::Internal)); |
| return; |
| } |
| ASSERT(cache->isOpened()); |
| callback(std::reference_wrapper<Cache> { *cache }); |
| }); |
| return; |
| } |
| callback(std::reference_wrapper<Cache> { *cache }); |
| } |
| |
| Cache* Engine::cache(uint64_t cacheIdentifier) |
| { |
| Cache* result = nullptr; |
| for (auto& caches : m_caches.values()) { |
| if ((result = caches->find(cacheIdentifier))) |
| break; |
| } |
| return result; |
| } |
| |
| void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback) |
| { |
| if (!shouldPersist()) { |
| callback(WTF::nullopt); |
| return; |
| } |
| |
| m_pendingWriteCallbacks.add(++m_pendingCallbacksCounter, WTFMove(callback)); |
| m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), identifier = m_pendingCallbacksCounter, data = WTFMove(data), filename = filename.isolatedCopy()]() mutable { |
| |
| String directoryPath = FileSystem::directoryName(filename); |
| if (!FileSystem::fileExists(directoryPath)) |
| FileSystem::makeAllDirectories(directoryPath); |
| |
| auto channel = IOChannel::open(filename, IOChannel::Type::Create); |
| channel->write(0, data, nullptr, [this, weakThis = WTFMove(weakThis), identifier](int error) mutable { |
| ASSERT(RunLoop::isMain()); |
| if (!weakThis) |
| return; |
| |
| auto callback = m_pendingWriteCallbacks.take(identifier); |
| if (error) { |
| RELEASE_LOG_ERROR(CacheStorage, "CacheStorage::Engine::writeFile failed with error %d", error); |
| |
| callback(Error::WriteDisk); |
| return; |
| } |
| callback(WTF::nullopt); |
| }); |
| }); |
| } |
| |
| void Engine::readFile(const String& filename, CompletionHandler<void(const NetworkCache::Data&, int error)>&& callback) |
| { |
| if (!shouldPersist()) { |
| callback(Data { }, 0); |
| return; |
| } |
| |
| m_pendingReadCallbacks.add(++m_pendingCallbacksCounter, WTFMove(callback)); |
| m_ioQueue->dispatch([this, weakThis = makeWeakPtr(this), identifier = m_pendingCallbacksCounter, filename = filename.isolatedCopy()]() mutable { |
| auto channel = IOChannel::open(filename, IOChannel::Type::Read); |
| if (!channel->isOpened()) { |
| RunLoop::main().dispatch([this, weakThis = WTFMove(weakThis), identifier]() mutable { |
| if (!weakThis) |
| return; |
| |
| m_pendingReadCallbacks.take(identifier)(Data { }, 0); |
| }); |
| return; |
| } |
| |
| channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [this, weakThis = WTFMove(weakThis), identifier](const Data& data, int error) mutable { |
| RELEASE_LOG_ERROR_IF(error, CacheStorage, "CacheStorage::Engine::readFile failed with error %d", error); |
| |
| // FIXME: We should do the decoding in the background thread. |
| ASSERT(RunLoop::isMain()); |
| |
| if (!weakThis) |
| return; |
| |
| m_pendingReadCallbacks.take(identifier)(data, error); |
| }); |
| }); |
| } |
| |
| void Engine::removeFile(const String& filename) |
| { |
| if (!shouldPersist()) |
| return; |
| |
| m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable { |
| FileSystem::deleteFile(filename); |
| }); |
| } |
| |
| void Engine::writeSizeFile(const String& path, uint64_t size, CompletionHandler<void()>&& completionHandler) |
| { |
| CompletionHandlerCallingScope completionHandlerCaller(WTFMove(completionHandler)); |
| if (!shouldPersist()) |
| return; |
| |
| m_ioQueue->dispatch([path = path.isolatedCopy(), size, completionHandlerCaller = WTFMove(completionHandlerCaller)]() mutable { |
| LockHolder locker(globalSizeFileLock); |
| auto fileHandle = FileSystem::openFile(path, FileSystem::FileOpenMode::Write); |
| auto closeFileHandler = makeScopeExit([&] { |
| FileSystem::closeFile(fileHandle); |
| }); |
| if (!FileSystem::isHandleValid(fileHandle)) |
| return; |
| |
| FileSystem::truncateFile(fileHandle, 0); |
| FileSystem::writeToFile(fileHandle, String::number(size).utf8().data(), String::number(size).utf8().length()); |
| |
| RunLoop::main().dispatch([completionHandlerCaller = WTFMove(completionHandlerCaller)]() mutable { }); |
| }); |
| } |
| |
| Optional<uint64_t> Engine::readSizeFile(const String& path) |
| { |
| ASSERT(!RunLoop::isMain()); |
| |
| LockHolder locker(globalSizeFileLock); |
| auto fileHandle = FileSystem::openFile(path, FileSystem::FileOpenMode::Read); |
| auto closeFileHandle = makeScopeExit([&] { |
| FileSystem::closeFile(fileHandle); |
| }); |
| |
| if (!FileSystem::isHandleValid(fileHandle)) |
| return WTF::nullopt; |
| |
| long long fileSize = 0; |
| if (!FileSystem::getFileSize(path, fileSize) || !fileSize) |
| return WTF::nullopt; |
| |
| size_t bytesToRead; |
| if (!WTF::convertSafely(fileSize, bytesToRead)) |
| return WTF::nullopt; |
| |
| Vector<unsigned char> buffer(bytesToRead); |
| size_t totalBytesRead = FileSystem::readFromFile(fileHandle, reinterpret_cast<char*>(buffer.data()), buffer.size()); |
| if (totalBytesRead != bytesToRead) |
| return WTF::nullopt; |
| |
| return charactersToUIntStrict(buffer.data(), totalBytesRead); |
| } |
| |
| class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> { |
| public: |
| static Ref<ReadOriginsTaskCounter> create(CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback) |
| { |
| return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback))); |
| } |
| |
| ~ReadOriginsTaskCounter() |
| { |
| m_callback(WTFMove(m_entries)); |
| } |
| |
| void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size) |
| { |
| m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size }); |
| } |
| |
| private: |
| explicit ReadOriginsTaskCounter(CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback) |
| : m_callback(WTFMove(callback)) |
| { |
| } |
| |
| CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback; |
| Vector<WebsiteData::Entry> m_entries; |
| }; |
| |
| void Engine::getDirectories(CompletionHandler<void(const Vector<String>&)>&& completionHandler) |
| { |
| m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable { |
| Vector<String> folderPaths; |
| for (auto& filename : FileSystem::listDirectory(path, "*")) { |
| if (FileSystem::fileIsDirectory(filename, FileSystem::ShouldFollowSymbolicLinks::No)) |
| folderPaths.append(filename.isolatedCopy()); |
| } |
| |
| RunLoop::main().dispatch([folderPaths = WTFMove(folderPaths), completionHandler = WTFMove(completionHandler)]() mutable { |
| completionHandler(folderPaths); |
| }); |
| }); |
| } |
| |
| void Engine::fetchEntries(bool shouldComputeSize, CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler) |
| { |
| if (!shouldPersist()) { |
| auto entries = WTF::map(m_caches, [] (auto& pair) { |
| return WebsiteData::Entry { pair.value->origin().clientOrigin, WebsiteDataType::DOMCache, 0 }; |
| }); |
| completionHandler(WTFMove(entries)); |
| return; |
| } |
| |
| getDirectories([this, weakThis = makeWeakPtr(this), path = m_rootPath.isolatedCopy(), shouldComputeSize, completionHandler = WTFMove(completionHandler)](const auto& folderPaths) mutable { |
| if (!weakThis) |
| return completionHandler({ }); |
| fetchDirectoryEntries(shouldComputeSize, folderPaths, WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::fetchDirectoryEntries(bool shouldComputeSize, const Vector<String>& folderPaths, CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler) |
| { |
| auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler)); |
| for (auto& folderPath : folderPaths) { |
| Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (auto&& origin) mutable { |
| ASSERT(RunLoop::isMain()); |
| if (!origin) |
| return; |
| |
| if (!shouldComputeSize) { |
| taskCounter->addOrigin(WTFMove(origin->topOrigin), 0); |
| taskCounter->addOrigin(WTFMove(origin->clientOrigin), 0); |
| return; |
| } |
| |
| protectedThis->readCachesFromDisk(origin.value(), [origin = origin.value(), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable { |
| if (!result.has_value()) |
| return; |
| taskCounter->addOrigin(WTFMove(origin.topOrigin), 0); |
| taskCounter->addOrigin(WTFMove(origin.clientOrigin), result.value().get().storageSize()); |
| }); |
| }); |
| } |
| } |
| |
| CompletionHandler<void()> Engine::createClearTask(CompletionHandler<void()>&& completionHandler) |
| { |
| ++m_clearTaskCounter; |
| return [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)]() mutable { |
| completionHandler(); |
| if (!--m_clearTaskCounter) { |
| auto callbacks = WTFMove(m_pendingClearCallbacks); |
| for (auto& callback : callbacks) |
| initialize(WTFMove(callback)); |
| } |
| }; |
| } |
| |
| void Engine::clearAllCaches(CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(RunLoop::isMain()); |
| |
| auto callbackAggregator = CallbackAggregator::create([this, completionHandler = createClearTask(WTFMove(completionHandler))]() mutable { |
| if (!this->shouldPersist()) |
| return completionHandler(); |
| |
| this->clearAllCachesFromDisk(WTFMove(completionHandler)); |
| }); |
| |
| for (auto& caches : m_caches.values()) |
| caches->clear([callbackAggregator = callbackAggregator.copyRef()] { }); |
| } |
| |
| void Engine::clearAllCachesFromDisk(CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(RunLoop::isMain()); |
| |
| m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable { |
| LockHolder locker(globalSizeFileLock); |
| for (auto& filename : FileSystem::listDirectory(path, "*")) { |
| if (FileSystem::fileIsDirectory(filename, FileSystem::ShouldFollowSymbolicLinks::No)) |
| deleteDirectoryRecursively(filename); |
| } |
| RunLoop::main().dispatch(WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::clearCachesForOrigin(const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(RunLoop::isMain()); |
| |
| auto callbackAggregator = CallbackAggregator::create([this, origin, completionHandler = createClearTask(WTFMove(completionHandler))]() mutable { |
| if (!this->shouldPersist()) |
| return completionHandler(); |
| |
| this->clearCachesForOriginFromDisk(origin, [completionHandler = WTFMove(completionHandler)]() mutable { |
| completionHandler(); |
| }); |
| }); |
| |
| for (auto& keyValue : m_caches) { |
| if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin) |
| keyValue.value->clear([callbackAggregator = callbackAggregator.copyRef()] { }); |
| } |
| } |
| |
| void Engine::clearCachesForOriginFromDisk(const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(RunLoop::isMain()); |
| getDirectories([this, weakThis = makeWeakPtr(this), origin, completionHandler = WTFMove(completionHandler)](const auto& folderPaths) mutable { |
| if (!weakThis) |
| return completionHandler(); |
| clearCachesForOriginFromDirectories(folderPaths, origin, WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::clearCachesForOriginFromDirectories(const Vector<String>& folderPaths, const WebCore::SecurityOriginData& origin, CompletionHandler<void()>&& completionHandler) |
| { |
| auto callbackAggregator = CallbackAggregator::create(WTFMove(completionHandler)); |
| for (auto& folderPath : folderPaths) { |
| Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [this, protectedThis = makeRef(*this), origin, callbackAggregator = callbackAggregator.copyRef(), folderPath] (Optional<WebCore::ClientOrigin>&& folderOrigin) mutable { |
| if (!folderOrigin) |
| return; |
| if (folderOrigin->topOrigin != origin && folderOrigin->clientOrigin != origin) |
| return; |
| |
| ASSERT(folderPath == cachesRootPath(*folderOrigin)); |
| deleteDirectoryRecursivelyOnBackgroundThread(folderPath, [callbackAggregator = WTFMove(callbackAggregator)] { }); |
| }); |
| } |
| } |
| |
| void Engine::deleteDirectoryRecursivelyOnBackgroundThread(const String& path, CompletionHandler<void()>&& completionHandler) |
| { |
| ASSERT(RunLoop::isMain()); |
| |
| m_ioQueue->dispatch([path = path.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable { |
| LockHolder locker(globalSizeFileLock); |
| deleteDirectoryRecursively(path); |
| |
| RunLoop::main().dispatch(WTFMove(completionHandler)); |
| }); |
| } |
| |
| void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback) |
| { |
| readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) mutable { |
| if (!result.has_value()) { |
| callback(result.error()); |
| return; |
| } |
| result.value().get().clearMemoryRepresentation(); |
| callback(WTF::nullopt); |
| }); |
| } |
| |
| void Engine::lock(uint64_t cacheIdentifier) |
| { |
| auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() { |
| return 0; |
| }).iterator->value; |
| |
| ++counter; |
| } |
| |
| void Engine::unlock(uint64_t cacheIdentifier) |
| { |
| auto lockCount = m_cacheLocks.find(cacheIdentifier); |
| if (lockCount == m_cacheLocks.end()) |
| return; |
| |
| ASSERT(lockCount->value); |
| if (--lockCount->value) |
| return; |
| |
| auto* cache = this->cache(cacheIdentifier); |
| if (!cache) |
| return; |
| |
| cache->dispose(); |
| } |
| |
| String Engine::representation() |
| { |
| bool isFirst = true; |
| StringBuilder builder; |
| builder.append("{ \"path\": \""); |
| builder.append(m_rootPath); |
| builder.append("\", \"origins\": ["); |
| for (auto& keyValue : m_caches) { |
| if (!isFirst) |
| builder.append(","); |
| isFirst = false; |
| |
| builder.append("\n{ \"origin\" : { \"topOrigin\" : \""); |
| builder.append(keyValue.key.topOrigin.toString()); |
| builder.append("\", \"clientOrigin\": \""); |
| builder.append(keyValue.key.clientOrigin.toString()); |
| builder.append("\" }, \"caches\" : "); |
| keyValue.value->appendRepresentation(builder); |
| builder.append("}"); |
| } |
| builder.append("]}"); |
| return builder.toString(); |
| } |
| |
| } // namespace CacheStorage |
| |
| } // namespace WebKit |