mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
288 lines
10 KiB
C++
288 lines
10 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;
|
|
|
|
// Increment this version when needing to alter the WebStorage schema.
|
|
static constexpr u32 WEB_STORAGE_VERSION = 2u;
|
|
|
|
static constexpr u32 WEB_STORAGE_METADATA_KEY = 12389u;
|
|
|
|
ErrorOr<NonnullOwnPtr<StorageJar>> StorageJar::create(Database::Database& database)
|
|
{
|
|
Statements statements {};
|
|
|
|
auto create_metadata_table = TRY(database.prepare_statement(R"#(
|
|
CREATE TABLE IF NOT EXISTS WebStorageMetadata (
|
|
metadata_key INTEGER,
|
|
version INTEGER,
|
|
PRIMARY KEY(metadata_key)
|
|
);
|
|
)#"sv));
|
|
database.execute_statement(create_metadata_table, {});
|
|
|
|
auto create_storage_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_storage_table, {});
|
|
|
|
auto read_storage_version = TRY(database.prepare_statement("SELECT version FROM WebStorageMetadata WHERE metadata_key = ?;"sv));
|
|
auto storage_version = 0u;
|
|
|
|
database.execute_statement(
|
|
read_storage_version,
|
|
[&](auto statement_id) { storage_version = database.result_column<u32>(statement_id, 0); },
|
|
WEB_STORAGE_METADATA_KEY);
|
|
|
|
if (storage_version != WEB_STORAGE_VERSION)
|
|
TRY(upgrade_database(database, storage_version));
|
|
|
|
statements.get_item = TRY(database.prepare_statement("SELECT bottle_value FROM WebStorage WHERE storage_endpoint = ? AND storage_key = ? AND bottle_key = ?;"sv));
|
|
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.update_last_access_time = TRY(database.prepare_statement("UPDATE WebStorage SET last_access_time = ? 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;
|
|
|
|
ErrorOr<void> StorageJar::upgrade_database(Database::Database& database, u32 current_version)
|
|
{
|
|
// Track the version numbers for each schema change:
|
|
static constexpr u32 VERSION_ADDED_LAST_ACCESS_TIME = 2u;
|
|
|
|
if (current_version < VERSION_ADDED_LAST_ACCESS_TIME) {
|
|
auto add_last_access_time = TRY(database.prepare_statement("ALTER TABLE WebStorage ADD COLUMN last_access_time INTEGER;"sv));
|
|
database.execute_statement(add_last_access_time, {});
|
|
|
|
auto set_last_access_time = TRY(database.prepare_statement("UPDATE WebStorage SET last_access_time = ?;"sv));
|
|
database.execute_statement(set_last_access_time, {}, UnixDateTime::now());
|
|
}
|
|
|
|
auto set_storage_version = TRY(database.prepare_statement("INSERT OR REPLACE INTO WebStorageMetadata VALUES (?, ?);"sv));
|
|
database.execute_statement(set_storage_version, {}, WEB_STORAGE_METADATA_KEY, WEB_STORAGE_VERSION);
|
|
|
|
return {};
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Optional<String> StorageJar::TransientStorage::get_item(StorageLocation const& key)
|
|
{
|
|
if (auto entry = m_storage_items.get(key); entry.has_value()) {
|
|
entry->last_access_time = UnixDateTime::now();
|
|
return entry->value;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
StorageOperationError StorageJar::TransientStorage::set_item(StorageLocation const& key, String const& value)
|
|
{
|
|
u64 current_size = 0;
|
|
|
|
for (auto const& [existing_key, existing_entry] : 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_entry.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, UnixDateTime::now() });
|
|
return StorageOperationError::None;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
},
|
|
to_underlying(key.storage_endpoint),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
|
|
if (result.has_value()) {
|
|
database.execute_statement(
|
|
statements.update_last_access_time,
|
|
{},
|
|
UnixDateTime::now(),
|
|
to_underlying(key.storage_endpoint),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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);
|
|
},
|
|
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,
|
|
{},
|
|
to_underlying(key.storage_endpoint),
|
|
key.storage_key,
|
|
key.bottle_key,
|
|
value,
|
|
UnixDateTime::now());
|
|
|
|
return StorageOperationError::None;
|
|
}
|
|
|
|
void StorageJar::PersistedStorage::delete_item(StorageLocation const& key)
|
|
{
|
|
database.execute_statement(
|
|
statements.delete_item,
|
|
{},
|
|
to_underlying(key.storage_endpoint),
|
|
key.storage_key,
|
|
key.bottle_key);
|
|
}
|
|
|
|
void StorageJar::PersistedStorage::clear(StorageEndpointType storage_endpoint, String const& storage_key)
|
|
{
|
|
database.execute_statement(
|
|
statements.clear,
|
|
{},
|
|
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));
|
|
},
|
|
to_underlying(storage_endpoint),
|
|
storage_key);
|
|
|
|
return keys;
|
|
}
|
|
|
|
}
|