mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-19 07:33:20 +00:00

It currently lives in LibWebView as it was only used for cookies and local storage, both of which are managed in the UI process. Let's move it to its own library now to allow other processes to use it, without having to depend on LibWebView (and therefore LibWeb).
223 lines
8 KiB
C++
223 lines
8 KiB
C++
/*
|
|
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/NonnullOwnPtr.h>
|
|
#include <AK/StdLibExtras.h>
|
|
#include <LibDatabase/Database.h>
|
|
#include <LibWebView/StorageJar.h>
|
|
|
|
namespace WebView {
|
|
|
|
// Quota size is specified in https://storage.spec.whatwg.org/#registered-storage-endpoints
|
|
static constexpr size_t LOCAL_STORAGE_QUOTA = 5 * MiB;
|
|
|
|
ErrorOr<NonnullOwnPtr<StorageJar>> StorageJar::create(Database::Database& database)
|
|
{
|
|
Statements statements {};
|
|
|
|
auto create_table = TRY(database.prepare_statement(R"#(
|
|
CREATE TABLE IF NOT EXISTS WebStorage (
|
|
storage_endpoint INTEGER,
|
|
storage_key TEXT,
|
|
bottle_key TEXT,
|
|
bottle_value TEXT,
|
|
PRIMARY KEY(storage_endpoint, storage_key, bottle_key)
|
|
);)#"sv));
|
|
database.execute_statement(create_table, {});
|
|
|
|
statements.set_item = TRY(database.prepare_statement("INSERT OR REPLACE INTO WebStorage VALUES (?, ?, ?, ?);"sv));
|
|
statements.delete_item = TRY(database.prepare_statement("DELETE FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?;"sv));
|
|
statements.get_item = TRY(database.prepare_statement("SELECT bottle_value FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?;"sv));
|
|
statements.clear = TRY(database.prepare_statement("DELETE FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ?;"sv));
|
|
statements.get_keys = TRY(database.prepare_statement("SELECT bottle_key FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ?;"sv));
|
|
statements.calculate_size_excluding_key = TRY(database.prepare_statement("SELECT SUM(LENGTH(bottle_key) + LENGTH(bottle_value)) FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key != ?;"sv));
|
|
|
|
return adopt_own(*new StorageJar { PersistedStorage { database, statements } });
|
|
}
|
|
|
|
NonnullOwnPtr<StorageJar> StorageJar::create()
|
|
{
|
|
return adopt_own(*new StorageJar { OptionalNone {} });
|
|
}
|
|
|
|
StorageJar::StorageJar(Optional<PersistedStorage> persisted_storage)
|
|
: m_persisted_storage(move(persisted_storage))
|
|
{
|
|
}
|
|
|
|
StorageJar::~StorageJar() = default;
|
|
|
|
Optional<String> StorageJar::get_item(StorageEndpointType storage_endpoint, String const& storage_key, String const& bottle_key)
|
|
{
|
|
StorageLocation storage_location { storage_endpoint, storage_key, bottle_key };
|
|
if (m_persisted_storage.has_value())
|
|
return m_persisted_storage->get_item(storage_location);
|
|
return m_transient_storage.get_item(storage_location);
|
|
}
|
|
|
|
StorageOperationError StorageJar::set_item(StorageEndpointType storage_endpoint, String const& storage_key, String const& bottle_key, String const& bottle_value)
|
|
{
|
|
StorageLocation storage_location { storage_endpoint, storage_key, bottle_key };
|
|
if (m_persisted_storage.has_value())
|
|
return m_persisted_storage->set_item(storage_location, bottle_value);
|
|
return m_transient_storage.set_item(storage_location, bottle_value);
|
|
}
|
|
|
|
void StorageJar::remove_item(StorageEndpointType storage_endpoint, String const& storage_key, String const& key)
|
|
{
|
|
StorageLocation storage_location { storage_endpoint, storage_key, key };
|
|
if (m_persisted_storage.has_value()) {
|
|
m_persisted_storage->delete_item(storage_location);
|
|
} else {
|
|
m_transient_storage.delete_item(storage_location);
|
|
}
|
|
}
|
|
|
|
void StorageJar::clear_storage_key(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
if (m_persisted_storage.has_value()) {
|
|
m_persisted_storage->clear(storage_endpoint, storage_key);
|
|
} else {
|
|
m_transient_storage.clear(storage_endpoint, storage_key);
|
|
}
|
|
}
|
|
|
|
Vector<String> StorageJar::get_all_keys(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
if (m_persisted_storage.has_value())
|
|
return m_persisted_storage->get_keys(storage_endpoint, storage_key);
|
|
return m_transient_storage.get_keys(storage_endpoint, storage_key);
|
|
}
|
|
|
|
StorageOperationError StorageJar::PersistedStorage::set_item(StorageLocation const& key, String const& value)
|
|
{
|
|
size_t current_size = 0;
|
|
database.execute_statement(
|
|
statements.calculate_size_excluding_key,
|
|
[&](auto statement_id) {
|
|
current_size = database.result_column<int>(statement_id, 0);
|
|
},
|
|
static_cast<int>(to_underlying(key.storage_endpoint)),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
|
|
auto new_size = key.bottle_key.bytes().size() + value.bytes().size();
|
|
if (current_size + new_size > LOCAL_STORAGE_QUOTA) {
|
|
return StorageOperationError::QuotaExceededError;
|
|
}
|
|
|
|
database.execute_statement(
|
|
statements.set_item,
|
|
{},
|
|
static_cast<int>(to_underlying(key.storage_endpoint)),
|
|
key.storage_key,
|
|
key.bottle_key,
|
|
value);
|
|
|
|
return StorageOperationError::None;
|
|
}
|
|
|
|
void StorageJar::PersistedStorage::delete_item(StorageLocation const& key)
|
|
{
|
|
database.execute_statement(
|
|
statements.delete_item,
|
|
{},
|
|
static_cast<int>(to_underlying(key.storage_endpoint)),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
}
|
|
|
|
Optional<String> StorageJar::PersistedStorage::get_item(StorageLocation const& key)
|
|
{
|
|
Optional<String> result;
|
|
database.execute_statement(
|
|
statements.get_item,
|
|
[&](auto statement_id) {
|
|
result = database.result_column<String>(statement_id, 0);
|
|
},
|
|
static_cast<int>(to_underlying(key.storage_endpoint)),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
return result;
|
|
}
|
|
|
|
void StorageJar::PersistedStorage::clear(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
database.execute_statement(
|
|
statements.clear,
|
|
{},
|
|
static_cast<int>(to_underlying(storage_endpoint)),
|
|
storage_key);
|
|
}
|
|
|
|
Vector<String> StorageJar::PersistedStorage::get_keys(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
Vector<String> keys;
|
|
database.execute_statement(
|
|
statements.get_keys,
|
|
[&](auto statement_id) {
|
|
keys.append(database.result_column<String>(statement_id, 0));
|
|
},
|
|
static_cast<int>(to_underlying(storage_endpoint)),
|
|
storage_key);
|
|
return keys;
|
|
}
|
|
|
|
StorageOperationError StorageJar::TransientStorage::set_item(StorageLocation const& key, String const& value)
|
|
{
|
|
u64 current_size = 0;
|
|
for (auto const& [existing_key, existing_value] : m_storage_items) {
|
|
if (existing_key.storage_endpoint == key.storage_endpoint && existing_key.storage_key == key.storage_key && existing_key.bottle_key != key.bottle_key) {
|
|
current_size += existing_key.bottle_key.bytes().size();
|
|
current_size += existing_value.bytes().size();
|
|
}
|
|
}
|
|
|
|
auto new_size = key.bottle_key.bytes().size() + value.bytes().size();
|
|
if (current_size + new_size > LOCAL_STORAGE_QUOTA) {
|
|
return StorageOperationError::QuotaExceededError;
|
|
}
|
|
|
|
m_storage_items.set(key, value);
|
|
return StorageOperationError::None;
|
|
}
|
|
|
|
Optional<String> StorageJar::TransientStorage::get_item(StorageLocation const& key)
|
|
{
|
|
if (auto value = m_storage_items.get(key); value.has_value())
|
|
return value.value();
|
|
return OptionalNone {};
|
|
}
|
|
|
|
void StorageJar::TransientStorage::delete_item(StorageLocation const& key)
|
|
{
|
|
m_storage_items.remove(key);
|
|
}
|
|
|
|
void StorageJar::TransientStorage::clear(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
Vector<StorageLocation> keys_to_remove;
|
|
for (auto const& [key, value] : m_storage_items) {
|
|
if (key.storage_endpoint == storage_endpoint && key.storage_key == storage_key)
|
|
keys_to_remove.append(key);
|
|
}
|
|
|
|
for (auto const& key : keys_to_remove) {
|
|
m_storage_items.remove(key);
|
|
}
|
|
}
|
|
|
|
Vector<String> StorageJar::TransientStorage::get_keys(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
Vector<String> keys;
|
|
for (auto const& [key, value] : m_storage_items) {
|
|
if (key.storage_endpoint == storage_endpoint && key.storage_key == storage_key)
|
|
keys.append(key.bottle_key);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
}
|