1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:24:57 +00:00

Browser+LibWeb+WebContent: Store cookie expiry times in UTC

We are currently converting parsed expiry times to local time, whereas
the RFC dictates we parse them as UTC. When expiring cookies, we must
also use the current UTC time to compare against the cookies' expiry
times.
This commit is contained in:
Timothy Flynn 2023-02-24 11:51:56 -05:00 committed by Tim Flynn
parent 1858163d3c
commit 87c4080d00
8 changed files with 83 additions and 47 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
*
@ -11,6 +11,7 @@
#include <AK/IPv4Address.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Time.h>
#include <AK/URL.h>
#include <AK/Vector.h>
#include <LibCore/Promise.h>
@ -140,9 +141,9 @@ void CookieJar::dump_cookies()
builder.appendff("{}{}{}\n", key_color, cookie.path, no_color);
builder.appendff("\t{}Value{} = {}\n", attribute_color, no_color, cookie.value);
builder.appendff("\t{}CreationTime{} = {}\n", attribute_color, no_color, cookie.creation_time.to_deprecated_string());
builder.appendff("\t{}LastAccessTime{} = {}\n", attribute_color, no_color, cookie.last_access_time.to_deprecated_string());
builder.appendff("\t{}ExpiryTime{} = {}\n", attribute_color, no_color, cookie.expiry_time.to_deprecated_string());
builder.appendff("\t{}CreationTime{} = {}\n", attribute_color, no_color, cookie.creation_time_to_string());
builder.appendff("\t{}LastAccessTime{} = {}\n", attribute_color, no_color, cookie.last_access_time_to_string());
builder.appendff("\t{}ExpiryTime{} = {}\n", attribute_color, no_color, cookie.expiry_time_to_string());
builder.appendff("\t{}Secure{} = {:s}\n", attribute_color, no_color, cookie.secure);
builder.appendff("\t{}HttpOnly{} = {:s}\n", attribute_color, no_color, cookie.http_only);
builder.appendff("\t{}HostOnly{} = {:s}\n", attribute_color, no_color, cookie.host_only);
@ -275,7 +276,7 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con
// 2. Create a new cookie with name cookie-name, value cookie-value. Set the creation-time and the last-access-time to the current date and time.
Web::Cookie::Cookie cookie { parsed_cookie.name, parsed_cookie.value, parsed_cookie.same_site_attribute };
cookie.creation_time = Core::DateTime::now();
cookie.creation_time = Time::now_realtime();
cookie.last_access_time = cookie.creation_time;
if (parsed_cookie.expiry_time_from_max_age_attribute.has_value()) {
@ -289,9 +290,9 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con
cookie.persistent = true;
cookie.expiry_time = parsed_cookie.expiry_time_from_expires_attribute.value();
} else {
// Set the cookie's persistent-flag to false. Set the cookie's expiry-time to the latest representable gddate.
// Set the cookie's persistent-flag to false. Set the cookie's expiry-time to the latest representable date.
cookie.persistent = false;
cookie.expiry_time = Core::DateTime::create(9999, 12, 31, 23, 59, 59);
cookie.expiry_time = Time::max();
}
// 4. If the cookie-attribute-list contains an attribute with an attribute-name of "Domain":
@ -396,13 +397,13 @@ Vector<Web::Cookie::Cookie> CookieJar::get_matching_cookies(const URL& url, Depr
// - Cookies with longer paths are listed before cookies with shorter paths.
// - Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times.
auto cookie_path_length = cookie.path.length();
auto cookie_creation_time = cookie.creation_time.timestamp();
auto cookie_creation_time = cookie.creation_time;
cookie_list.insert_before_matching(move(cookie), [cookie_path_length, cookie_creation_time](auto const& entry) {
if (cookie_path_length > entry.path.length()) {
return true;
} else if (cookie_path_length == entry.path.length()) {
if (cookie_creation_time < entry.creation_time.timestamp())
if (cookie_creation_time < entry.creation_time)
return true;
}
return false;
@ -410,7 +411,7 @@ Vector<Web::Cookie::Cookie> CookieJar::get_matching_cookies(const URL& url, Depr
});
// 3. Update the last-access-time of each cookie in the cookie-list to the current date and time.
auto now = Core::DateTime::now();
auto now = Time::now_realtime();
for (auto& cookie : cookie_list) {
cookie.last_access_time = now;
@ -450,8 +451,8 @@ static ErrorOr<Web::Cookie::Cookie> parse_cookie(ReadonlySpan<SQL::Value> row)
if (value.type() != SQL::SQLType::Integer)
return Error::from_string_view(name);
auto time = value.to_int<time_t>().value();
field = Core::DateTime::from_timestamp(time);
auto time = value.to_int<i64>().value();
field = Time::from_seconds(time);
return {};
};
@ -492,9 +493,9 @@ void CookieJar::insert_cookie_into_database(Web::Cookie::Cookie const& cookie)
cookie.name,
cookie.value,
to_underlying(cookie.same_site),
cookie.creation_time.timestamp(),
cookie.last_access_time.timestamp(),
cookie.expiry_time.timestamp(),
cookie.creation_time.to_seconds(),
cookie.last_access_time.to_seconds(),
cookie.expiry_time.to_seconds(),
cookie.domain,
cookie.path,
cookie.secure,
@ -509,9 +510,9 @@ void CookieJar::update_cookie_in_database(Web::Cookie::Cookie const& cookie)
m_statements.update_cookie, {}, [this]() { purge_expired_cookies(); }, {},
cookie.value,
to_underlying(cookie.same_site),
cookie.creation_time.timestamp(),
cookie.last_access_time.timestamp(),
cookie.expiry_time.timestamp(),
cookie.creation_time.to_seconds(),
cookie.last_access_time.to_seconds(),
cookie.expiry_time.to_seconds(),
cookie.secure,
cookie.http_only,
cookie.host_only,
@ -581,7 +582,8 @@ void CookieJar::select_all_cookies_from_database(OnSelectAllCookiesResult on_res
void CookieJar::purge_expired_cookies()
{
auto now = Core::DateTime::now().timestamp();
auto now = Time::now_realtime().to_seconds();
m_database.execute_statement(m_statements.expire_cookie, {}, {}, {}, now);
}
}

View file

@ -80,7 +80,7 @@ GUI::Variant CookiesModel::data(GUI::ModelIndex const& index, GUI::ModelRole rol
case Column::Value:
return cookie.value;
case Column::ExpiryTime:
return cookie.expiry_time.to_deprecated_string();
return cookie.expiry_time_to_string();
case Column::SameSite:
return Web::Cookie::same_site_to_string(cookie.same_site);
}

View file

@ -130,7 +130,7 @@ void StorageWidget::clear_session_storage_entries()
void StorageWidget::delete_cookie(Web::Cookie::Cookie cookie)
{
// Delete cookie by making its expiry time in the past.
cookie.expiry_time = Core::DateTime::from_timestamp(0);
cookie.expiry_time = Time::from_seconds(0);
if (on_update_cookie)
on_update_cookie(move(cookie));
}

View file

@ -1,15 +1,38 @@
/*
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Cookie.h"
#include <LibCore/DateTime.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace Web::Cookie {
static DeprecatedString time_to_string(Time const& time)
{
auto local_time = Core::DateTime::from_timestamp(time.to_seconds());
return local_time.to_deprecated_string("%Y-%m-%d %H:%M:%S %Z"sv);
}
DeprecatedString Cookie::creation_time_to_string() const
{
return time_to_string(creation_time);
}
DeprecatedString Cookie::last_access_time_to_string() const
{
return time_to_string(last_access_time);
}
DeprecatedString Cookie::expiry_time_to_string() const
{
return time_to_string(expiry_time);
}
StringView same_site_to_string(SameSite same_site)
{
switch (same_site) {
@ -64,11 +87,11 @@ ErrorOr<Web::Cookie::Cookie> IPC::decode(Decoder& decoder)
auto value = TRY(decoder.decode<DeprecatedString>());
auto domain = TRY(decoder.decode<DeprecatedString>());
auto path = TRY(decoder.decode<DeprecatedString>());
auto creation_time = TRY(decoder.decode<Core::DateTime>());
auto expiry_time = TRY(decoder.decode<Core::DateTime>());
auto creation_time = TRY(decoder.decode<Time>());
auto expiry_time = TRY(decoder.decode<Time>());
auto host_only = TRY(decoder.decode<bool>());
auto http_only = TRY(decoder.decode<bool>());
auto last_access_time = TRY(decoder.decode<Core::DateTime>());
auto last_access_time = TRY(decoder.decode<Time>());
auto persistent = TRY(decoder.decode<bool>());
auto secure = TRY(decoder.decode<bool>());
auto same_site = TRY(decoder.decode<Web::Cookie::SameSite>());

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,7 +7,7 @@
#pragma once
#include <AK/DeprecatedString.h>
#include <LibCore/DateTime.h>
#include <AK/Time.h>
#include <LibIPC/Forward.h>
namespace Web::Cookie {
@ -25,12 +25,16 @@ enum class Source {
};
struct Cookie {
DeprecatedString creation_time_to_string() const;
DeprecatedString last_access_time_to_string() const;
DeprecatedString expiry_time_to_string() const;
DeprecatedString name;
DeprecatedString value;
SameSite same_site;
Core::DateTime creation_time {};
Core::DateTime last_access_time {};
Core::DateTime expiry_time {};
Time creation_time {};
Time last_access_time {};
Time expiry_time {};
DeprecatedString domain {};
DeprecatedString path {};
bool secure { false };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,6 +8,7 @@
#include <AK/DateConstants.h>
#include <AK/Function.h>
#include <AK/StdLibExtras.h>
#include <AK/Time.h>
#include <AK/Vector.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
@ -26,7 +27,7 @@ static void on_path_attribute(ParsedCookie& parsed_cookie, StringView attribute_
static void on_secure_attribute(ParsedCookie& parsed_cookie);
static void on_http_only_attribute(ParsedCookie& parsed_cookie);
static void on_same_site_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
static Optional<Core::DateTime> parse_date_time(StringView date_string);
static Optional<Time> parse_date_time(StringView date_string);
Optional<ParsedCookie> parse_cookie(DeprecatedString const& cookie_string)
{
@ -153,7 +154,7 @@ void on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_valu
{
// https://tools.ietf.org/html/rfc6265#section-5.2.1
if (auto expiry_time = parse_date_time(attribute_value); expiry_time.has_value())
parsed_cookie.expiry_time_from_expires_attribute = move(*expiry_time);
parsed_cookie.expiry_time_from_expires_attribute = expiry_time.release_value();
}
void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
@ -168,11 +169,10 @@ void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_valu
if (auto delta_seconds = attribute_value.to_int(); delta_seconds.has_value()) {
if (*delta_seconds <= 0) {
// If delta-seconds is less than or equal to zero (0), let expiry-time be the earliest representable date and time.
parsed_cookie.expiry_time_from_max_age_attribute = Core::DateTime::from_timestamp(0);
parsed_cookie.expiry_time_from_max_age_attribute = Time::min();
} else {
// Otherwise, let the expiry-time be the current date and time plus delta-seconds seconds.
time_t now = Core::DateTime::now().timestamp();
parsed_cookie.expiry_time_from_max_age_attribute = Core::DateTime::from_timestamp(now + *delta_seconds);
parsed_cookie.expiry_time_from_max_age_attribute = Time::now_realtime() + Time::from_seconds(*delta_seconds);
}
}
}
@ -236,7 +236,7 @@ void on_same_site_attribute(ParsedCookie& parsed_cookie, StringView attribute_va
parsed_cookie.same_site_attribute = same_site_from_string(attribute_value);
}
Optional<Core::DateTime> parse_date_time(StringView date_string)
Optional<Time> parse_date_time(StringView date_string)
{
// https://tools.ietf.org/html/rfc6265#section-5.1.1
unsigned hour = 0;
@ -341,8 +341,14 @@ Optional<Core::DateTime> parse_date_time(StringView date_string)
if (second > 59)
return {};
// 6. Let the parsed-cookie-date be the date whose day-of-month, month, year, hour, minute, and second (in UTC) are the
// day-of-month-value, the month-value, the year-value, the hour-value, the minute-value, and the second-value, respectively.
// If no such date exists, abort these steps and fail to parse the cookie-date.
// FIXME: Fail on dates that do not exist.
return Core::DateTime::create(year, month, day_of_month, hour, minute, second);
auto parsed_cookie_date = Time::from_timestamp(year, month, day_of_month, hour, minute, second, 0);
// 7. Return the parsed-cookie-date as the result of this algorithm.
return parsed_cookie_date;
}
}
@ -368,8 +374,8 @@ ErrorOr<Web::Cookie::ParsedCookie> IPC::decode(Decoder& decoder)
{
auto name = TRY(decoder.decode<DeprecatedString>());
auto value = TRY(decoder.decode<DeprecatedString>());
auto expiry_time_from_expires_attribute = TRY(decoder.decode<Optional<Core::DateTime>>());
auto expiry_time_from_max_age_attribute = TRY(decoder.decode<Optional<Core::DateTime>>());
auto expiry_time_from_expires_attribute = TRY(decoder.decode<Optional<Time>>());
auto expiry_time_from_max_age_attribute = TRY(decoder.decode<Optional<Time>>());
auto domain = TRY(decoder.decode<Optional<DeprecatedString>>());
auto path = TRY(decoder.decode<Optional<DeprecatedString>>());
auto secure_attribute_present = TRY(decoder.decode<bool>());

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,7 +8,7 @@
#include <AK/DeprecatedString.h>
#include <AK/Optional.h>
#include <LibCore/DateTime.h>
#include <AK/Time.h>
#include <LibIPC/Forward.h>
#include <LibWeb/Cookie/Cookie.h>
@ -18,8 +18,8 @@ struct ParsedCookie {
DeprecatedString name;
DeprecatedString value;
SameSite same_site_attribute { SameSite::Default };
Optional<Core::DateTime> expiry_time_from_expires_attribute {};
Optional<Core::DateTime> expiry_time_from_max_age_attribute {};
Optional<Time> expiry_time_from_expires_attribute {};
Optional<Time> expiry_time_from_max_age_attribute {};
Optional<DeprecatedString> domain {};
Optional<DeprecatedString> path {};
bool secure_attribute_present { false };

View file

@ -3,13 +3,14 @@
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/Time.h>
#include <AK/Vector.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/Value.h>
@ -54,7 +55,7 @@ static JsonValue serialize_cookie(Web::Cookie::Cookie const& cookie)
serialized_cookie.set("domain"sv, cookie.domain);
serialized_cookie.set("secure"sv, cookie.secure);
serialized_cookie.set("httpOnly"sv, cookie.http_only);
serialized_cookie.set("expiry"sv, cookie.expiry_time.timestamp());
serialized_cookie.set("expiry"sv, cookie.expiry_time.to_seconds());
serialized_cookie.set("sameSite"sv, Web::Cookie::same_site_to_string(cookie.same_site));
return serialized_cookie;
@ -1593,7 +1594,7 @@ Messages::WebDriverClient::AddCookieResponse WebDriverConnection::add_cookie(Jso
if (data.has("expiry"sv)) {
// NOTE: less than 0 or greater than safe integer are handled by the JSON parser
auto expiry = TRY(get_property<u32>(data, "expiry"sv));
cookie.expiry_time_from_expires_attribute = Core::DateTime::from_timestamp(expiry);
cookie.expiry_time_from_expires_attribute = Time::from_seconds(expiry);
}
// Cookie same site
@ -1985,7 +1986,7 @@ void WebDriverConnection::delete_cookies(Optional<StringView> const& name)
// -> name is equal to cookie name
if (!name.has_value() || name.value() == cookie.name) {
// Set the cookie expiry time to a Unix timestamp in the past.
cookie.expiry_time = Core::DateTime::from_timestamp(0);
cookie.expiry_time = Time::from_seconds(0);
m_page_client.page_did_update_cookie(move(cookie));
}
// -> Otherwise