summaryrefslogtreecommitdiff
path: root/Userland/Applications/Browser
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2023-04-20 14:22:40 -0400
committerAndreas Kling <kling@serenityos.org>2023-04-21 07:56:14 +0200
commita01ad58e91dfdd11e521940ac58b0e2932094ba4 (patch)
tree5efcd7716de88b3bdcc34f95d53606a236a2f159 /Userland/Applications/Browser
parent59a1a3f46384e2711751913b0a6e472f1b6f0842 (diff)
downloadserenity-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.
Diffstat (limited to 'Userland/Applications/Browser')
-rw-r--r--Userland/Applications/Browser/CookieJar.cpp193
-rw-r--r--Userland/Applications/Browser/CookieJar.h40
-rw-r--r--Userland/Applications/Browser/Forward.h25
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>;
+
+}