diff options
author | Timothy Flynn <trflynn89@pm.me> | 2023-04-20 14:22:40 -0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-04-21 07:56:14 +0200 |
commit | a01ad58e91dfdd11e521940ac58b0e2932094ba4 (patch) | |
tree | 5efcd7716de88b3bdcc34f95d53606a236a2f159 | |
parent | 59a1a3f46384e2711751913b0a6e472f1b6f0842 (diff) | |
download | serenity-a01ad58e91dfdd11e521940ac58b0e2932094ba4.zip |
Browser: Add support for CookieJar to run in a transient, in-memory mode
This is to allow running Ladybird without the SQL database for testing.
Primarily, this will let us set 'follow-fork-mode' to 'child' within GDB
to enter the WebContent process, rather than the SQLServer process. But
this is also handy for digging into cookie storage issues.
-rw-r--r-- | Userland/Applications/Browser/CookieJar.cpp | 193 | ||||
-rw-r--r-- | Userland/Applications/Browser/CookieJar.h | 40 | ||||
-rw-r--r-- | Userland/Applications/Browser/Forward.h | 25 |
3 files changed, 185 insertions, 73 deletions
diff --git a/Userland/Applications/Browser/CookieJar.cpp b/Userland/Applications/Browser/CookieJar.cpp index 5aacf01361..38dd10047d 100644 --- a/Userland/Applications/Browser/CookieJar.cpp +++ b/Userland/Applications/Browser/CookieJar.cpp @@ -59,14 +59,24 @@ ErrorOr<CookieJar> CookieJar::create(Database& database) statements.select_cookie = TRY(database.prepare_statement("SELECT * FROM Cookies WHERE ((name = ?) AND (domain = ?) AND (path = ?));"sv)); statements.select_all_cookies = TRY(database.prepare_statement("SELECT * FROM Cookies;"sv)); - return CookieJar { database, move(statements) }; + return CookieJar { PersistedStorage { database, move(statements) } }; } -CookieJar::CookieJar(Database& database, Statements statements) - : m_database(database) - , m_statements(move(statements)) +CookieJar CookieJar::create() +{ + return CookieJar { TransientStorage {} }; +} + +CookieJar::CookieJar(PersistedStorage storage) + : m_storage(move(storage)) +{ + auto& persisted_storage = m_storage.get<PersistedStorage>(); + persisted_storage.database.execute_statement(persisted_storage.statements.create_table, {}, {}, {}); +} + +CookieJar::CookieJar(TransientStorage storage) + : m_storage(move(storage)) { - m_database.execute_statement(m_statements.create_table, {}, {}, {}); } DeprecatedString CookieJar::get_cookie(const URL& url, Web::Cookie::Source source) @@ -488,38 +498,52 @@ static ErrorOr<Web::Cookie::Cookie> parse_cookie(ReadonlySpan<SQL::Value> row) void CookieJar::insert_cookie_into_database(Web::Cookie::Cookie const& cookie) { - m_database.execute_statement( - m_statements.insert_cookie, {}, [this]() { purge_expired_cookies(); }, {}, - cookie.name, - cookie.value, - to_underlying(cookie.same_site), - cookie.creation_time.to_seconds(), - cookie.last_access_time.to_seconds(), - cookie.expiry_time.to_seconds(), - cookie.domain, - cookie.path, - cookie.secure, - cookie.http_only, - cookie.host_only, - cookie.persistent); + m_storage.visit( + [&](PersistedStorage& storage) { + storage.database.execute_statement( + storage.statements.insert_cookie, {}, [this]() { purge_expired_cookies(); }, {}, + cookie.name, + cookie.value, + to_underlying(cookie.same_site), + cookie.creation_time.to_seconds(), + cookie.last_access_time.to_seconds(), + cookie.expiry_time.to_seconds(), + cookie.domain, + cookie.path, + cookie.secure, + cookie.http_only, + cookie.host_only, + cookie.persistent); + }, + [&](TransientStorage& storage) { + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; + storage.set(key, cookie); + }); } void CookieJar::update_cookie_in_database(Web::Cookie::Cookie const& cookie) { - m_database.execute_statement( - m_statements.update_cookie, {}, [this]() { purge_expired_cookies(); }, {}, - cookie.value, - to_underlying(cookie.same_site), - cookie.creation_time.to_seconds(), - cookie.last_access_time.to_seconds(), - cookie.expiry_time.to_seconds(), - cookie.secure, - cookie.http_only, - cookie.host_only, - cookie.persistent, - cookie.name, - cookie.domain, - cookie.path); + m_storage.visit( + [&](PersistedStorage& storage) { + storage.database.execute_statement( + storage.statements.update_cookie, {}, [this]() { purge_expired_cookies(); }, {}, + cookie.value, + to_underlying(cookie.same_site), + cookie.creation_time.to_seconds(), + cookie.last_access_time.to_seconds(), + cookie.expiry_time.to_seconds(), + cookie.secure, + cookie.http_only, + cookie.host_only, + cookie.persistent, + cookie.name, + cookie.domain, + cookie.path); + }, + [&](TransientStorage& storage) { + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; + storage.set(key, cookie); + }); } struct WrappedCookie : public RefCounted<WrappedCookie> { @@ -535,55 +559,88 @@ struct WrappedCookie : public RefCounted<WrappedCookie> { void CookieJar::select_cookie_from_database(Web::Cookie::Cookie cookie, OnCookieFound on_result, OnCookieNotFound on_complete_without_results) { - auto wrapped_cookie = make_ref_counted<WrappedCookie>(move(cookie)); + m_storage.visit( + [&](PersistedStorage& storage) { + auto wrapped_cookie = make_ref_counted<WrappedCookie>(move(cookie)); + + storage.database.execute_statement( + storage.statements.select_cookie, + [on_result = move(on_result), wrapped_cookie = wrapped_cookie](auto row) { + if (auto selected_cookie = parse_cookie(row); selected_cookie.is_error()) + dbgln("Failed to parse cookie '{}': {}", selected_cookie.error(), row); + else + on_result(wrapped_cookie->cookie, selected_cookie.release_value()); + + wrapped_cookie->had_any_results = true; + }, + [on_complete_without_results = move(on_complete_without_results), wrapped_cookie = wrapped_cookie]() { + if (!wrapped_cookie->had_any_results) + on_complete_without_results(move(wrapped_cookie->cookie)); + }, + {}, + wrapped_cookie->cookie.name, + wrapped_cookie->cookie.domain, + wrapped_cookie->cookie.path); + }, + [&](TransientStorage& storage) { + CookieStorageKey key { cookie.name, cookie.domain, cookie.path }; - m_database.execute_statement( - m_statements.select_cookie, - [on_result = move(on_result), wrapped_cookie = wrapped_cookie](auto row) { - if (auto selected_cookie = parse_cookie(row); selected_cookie.is_error()) - dbgln("Failed to parse cookie '{}': {}", selected_cookie.error(), row); + if (auto it = storage.find(key); it != storage.end()) + on_result(cookie, it->value); else - on_result(wrapped_cookie->cookie, selected_cookie.release_value()); - - wrapped_cookie->had_any_results = true; - }, - [on_complete_without_results = move(on_complete_without_results), wrapped_cookie = wrapped_cookie]() { - if (!wrapped_cookie->had_any_results) - on_complete_without_results(move(wrapped_cookie->cookie)); - }, - {}, - wrapped_cookie->cookie.name, - wrapped_cookie->cookie.domain, - wrapped_cookie->cookie.path); + on_complete_without_results(cookie); + }); } void CookieJar::select_all_cookies_from_database(OnSelectAllCookiesResult on_result) { // FIXME: Make surrounding APIs asynchronous. - auto promise = Core::Promise<Empty>::construct(); - - m_database.execute_statement( - m_statements.select_all_cookies, - [on_result = move(on_result)](auto row) { - if (auto cookie = parse_cookie(row); cookie.is_error()) - dbgln("Failed to parse cookie '{}': {}", cookie.error(), row); - else - on_result(cookie.release_value()); - }, - [&]() { - MUST(promise->resolve({})); + m_storage.visit( + [&](PersistedStorage& storage) { + auto promise = Core::Promise<Empty>::construct(); + + storage.database.execute_statement( + storage.statements.select_all_cookies, + [on_result = move(on_result)](auto row) { + if (auto cookie = parse_cookie(row); cookie.is_error()) + dbgln("Failed to parse cookie '{}': {}", cookie.error(), row); + else + on_result(cookie.release_value()); + }, + [&]() { + MUST(promise->resolve({})); + }, + [&](auto) { + MUST(promise->resolve({})); + }); + + MUST(promise->await()); }, - [&](auto) { - MUST(promise->resolve({})); + [&](TransientStorage& storage) { + for (auto const& cookie : storage) + on_result(cookie.value); }); - - MUST(promise->await()); } void CookieJar::purge_expired_cookies() { auto now = Time::now_realtime().to_seconds(); - m_database.execute_statement(m_statements.expire_cookie, {}, {}, {}, now); + + m_storage.visit( + [&](PersistedStorage& storage) { + storage.database.execute_statement(storage.statements.expire_cookie, {}, {}, {}, now); + }, + [&](TransientStorage& storage) { + Vector<CookieStorageKey> keys_to_evict; + + for (auto const& cookie : storage) { + if (cookie.value.expiry_time.to_seconds() < now) + keys_to_evict.append(cookie.key); + } + + for (auto const& key : keys_to_evict) + storage.remove(key); + }); } } diff --git a/Userland/Applications/Browser/CookieJar.h b/Userland/Applications/Browser/CookieJar.h index 8c9e7d7854..ab5d6b8603 100644 --- a/Userland/Applications/Browser/CookieJar.h +++ b/Userland/Applications/Browser/CookieJar.h @@ -1,15 +1,17 @@ /* - * Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org> + * Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include "Forward.h" #include <AK/DeprecatedString.h> #include <AK/Function.h> #include <AK/HashMap.h> #include <AK/Optional.h> +#include <AK/Traits.h> #include <LibCore/DateTime.h> #include <LibSQL/Type.h> #include <LibWeb/Cookie/Cookie.h> @@ -19,6 +21,14 @@ namespace Browser { class Database; +struct CookieStorageKey { + bool operator==(CookieStorageKey const&) const = default; + + DeprecatedString name; + DeprecatedString domain; + DeprecatedString path; +}; + class CookieJar { struct Statements { SQL::StatementID create_table { 0 }; @@ -29,8 +39,16 @@ class CookieJar { SQL::StatementID select_all_cookies { 0 }; }; + struct PersistedStorage { + Database& database; + Statements statements; + }; + + using TransientStorage = HashMap<CookieStorageKey, Web::Cookie::Cookie>; + public: - static ErrorOr<CookieJar> create(Database& database); + static ErrorOr<CookieJar> create(Database&); + static CookieJar create(); DeprecatedString get_cookie(const URL& url, Web::Cookie::Source source); void set_cookie(const URL& url, Web::Cookie::ParsedCookie const& parsed_cookie, Web::Cookie::Source source); @@ -41,7 +59,8 @@ public: Optional<Web::Cookie::Cookie> get_named_cookie(URL const& url, DeprecatedString const& name); private: - CookieJar(Database& database, Statements statements); + explicit CookieJar(PersistedStorage); + explicit CookieJar(TransientStorage); static Optional<DeprecatedString> canonicalize_domain(const URL& url); static bool domain_matches(DeprecatedString const& string, DeprecatedString const& domain_string); @@ -68,8 +87,19 @@ private: void purge_expired_cookies(); - Database& m_database; - Statements m_statements; + Variant<PersistedStorage, TransientStorage> m_storage; }; } + +template<> +struct AK::Traits<Browser::CookieStorageKey> : public AK::GenericTraits<Browser::CookieStorageKey> { + static unsigned hash(Browser::CookieStorageKey const& key) + { + unsigned hash = 0; + hash = pair_int_hash(hash, string_hash(key.name.characters(), key.name.length())); + hash = pair_int_hash(hash, string_hash(key.domain.characters(), key.domain.length())); + hash = pair_int_hash(hash, string_hash(key.path.characters(), key.path.length())); + return hash; + } +}; diff --git a/Userland/Applications/Browser/Forward.h b/Userland/Applications/Browser/Forward.h new file mode 100644 index 0000000000..e882af1680 --- /dev/null +++ b/Userland/Applications/Browser/Forward.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Traits.h> + +namespace Browser { + +class CookieJar; +class Database; + +struct CookieStorageKey; + +} + +namespace AK { + +template<> +struct Traits<Browser::CookieStorageKey>; + +} |