| /* |
| * Copyright (C) 2021 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. AND ITS CONTRIBUTORS ``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 ITS 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 "SWScriptStorage.h" |
| |
| #if ENABLE(SERVICE_WORKER) |
| #include "Logging.h" |
| #include "ScriptBuffer.h" |
| #include "ServiceWorkerRegistrationKey.h" |
| #include <pal/crypto/CryptoDigest.h> |
| #include <wtf/MainThread.h> |
| #include <wtf/PageBlock.h> |
| #include <wtf/text/Base64.h> |
| |
| namespace WebCore { |
| |
| static bool shouldUseFileMapping(uint64_t fileSize) |
| { |
| return fileSize >= pageSize(); |
| } |
| |
| SWScriptStorage::SWScriptStorage(const String& directory) |
| : m_directory(directory) |
| , m_salt(FileSystem::readOrMakeSalt(saltPath()).value_or(FileSystem::Salt())) |
| { |
| ASSERT(!isMainThread()); |
| } |
| |
| String SWScriptStorage::sha2Hash(const String& input) const |
| { |
| auto crypto = PAL::CryptoDigest::create(PAL::CryptoDigest::Algorithm::SHA_256); |
| crypto->addBytes(m_salt.data(), m_salt.size()); |
| auto inputUtf8 = input.utf8(); |
| crypto->addBytes(inputUtf8.data(), inputUtf8.length()); |
| auto hash = crypto->computeHash(); |
| return base64URLEncodeToString(hash.data(), hash.size()); |
| } |
| |
| String SWScriptStorage::sha2Hash(const URL& input) const |
| { |
| return sha2Hash(input.string()); |
| } |
| |
| String SWScriptStorage::saltPath() const |
| { |
| return FileSystem::pathByAppendingComponent(m_directory, "salt"); |
| } |
| |
| String SWScriptStorage::registrationDirectory(const ServiceWorkerRegistrationKey& registrationKey) const |
| { |
| return FileSystem::pathByAppendingComponents(m_directory, { sha2Hash(registrationKey.topOrigin().toString()), sha2Hash(registrationKey.scope()) }); |
| } |
| |
| String SWScriptStorage::scriptPath(const ServiceWorkerRegistrationKey& registrationKey, const URL& scriptURL) const |
| { |
| return FileSystem::pathByAppendingComponent(registrationDirectory(registrationKey), sha2Hash(scriptURL)); |
| } |
| |
| ScriptBuffer SWScriptStorage::store(const ServiceWorkerRegistrationKey& registrationKey, const URL& scriptURL, const ScriptBuffer& script) |
| { |
| ASSERT(!isMainThread()); |
| |
| auto scriptPath = this->scriptPath(registrationKey, scriptURL); |
| FileSystem::makeAllDirectories(FileSystem::parentPath(scriptPath)); |
| |
| auto iterateOverBufferAndWriteData = [&](const Function<bool(Span<const uint8_t>)>& writeData) { |
| for (auto& entry : *script.buffer()) |
| writeData({ entry.segment->data(), entry.segment->size() }); |
| }; |
| |
| if (!shouldUseFileMapping(script.buffer()->size())) { |
| auto handle = FileSystem::openFile(scriptPath, FileSystem::FileOpenMode::Write); |
| if (!FileSystem::isHandleValid(handle)) { |
| RELEASE_LOG_ERROR(ServiceWorker, "SWScriptStorage::store: Failure to store %s, FileSystem::openFile() failed", scriptPath.utf8().data()); |
| return { }; |
| } |
| iterateOverBufferAndWriteData([&](Span<const uint8_t> span) { |
| FileSystem::writeToFile(handle, span.data(), span.size()); |
| return true; |
| }); |
| FileSystem::closeFile(handle); |
| return script; |
| } |
| |
| // Make sure we delete the file before writing as there may be code using a mmap'd version of this file. |
| FileSystem::deleteFile(scriptPath); |
| auto mappedFile = FileSystem::mapToFile(scriptPath, script.buffer()->size(), WTFMove(iterateOverBufferAndWriteData)); |
| if (!mappedFile) { |
| RELEASE_LOG_ERROR(ServiceWorker, "SWScriptStorage::store: Failure to store %s, FileSystem::mapToFile() failed", scriptPath.utf8().data()); |
| return { }; |
| } |
| return ScriptBuffer { SharedBuffer::create(WTFMove(mappedFile)) }; |
| } |
| |
| ScriptBuffer SWScriptStorage::retrieve(const ServiceWorkerRegistrationKey& registrationKey, const URL& scriptURL) |
| { |
| ASSERT(!isMainThread()); |
| |
| auto scriptPath = this->scriptPath(registrationKey, scriptURL); |
| auto fileSize = FileSystem::fileSize(scriptPath); |
| if (!fileSize) { |
| RELEASE_LOG_ERROR(ServiceWorker, "SWScriptStorage::retrieve: Failure to retrieve %s, FileSystem::fileSize() failed", scriptPath.utf8().data()); |
| return { }; |
| } |
| |
| // FIXME: Do we need to disable file mapping in more cases to avoid having too many file descriptors open? |
| return SharedBuffer::createWithContentsOfFile(scriptPath, FileSystem::MappedFileMode::Private, shouldUseFileMapping(*fileSize) ? SharedBuffer::MayUseFileMapping::Yes : SharedBuffer::MayUseFileMapping::No); |
| } |
| |
| void SWScriptStorage::clear(const ServiceWorkerRegistrationKey& registrationKey) |
| { |
| auto registrationDirectory = this->registrationDirectory(registrationKey); |
| bool result = FileSystem::deleteNonEmptyDirectory(registrationDirectory); |
| RELEASE_LOG_ERROR_IF(!result, ServiceWorker, "SWScriptStorage::clear: Failure to clear scripts for registration %s", registrationKey.toDatabaseKey().utf8().data()); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(SERVICE_WORKER) |