/* * Copyright (c) 2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include 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> StorageJar::create(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::create() { return adopt_own(*new StorageJar { OptionalNone {} }); } StorageJar::StorageJar(Optional persisted_storage) : m_persisted_storage(move(persisted_storage)) { } StorageJar::~StorageJar() = default; Optional 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 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(statement_id, 0); }, static_cast(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(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(to_underlying(key.storage_endpoint)), key.storage_key, key.bottle_key); } Optional StorageJar::PersistedStorage::get_item(StorageLocation const& key) { Optional result; database.execute_statement( statements.get_item, [&](auto statement_id) { result = database.result_column(statement_id, 0); }, static_cast(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(to_underlying(storage_endpoint)), storage_key); } Vector StorageJar::PersistedStorage::get_keys(StorageEndpointType storage_endpoint, String const& storage_key) { Vector keys; database.execute_statement( statements.get_keys, [&](auto statement_id) { keys.append(database.result_column(statement_id, 0)); }, static_cast(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 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 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 StorageJar::TransientStorage::get_keys(StorageEndpointType storage_endpoint, String const& storage_key) { Vector 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; } }