APIUserContentExtensionStore.cpp [plain text]
#include "config.h"
#include "APIUserContentExtensionStore.h"
#if ENABLE(CONTENT_EXTENSIONS)
#include "APIUserContentExtension.h"
#include "NetworkCacheData.h"
#include "NetworkCacheDecoder.h"
#include "NetworkCacheEncoder.h"
#include "NetworkCacheFileSystem.h"
#include "SharedMemory.h"
#include "WebCompiledContentExtension.h"
#include <WebCore/ContentExtensionCompiler.h>
#include <WebCore/ContentExtensionError.h>
#include <string>
#include <wtf/NeverDestroyed.h>
#include <wtf/RunLoop.h>
#include <wtf/WorkQueue.h>
using namespace WebKit::NetworkCache;
namespace API {
UserContentExtensionStore& UserContentExtensionStore::defaultStore()
{
static UserContentExtensionStore* defaultStore = adoptRef(new UserContentExtensionStore).leakRef();
return *defaultStore;
}
Ref<UserContentExtensionStore> UserContentExtensionStore::storeWithPath(const WTF::String& storePath)
{
return adoptRef(*new UserContentExtensionStore(storePath));
}
UserContentExtensionStore::UserContentExtensionStore()
: UserContentExtensionStore(defaultStorePath())
{
}
UserContentExtensionStore::UserContentExtensionStore(const WTF::String& storePath)
: m_storePath(storePath)
, m_compileQueue(WorkQueue::create("UserContentExtensionStore Compile Queue", WorkQueue::Type::Concurrent))
, m_readQueue(WorkQueue::create("UserContentExtensionStore Read Queue"))
, m_removeQueue(WorkQueue::create("UserContentExtensionStore Remove Queue"))
{
}
UserContentExtensionStore::~UserContentExtensionStore()
{
}
static String constructedPath(const String& base, const String& identifier)
{
return WebCore::pathByAppendingComponent(base, "ContentExtension-" + WebCore::encodeForFileName(identifier));
}
const size_t ContentExtensionFileHeaderSize = sizeof(uint32_t) + 4 * sizeof(uint64_t);
struct ContentExtensionMetaData {
uint32_t version { UserContentExtensionStore::CurrentContentExtensionFileVersion };
uint64_t actionsSize { 0 };
uint64_t filtersWithoutDomainsBytecodeSize { 0 };
uint64_t filtersWithDomainBytecodeSize { 0 };
uint64_t domainFiltersBytecodeSize { 0 };
size_t fileSize() const
{
return ContentExtensionFileHeaderSize
+ actionsSize
+ filtersWithoutDomainsBytecodeSize
+ filtersWithDomainBytecodeSize
+ domainFiltersBytecodeSize;
}
};
static Data encodeContentExtensionMetaData(const ContentExtensionMetaData& metaData)
{
WebKit::NetworkCache::Encoder encoder;
encoder << metaData.version;
encoder << metaData.actionsSize;
encoder << metaData.filtersWithoutDomainsBytecodeSize;
encoder << metaData.filtersWithDomainBytecodeSize;
encoder << metaData.domainFiltersBytecodeSize;
ASSERT(encoder.bufferSize() == ContentExtensionFileHeaderSize);
return Data(encoder.buffer(), encoder.bufferSize());
}
static bool decodeContentExtensionMetaData(ContentExtensionMetaData& metaData, const Data& fileData)
{
bool success = false;
fileData.apply([&metaData, &success, &fileData](const uint8_t* data, size_t size) {
if (size != fileData.size())
return false;
WebKit::NetworkCache::Decoder decoder(data, size);
if (!decoder.decode(metaData.version))
return false;
if (!decoder.decode(metaData.actionsSize))
return false;
if (!decoder.decode(metaData.filtersWithoutDomainsBytecodeSize))
return false;
if (!decoder.decode(metaData.filtersWithDomainBytecodeSize))
return false;
if (!decoder.decode(metaData.domainFiltersBytecodeSize))
return false;
success = true;
return false;
});
return success;
}
static bool openAndMapContentExtension(const String& path, ContentExtensionMetaData& metaData, Data& fileData)
{
fileData = mapFile(WebCore::fileSystemRepresentation(path).data());
if (fileData.isNull())
return false;
if (!decodeContentExtensionMetaData(metaData, fileData))
return false;
return true;
}
static bool writeDataToFile(const Data& fileData, WebCore::PlatformFileHandle fd)
{
bool success = true;
fileData.apply([fd, &success](const uint8_t* data, size_t size) {
if (WebCore::writeToFile(fd, (const char*)data, size) == -1) {
success = false;
return false;
}
return true;
});
return success;
}
static std::error_code compiledToFile(String&& json, const String& finalFilePath, ContentExtensionMetaData& metaData, Data& mappedData)
{
using namespace WebCore::ContentExtensions;
class CompilationClient final : public ContentExtensionCompilationClient {
public:
CompilationClient(WebCore::PlatformFileHandle fileHandle, ContentExtensionMetaData& metaData)
: m_fileHandle(fileHandle)
, m_metaData(metaData)
{
ASSERT(!metaData.actionsSize);
ASSERT(!metaData.filtersWithoutDomainsBytecodeSize);
ASSERT(!metaData.filtersWithDomainBytecodeSize);
ASSERT(!metaData.domainFiltersBytecodeSize);
}
void writeFiltersWithoutDomainsBytecode(Vector<DFABytecode>&& bytecode) override
{
ASSERT(!m_filtersWithDomainBytecodeWritten);
ASSERT(!m_domainFiltersBytecodeWritten);
m_filtersWithoutDomainsBytecodeWritten += bytecode.size();
writeToFile(Data(bytecode.data(), bytecode.size()));
}
void writeFiltersWithDomainsBytecode(Vector<DFABytecode>&& bytecode) override
{
ASSERT(!m_domainFiltersBytecodeWritten);
m_filtersWithDomainBytecodeWritten += bytecode.size();
writeToFile(Data(bytecode.data(), bytecode.size()));
}
void writeDomainFiltersBytecode(Vector<DFABytecode>&& bytecode) override
{
m_domainFiltersBytecodeWritten += bytecode.size();
writeToFile(Data(bytecode.data(), bytecode.size()));
}
void writeActions(Vector<SerializedActionByte>&& actions) override
{
ASSERT(!m_filtersWithoutDomainsBytecodeWritten);
ASSERT(!m_filtersWithDomainBytecodeWritten);
ASSERT(!m_domainFiltersBytecodeWritten);
ASSERT(!m_actionsWritten);
m_actionsWritten += actions.size();
writeToFile(Data(actions.data(), actions.size()));
}
void finalize() override
{
m_metaData.actionsSize = m_actionsWritten;
m_metaData.filtersWithoutDomainsBytecodeSize = m_filtersWithoutDomainsBytecodeWritten;
m_metaData.filtersWithDomainBytecodeSize = m_filtersWithDomainBytecodeWritten;
m_metaData.domainFiltersBytecodeSize = m_domainFiltersBytecodeWritten;
Data header = encodeContentExtensionMetaData(m_metaData);
if (!m_fileError && WebCore::seekFile(m_fileHandle, 0ll, WebCore::FileSeekOrigin::SeekFromBeginning) == -1) {
WebCore::closeFile(m_fileHandle);
m_fileError = true;
}
writeToFile(header);
}
bool hadErrorWhileWritingToFile() { return m_fileError; }
private:
void writeToFile(const Data& data)
{
if (!m_fileError && !writeDataToFile(data, m_fileHandle)) {
WebCore::closeFile(m_fileHandle);
m_fileError = true;
}
}
WebCore::PlatformFileHandle m_fileHandle;
ContentExtensionMetaData& m_metaData;
size_t m_filtersWithoutDomainsBytecodeWritten { 0 };
size_t m_filtersWithDomainBytecodeWritten { 0 };
size_t m_domainFiltersBytecodeWritten { 0 };
size_t m_actionsWritten { 0 };
bool m_fileError { false };
};
auto temporaryFileHandle = WebCore::invalidPlatformFileHandle;
String temporaryFilePath = WebCore::openTemporaryFile("ContentExtension", temporaryFileHandle);
if (temporaryFileHandle == WebCore::invalidPlatformFileHandle)
return UserContentExtensionStore::Error::CompileFailed;
char invalidHeader[ContentExtensionFileHeaderSize];
memset(invalidHeader, 0xFF, sizeof(invalidHeader));
if (WebCore::writeToFile(temporaryFileHandle, invalidHeader, sizeof(invalidHeader)) == -1) {
WebCore::closeFile(temporaryFileHandle);
return UserContentExtensionStore::Error::CompileFailed;
}
CompilationClient compilationClient(temporaryFileHandle, metaData);
if (auto compilerError = compileRuleList(compilationClient, WTFMove(json))) {
WebCore::closeFile(temporaryFileHandle);
return compilerError;
}
if (compilationClient.hadErrorWhileWritingToFile()) {
WebCore::closeFile(temporaryFileHandle);
return UserContentExtensionStore::Error::CompileFailed;
}
mappedData = adoptAndMapFile(temporaryFileHandle, 0, metaData.fileSize());
if (mappedData.isNull())
return UserContentExtensionStore::Error::CompileFailed;
if (!WebCore::moveFile(temporaryFilePath, finalFilePath))
return UserContentExtensionStore::Error::CompileFailed;
return { };
}
static RefPtr<API::UserContentExtension> createExtension(const String& identifier, const ContentExtensionMetaData& metaData, const Data& fileData)
{
auto sharedMemory = WebKit::SharedMemory::create(const_cast<uint8_t*>(fileData.data()), fileData.size(), WebKit::SharedMemory::Protection::ReadOnly);
auto compiledContentExtensionData = WebKit::WebCompiledContentExtensionData(
WTFMove(sharedMemory),
fileData,
ContentExtensionFileHeaderSize,
metaData.actionsSize,
ContentExtensionFileHeaderSize
+ metaData.actionsSize,
metaData.filtersWithoutDomainsBytecodeSize,
ContentExtensionFileHeaderSize
+ metaData.actionsSize
+ metaData.filtersWithoutDomainsBytecodeSize,
metaData.filtersWithDomainBytecodeSize,
ContentExtensionFileHeaderSize
+ metaData.actionsSize
+ metaData.filtersWithoutDomainsBytecodeSize
+ metaData.filtersWithDomainBytecodeSize,
metaData.domainFiltersBytecodeSize
);
auto compiledContentExtension = WebKit::WebCompiledContentExtension::create(WTFMove(compiledContentExtensionData));
return API::UserContentExtension::create(identifier, WTFMove(compiledContentExtension));
}
void UserContentExtensionStore::lookupContentExtension(const WTF::String& identifier, std::function<void(RefPtr<API::UserContentExtension>, std::error_code)> completionHandler)
{
m_readQueue->dispatch([protectedThis = makeRef(*this), identifier = identifier.isolatedCopy(), storePath = m_storePath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
auto path = constructedPath(storePath, identifier);
ContentExtensionMetaData metaData;
Data fileData;
if (!openAndMapContentExtension(path, metaData, fileData)) {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)] {
completionHandler(nullptr, Error::LookupFailed);
});
return;
}
if (metaData.version != UserContentExtensionStore::CurrentContentExtensionFileVersion) {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)] {
completionHandler(nullptr, Error::VersionMismatch);
});
return;
}
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), identifier = WTFMove(identifier), fileData = WTFMove(fileData), metaData = WTFMove(metaData), completionHandler = WTFMove(completionHandler)] {
RefPtr<API::UserContentExtension> userContentExtension = createExtension(identifier, metaData, fileData);
completionHandler(userContentExtension, { });
});
});
}
void UserContentExtensionStore::compileContentExtension(const WTF::String& identifier, WTF::String&& json, std::function<void(RefPtr<API::UserContentExtension>, std::error_code)> completionHandler)
{
m_compileQueue->dispatch([protectedThis = makeRef(*this), identifier = identifier.isolatedCopy(), json = json.isolatedCopy(), storePath = m_storePath.isolatedCopy(), completionHandler = WTFMove(completionHandler)] () mutable {
auto path = constructedPath(storePath, identifier);
ContentExtensionMetaData metaData;
Data fileData;
auto error = compiledToFile(WTFMove(json), path, metaData, fileData);
if (error) {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), error = WTFMove(error), completionHandler = WTFMove(completionHandler)] {
completionHandler(nullptr, error);
});
return;
}
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), identifier = WTFMove(identifier), fileData = WTFMove(fileData), metaData = WTFMove(metaData), completionHandler = WTFMove(completionHandler)] {
RefPtr<API::UserContentExtension> userContentExtension = createExtension(identifier, metaData, fileData);
completionHandler(userContentExtension, { });
});
});
}
void UserContentExtensionStore::removeContentExtension(const WTF::String& identifier, std::function<void(std::error_code)> completionHandler)
{
m_removeQueue->dispatch([protectedThis = makeRef(*this), identifier = identifier.isolatedCopy(), storePath = m_storePath.isolatedCopy(), completionHandler = WTFMove(completionHandler)]() mutable {
auto path = constructedPath(storePath, identifier);
if (!WebCore::deleteFile(path)) {
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)] {
completionHandler(Error::RemoveFailed);
});
return;
}
RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler)] {
completionHandler({ });
});
});
}
void UserContentExtensionStore::synchronousRemoveAllContentExtensions()
{
for (const auto& path : WebCore::listDirectory(m_storePath, "*"))
WebCore::deleteFile(path);
}
void UserContentExtensionStore::invalidateContentExtensionVersion(const WTF::String& identifier)
{
auto file = WebCore::openFile(constructedPath(m_storePath, identifier), WebCore::OpenForWrite);
if (file == WebCore::invalidPlatformFileHandle)
return;
ContentExtensionMetaData invalidHeader = {0, 0, 0, 0, 0};
auto bytesWritten = WebCore::writeToFile(file, reinterpret_cast<const char*>(&invalidHeader), sizeof(invalidHeader));
ASSERT_UNUSED(bytesWritten, bytesWritten == sizeof(invalidHeader));
WebCore::closeFile(file);
}
const std::error_category& userContentExtensionStoreErrorCategory()
{
class UserContentExtensionStoreErrorCategory : public std::error_category {
const char* name() const noexcept override
{
return "user content extension store";
}
std::string message(int errorCode) const override
{
switch (static_cast<UserContentExtensionStore::Error>(errorCode)) {
case UserContentExtensionStore::Error::LookupFailed:
return "Unspecified error during lookup.";
case UserContentExtensionStore::Error::VersionMismatch:
return "Version of file does not match version of interpreter.";
case UserContentExtensionStore::Error::CompileFailed:
return "Unspecified error during compile.";
case UserContentExtensionStore::Error::RemoveFailed:
return "Unspecified error during remove.";
}
return std::string();
}
};
static NeverDestroyed<UserContentExtensionStoreErrorCategory> contentExtensionErrorCategory;
return contentExtensionErrorCategory;
}
}
#endif // ENABLE(CONTENT_EXTENSIONS)