mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 11:57:35 +00:00
Browser: Synchronize database statements used for cookies
Since SQLServer is inherently asynchronous, database statements can run in parallel. Our `CookieJar` did not require synchronous actions on the database for its cookies, resulting in cookies not being set immediately. This resulted in a bug that could be exposed by setting `document.cookie` and immediately querying its value, revealing that the cookie was not yet persisted. Solve this by requiring all database statements to be executed synchronously. Ideally SQLServer has a mechanism to determine interquery dependencies and blocks until dependent queries are fully executed, but until we have that, this works around that limitation.
This commit is contained in:
parent
568c486610
commit
14091f32c6
2 changed files with 26 additions and 14 deletions
|
@ -2,6 +2,7 @@
|
||||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||||
* Copyright (c) 2022, the SerenityOS developers.
|
* Copyright (c) 2022, the SerenityOS developers.
|
||||||
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
|
||||||
|
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -349,11 +350,14 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con
|
||||||
if (source != Web::Cookie::Source::Http && cookie.http_only)
|
if (source != Web::Cookie::Source::Http && cookie.http_only)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Synchronize persisting the cookie
|
||||||
|
auto sync_promise = Core::Promise<Empty>::construct();
|
||||||
|
|
||||||
select_cookie_from_database(
|
select_cookie_from_database(
|
||||||
move(cookie),
|
move(cookie),
|
||||||
|
|
||||||
// 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
|
// 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
|
||||||
[this, source](auto& cookie, auto old_cookie) {
|
[this, source, sync_promise](auto& cookie, auto old_cookie) {
|
||||||
// If the newly created cookie was received from a "non-HTTP" API and the old-cookie's http-only-flag is set, abort these
|
// If the newly created cookie was received from a "non-HTTP" API and the old-cookie's http-only-flag is set, abort these
|
||||||
// steps and ignore the newly created cookie entirely.
|
// steps and ignore the newly created cookie entirely.
|
||||||
if (source != Web::Cookie::Source::Http && old_cookie.http_only)
|
if (source != Web::Cookie::Source::Http && old_cookie.http_only)
|
||||||
|
@ -365,12 +369,16 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con
|
||||||
// Remove the old-cookie from the cookie store.
|
// Remove the old-cookie from the cookie store.
|
||||||
// NOTE: Rather than deleting then re-inserting this cookie, we update it in-place.
|
// NOTE: Rather than deleting then re-inserting this cookie, we update it in-place.
|
||||||
update_cookie_in_database(cookie);
|
update_cookie_in_database(cookie);
|
||||||
|
sync_promise->resolve({});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 12. Insert the newly created cookie into the cookie store.
|
// 12. Insert the newly created cookie into the cookie store.
|
||||||
[this](auto cookie) {
|
[this, sync_promise](auto cookie) {
|
||||||
insert_cookie_into_database(cookie);
|
insert_cookie_into_database(cookie);
|
||||||
|
sync_promise->resolve({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
MUST(sync_promise->await());
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Web::Cookie::Cookie> CookieJar::get_matching_cookies(const URL& url, DeprecatedString const& canonicalized_domain, Web::Cookie::Source source, MatchingCookiesSpecMode mode)
|
Vector<Web::Cookie::Cookie> CookieJar::get_matching_cookies(const URL& url, DeprecatedString const& canonicalized_domain, Web::Cookie::Source source, MatchingCookiesSpecMode mode)
|
||||||
|
@ -600,8 +608,6 @@ void CookieJar::select_all_cookies_from_database(OnSelectAllCookiesResult on_res
|
||||||
// FIXME: Make surrounding APIs asynchronous.
|
// FIXME: Make surrounding APIs asynchronous.
|
||||||
m_storage.visit(
|
m_storage.visit(
|
||||||
[&](PersistedStorage& storage) {
|
[&](PersistedStorage& storage) {
|
||||||
auto promise = Core::Promise<Empty>::construct();
|
|
||||||
|
|
||||||
storage.database.execute_statement(
|
storage.database.execute_statement(
|
||||||
storage.statements.select_all_cookies,
|
storage.statements.select_all_cookies,
|
||||||
[on_result = move(on_result)](auto row) {
|
[on_result = move(on_result)](auto row) {
|
||||||
|
@ -610,14 +616,8 @@ void CookieJar::select_all_cookies_from_database(OnSelectAllCookiesResult on_res
|
||||||
else
|
else
|
||||||
on_result(cookie.release_value());
|
on_result(cookie.release_value());
|
||||||
},
|
},
|
||||||
[&]() {
|
{},
|
||||||
promise->resolve({});
|
{});
|
||||||
},
|
|
||||||
[&](auto) {
|
|
||||||
promise->resolve({});
|
|
||||||
});
|
|
||||||
|
|
||||||
MUST(promise->await());
|
|
||||||
},
|
},
|
||||||
[&](TransientStorage& storage) {
|
[&](TransientStorage& storage) {
|
||||||
for (auto const& cookie : storage)
|
for (auto const& cookie : storage)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||||
|
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -14,6 +15,7 @@
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibCore/Promise.h>
|
||||||
#include <LibSQL/SQLClient.h>
|
#include <LibSQL/SQLClient.h>
|
||||||
#include <LibSQL/Type.h>
|
#include <LibSQL/Type.h>
|
||||||
#include <LibSQL/Value.h>
|
#include <LibSQL/Value.h>
|
||||||
|
@ -34,14 +36,24 @@ public:
|
||||||
template<typename... PlaceholderValues>
|
template<typename... PlaceholderValues>
|
||||||
void execute_statement(SQL::StatementID statement_id, OnResult on_result, OnComplete on_complete, OnError on_error, PlaceholderValues&&... placeholder_values)
|
void execute_statement(SQL::StatementID statement_id, OnResult on_result, OnComplete on_complete, OnError on_error, PlaceholderValues&&... placeholder_values)
|
||||||
{
|
{
|
||||||
|
auto sync_promise = Core::Promise<Empty>::construct();
|
||||||
|
|
||||||
PendingExecution pending_execution {
|
PendingExecution pending_execution {
|
||||||
.on_result = move(on_result),
|
.on_result = move(on_result),
|
||||||
.on_complete = move(on_complete),
|
.on_complete = [sync_promise, on_complete = move(on_complete)] {
|
||||||
.on_error = move(on_error),
|
if (on_complete)
|
||||||
|
on_complete();
|
||||||
|
sync_promise->resolve({}); },
|
||||||
|
.on_error = [sync_promise, on_error = move(on_error)](auto message) {
|
||||||
|
if (on_error)
|
||||||
|
on_error(message);
|
||||||
|
sync_promise->resolve({}); },
|
||||||
};
|
};
|
||||||
|
|
||||||
Vector<SQL::Value> values { SQL::Value(forward<PlaceholderValues>(placeholder_values))... };
|
Vector<SQL::Value> values { SQL::Value(forward<PlaceholderValues>(placeholder_values))... };
|
||||||
execute_statement(statement_id, move(values), move(pending_execution));
|
execute_statement(statement_id, move(values), move(pending_execution));
|
||||||
|
|
||||||
|
MUST(sync_promise->await());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue