2021-04-11 10:43:54 -04:00
/*
2024-01-26 10:47:57 -05:00
* Copyright ( c ) 2021 - 2024 , Tim Flynn < trflynn89 @ serenityos . org >
2022-02-09 17:45:15 -07:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2022-10-16 19:48:19 +02:00
* Copyright ( c ) 2022 , Tobias Christiansen < tobyase @ serenityos . org >
2023-08-07 23:33:01 +02:00
* Copyright ( c ) 2023 , Jelle Raaijmakers < jelle @ gmta . nl >
2021-04-11 10:43:54 -04:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2021-04-11 10:43:54 -04:00
*/
2021-04-12 23:16:27 -04:00
# include <AK/IPv4Address.h>
2021-04-12 12:12:13 -04:00
# include <AK/StringBuilder.h>
2023-02-24 11:51:56 -05:00
# include <AK/Time.h>
2021-04-11 10:43:54 -04:00
# include <AK/URL.h>
2021-04-12 23:16:27 -04:00
# include <AK/Vector.h>
2022-10-27 12:56:22 -04:00
# include <LibCore/Promise.h>
# include <LibSQL/TupleDescriptor.h>
# include <LibSQL/Value.h>
2021-04-13 16:47:05 -04:00
# include <LibWeb/Cookie/ParsedCookie.h>
2023-08-31 07:07:07 -04:00
# include <LibWebView/CookieJar.h>
# include <LibWebView/Database.h>
2023-10-20 10:45:30 -04:00
# include <LibWebView/URL.h>
2021-04-11 10:43:54 -04:00
2023-08-31 07:07:07 -04:00
namespace WebView {
2021-04-11 10:43:54 -04:00
2022-10-27 12:56:22 -04:00
ErrorOr < CookieJar > CookieJar : : create ( Database & database )
{
Statements statements { } ;
statements . create_table = TRY ( database . prepare_statement ( R " #(
CREATE TABLE IF NOT EXISTS Cookies (
name TEXT ,
value TEXT ,
same_site INTEGER ,
creation_time INTEGER ,
last_access_time INTEGER ,
expiry_time INTEGER ,
domain TEXT ,
path TEXT ,
secure BOOLEAN ,
http_only BOOLEAN ,
host_only BOOLEAN ,
persistent BOOLEAN
) ; ) # " sv));
statements . update_cookie = TRY ( database . prepare_statement ( R " #(
UPDATE Cookies SET
value = ? ,
same_site = ? ,
creation_time = ? ,
last_access_time = ? ,
expiry_time = ? ,
secure = ? ,
http_only = ? ,
host_only = ? ,
persistent = ?
WHERE ( ( name = ? ) AND ( domain = ? ) AND ( path = ? ) ) ; ) # " sv));
statements . insert_cookie = TRY ( database . prepare_statement ( " INSERT INTO Cookies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); " sv ) ) ;
statements . expire_cookie = TRY ( database . prepare_statement ( " DELETE FROM Cookies WHERE (expiry_time < ?); " sv ) ) ;
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 ) ) ;
2023-04-20 14:22:40 -04:00
return CookieJar { PersistedStorage { database , move ( statements ) } } ;
2022-10-27 12:56:22 -04:00
}
2023-04-20 14:22:40 -04:00
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 ) )
2022-10-27 12:56:22 -04:00
{
}
2024-01-26 11:10:15 -05:00
String CookieJar : : get_cookie ( const URL & url , Web : : Cookie : : Source source )
2021-04-11 10:43:54 -04:00
{
2021-04-13 12:28:39 -04:00
purge_expired_cookies ( ) ;
2021-04-11 10:43:54 -04:00
auto domain = canonicalize_domain ( url ) ;
if ( ! domain . has_value ( ) )
return { } ;
2021-06-08 19:36:27 +04:30
auto cookie_list = get_matching_cookies ( url , domain . value ( ) , source ) ;
2021-04-11 10:43:54 -04:00
StringBuilder builder ;
2022-04-01 20:58:27 +03:00
for ( auto const & cookie : cookie_list ) {
2021-04-14 10:18:13 -04:00
// If there is an unprocessed cookie in the cookie-list, output the characters %x3B and %x20 ("; ")
2021-04-12 23:16:27 -04:00
if ( ! builder . is_empty ( ) )
2022-07-11 17:32:29 +00:00
builder . append ( " ; " sv ) ;
2021-04-14 10:18:13 -04:00
// Output the cookie's name, the %x3D ("=") character, and the cookie's value.
2021-06-08 19:36:27 +04:30
builder . appendff ( " {}={} " , cookie . name , cookie . value ) ;
2021-04-11 10:43:54 -04:00
}
2024-01-26 11:10:15 -05:00
return MUST ( builder . to_string ( ) ) ;
2021-04-11 10:43:54 -04:00
}
2022-04-01 20:58:27 +03:00
void CookieJar : : set_cookie ( const URL & url , Web : : Cookie : : ParsedCookie const & parsed_cookie , Web : : Cookie : : Source source )
2021-04-11 10:43:54 -04:00
{
auto domain = canonicalize_domain ( url ) ;
if ( ! domain . has_value ( ) )
return ;
2024-01-26 11:39:00 -05:00
store_cookie ( parsed_cookie , url , domain . release_value ( ) , source ) ;
2021-04-11 10:43:54 -04:00
}
2022-10-16 19:48:19 +02:00
// This is based on https://www.rfc-editor.org/rfc/rfc6265#section-5.3 as store_cookie() below
// however the whole ParsedCookie->Cookie conversion is skipped.
2022-11-28 11:24:04 -05:00
void CookieJar : : update_cookie ( Web : : Cookie : : Cookie cookie )
2022-10-16 19:48:19 +02:00
{
2022-10-27 12:56:22 -04:00
select_cookie_from_database (
move ( cookie ) ,
// 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
[ this ] ( auto & cookie , auto old_cookie ) {
// Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
cookie . creation_time = old_cookie . creation_time ;
// Remove the old-cookie from the cookie store.
// NOTE: Rather than deleting then re-inserting this cookie, we update it in-place.
update_cookie_in_database ( cookie ) ;
} ,
// 12. Insert the newly created cookie into the cookie store.
[ this ] ( auto cookie ) {
insert_cookie_into_database ( cookie ) ;
} ) ;
2022-10-16 19:48:19 +02:00
}
2022-10-27 12:56:22 -04:00
void CookieJar : : dump_cookies ( )
2021-04-12 12:12:13 -04:00
{
2022-10-27 12:56:22 -04:00
static constexpr auto key_color = " \033 [34;1m " sv ;
static constexpr auto attribute_color = " \033 [33m " sv ;
static constexpr auto no_color = " \033 [0m " sv ;
2021-04-12 12:12:13 -04:00
StringBuilder builder ;
2022-10-27 12:56:22 -04:00
size_t total_cookies { 0 } ;
select_all_cookies_from_database ( [ & ] ( auto cookie ) {
+ + total_cookies ;
builder . appendff ( " {}{}{} - " , key_color , cookie . name , no_color ) ;
builder . appendff ( " {}{}{} - " , key_color , cookie . domain , no_color ) ;
builder . appendff ( " {}{}{} \n " , key_color , cookie . path , no_color ) ;
builder . appendff ( " \t {}Value{} = {} \n " , attribute_color , no_color , cookie . value ) ;
2023-02-24 11:51:56 -05:00
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 ( ) ) ;
2022-10-27 12:56:22 -04:00
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 ) ;
builder . appendff ( " \t {}Persistent{} = {:s} \n " , attribute_color , no_color , cookie . persistent ) ;
builder . appendff ( " \t {}SameSite{} = {:s} \n " , attribute_color , no_color , Web : : Cookie : : same_site_to_string ( cookie . same_site ) ) ;
} ) ;
2024-01-26 10:47:57 -05:00
dbgln ( " {} cookies stored \n {} " , total_cookies , builder . string_view ( ) ) ;
2021-04-12 12:12:13 -04:00
}
2022-10-27 12:56:22 -04:00
Vector < Web : : Cookie : : Cookie > CookieJar : : get_all_cookies ( )
2022-03-01 17:05:42 +01:00
{
Vector < Web : : Cookie : : Cookie > cookies ;
2022-10-27 12:56:22 -04:00
select_all_cookies_from_database ( [ & ] ( auto cookie ) {
cookies . append ( move ( cookie ) ) ;
} ) ;
2022-03-01 17:05:42 +01:00
return cookies ;
}
2022-11-11 09:24:07 -05:00
// https://w3c.github.io/webdriver/#dfn-associated-cookies
Vector < Web : : Cookie : : Cookie > CookieJar : : get_all_cookies ( URL const & url )
{
auto domain = canonicalize_domain ( url ) ;
if ( ! domain . has_value ( ) )
return { } ;
2022-10-27 12:56:22 -04:00
return get_matching_cookies ( url , domain . value ( ) , Web : : Cookie : : Source : : Http , MatchingCookiesSpecMode : : WebDriver ) ;
2022-11-11 09:24:07 -05:00
}
2024-01-26 10:47:57 -05:00
Optional < Web : : Cookie : : Cookie > CookieJar : : get_named_cookie ( URL const & url , StringView name )
2022-11-11 09:55:11 -05:00
{
auto domain = canonicalize_domain ( url ) ;
if ( ! domain . has_value ( ) )
return { } ;
auto cookie_list = get_matching_cookies ( url , domain . value ( ) , Web : : Cookie : : Source : : Http , MatchingCookiesSpecMode : : WebDriver ) ;
for ( auto const & cookie : cookie_list ) {
2024-01-26 10:47:57 -05:00
if ( cookie . name = = name )
2022-11-11 09:55:11 -05:00
return cookie ;
}
return { } ;
}
2024-01-26 11:39:00 -05:00
Optional < String > CookieJar : : canonicalize_domain ( const URL & url )
2021-04-11 10:43:54 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.1.2
if ( ! url . is_valid ( ) )
return { } ;
// FIXME: Implement RFC 5890 to "Convert each label that is not a Non-Reserved LDH (NR-LDH) label to an A-label".
2023-07-27 21:40:41 +12:00
if ( url . host ( ) . has < Empty > ( ) )
return { } ;
2024-01-26 11:39:00 -05:00
return MUST ( MUST ( url . serialized_host ( ) ) . to_lowercase ( ) ) ;
2021-04-11 10:43:54 -04:00
}
2024-01-26 10:47:57 -05:00
bool CookieJar : : domain_matches ( StringView string , StringView domain_string )
2021-04-12 23:16:27 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.1.3
// A string domain-matches a given domain string if at least one of the following conditions hold:
// The domain string and the string are identical.
if ( string = = domain_string )
return true ;
// All of the following conditions hold:
// - The domain string is a suffix of the string.
// - The last character of the string that is not included in the domain string is a %x2E (".") character.
// - The string is a host name (i.e., not an IP address).
if ( ! string . ends_with ( domain_string ) )
return false ;
if ( string [ string . length ( ) - domain_string . length ( ) - 1 ] ! = ' . ' )
return false ;
if ( AK : : IPv4Address : : from_string ( string ) . has_value ( ) )
return false ;
return true ;
}
2024-01-26 10:47:57 -05:00
bool CookieJar : : path_matches ( StringView request_path , StringView cookie_path )
2021-04-14 10:18:13 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.1.4
// A request-path path-matches a given cookie-path if at least one of the following conditions holds:
// The cookie-path and the request-path are identical.
if ( request_path = = cookie_path )
return true ;
if ( request_path . starts_with ( cookie_path ) ) {
// The cookie-path is a prefix of the request-path, and the last character of the cookie-path is %x2F ("/").
if ( cookie_path . ends_with ( ' / ' ) )
return true ;
// The cookie-path is a prefix of the request-path, and the first character of the request-path that is not included in the cookie-path is a %x2F ("/") character.
if ( request_path [ cookie_path . length ( ) ] = = ' / ' )
return true ;
}
return false ;
}
2024-01-26 11:39:00 -05:00
String CookieJar : : default_path ( const URL & url )
2021-04-13 16:47:05 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.1.4
// 1. Let uri-path be the path portion of the request-uri if such a portion exists (and empty otherwise).
2024-01-26 11:39:00 -05:00
auto uri_path = url . serialize_path ( ) ;
2021-04-13 16:47:05 -04:00
// 2. If the uri-path is empty or if the first character of the uri-path is not a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
if ( uri_path . is_empty ( ) | | ( uri_path [ 0 ] ! = ' / ' ) )
2024-01-26 11:39:00 -05:00
return " / " _string ;
2021-04-13 16:47:05 -04:00
StringView uri_path_view = uri_path ;
2021-07-01 15:01:29 +02:00
size_t last_separator = uri_path_view . find_last ( ' / ' ) . value ( ) ;
2021-04-13 16:47:05 -04:00
// 3. If the uri-path contains no more than one %x2F ("/") character, output %x2F ("/") and skip the remaining step.
if ( last_separator = = 0 )
2024-01-26 11:39:00 -05:00
return " / " _string ;
2021-04-13 16:47:05 -04:00
// 4. Output the characters of the uri-path from the first character up to, but not including, the right-most %x2F ("/").
2024-01-26 11:39:00 -05:00
return MUST ( String : : from_utf8 ( uri_path . substring_view ( 0 , last_separator ) ) ) ;
2021-04-13 16:47:05 -04:00
}
2024-01-26 11:39:00 -05:00
void CookieJar : : store_cookie ( Web : : Cookie : : ParsedCookie const & parsed_cookie , const URL & url , String canonicalized_domain , Web : : Cookie : : Source source )
2021-04-12 23:16:27 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.3
// 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.
2023-11-22 08:36:23 +13:00
Web : : Cookie : : Cookie cookie { parsed_cookie . name , parsed_cookie . value , parsed_cookie . same_site_attribute } ;
2023-03-13 22:35:22 +01:00
cookie . creation_time = UnixDateTime : : now ( ) ;
2021-04-12 23:16:27 -04:00
cookie . last_access_time = cookie . creation_time ;
if ( parsed_cookie . expiry_time_from_max_age_attribute . has_value ( ) ) {
// 3. If the cookie-attribute-list contains an attribute with an attribute-name of "Max-Age": Set the cookie's persistent-flag to true.
// Set the cookie's expiry-time to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Max-Age".
cookie . persistent = true ;
2021-04-15 10:36:20 -04:00
cookie . expiry_time = parsed_cookie . expiry_time_from_max_age_attribute . value ( ) ;
2021-04-12 23:16:27 -04:00
} else if ( parsed_cookie . expiry_time_from_expires_attribute . has_value ( ) ) {
// If the cookie-attribute-list contains an attribute with an attribute-name of "Expires": Set the cookie's persistent-flag to true.
// Set the cookie's expiry-time to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Expires".
cookie . persistent = true ;
2021-04-15 10:36:20 -04:00
cookie . expiry_time = parsed_cookie . expiry_time_from_expires_attribute . value ( ) ;
2021-04-12 23:16:27 -04:00
} else {
2023-02-24 11:51:56 -05:00
// Set the cookie's persistent-flag to false. Set the cookie's expiry-time to the latest representable date.
2021-04-12 23:16:27 -04:00
cookie . persistent = false ;
2023-03-13 22:35:22 +01:00
cookie . expiry_time = UnixDateTime : : latest ( ) ;
2021-04-12 23:16:27 -04:00
}
// 4. If the cookie-attribute-list contains an attribute with an attribute-name of "Domain":
if ( parsed_cookie . domain . has_value ( ) ) {
// Let the domain-attribute be the attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Domain".
2023-11-22 08:36:23 +13:00
cookie . domain = parsed_cookie . domain . value ( ) ;
2021-04-12 23:16:27 -04:00
}
// 5. If the user agent is configured to reject "public suffixes" and the domain-attribute is a public suffix:
2023-10-20 10:45:30 -04:00
if ( is_public_suffix ( cookie . domain ) ) {
// If the domain-attribute is identical to the canonicalized request-host:
2024-01-26 11:39:00 -05:00
if ( cookie . domain = = canonicalized_domain ) {
2023-10-20 10:45:30 -04:00
// Let the domain-attribute be the empty string.
2023-11-20 21:09:58 +13:00
cookie . domain = String { } ;
2023-10-20 10:45:30 -04:00
}
// Otherwise:
else {
// Ignore the cookie entirely and abort these steps.
return ;
}
}
2021-04-12 23:16:27 -04:00
// 6. If the domain-attribute is non-empty:
if ( ! cookie . domain . is_empty ( ) ) {
// If the canonicalized request-host does not domain-match the domain-attribute: Ignore the cookie entirely and abort these steps.
2024-01-26 10:47:57 -05:00
if ( ! domain_matches ( canonicalized_domain , cookie . domain ) )
2021-04-12 23:16:27 -04:00
return ;
// Set the cookie's host-only-flag to false. Set the cookie's domain to the domain-attribute.
cookie . host_only = false ;
} else {
// Set the cookie's host-only-flag to true. Set the cookie's domain to the canonicalized request-host.
cookie . host_only = true ;
2024-01-26 11:39:00 -05:00
cookie . domain = move ( canonicalized_domain ) ;
2021-04-12 23:16:27 -04:00
}
// 7. If the cookie-attribute-list contains an attribute with an attribute-name of "Path":
if ( parsed_cookie . path . has_value ( ) ) {
// Set the cookie's path to attribute-value of the last attribute in the cookie-attribute-list with an attribute-name of "Path".
2023-11-22 08:36:23 +13:00
cookie . path = parsed_cookie . path . value ( ) ;
2021-04-12 23:16:27 -04:00
} else {
2024-01-26 11:39:00 -05:00
cookie . path = default_path ( url ) ;
2021-04-12 23:16:27 -04:00
}
// 8. If the cookie-attribute-list contains an attribute with an attribute-name of "Secure", set the cookie's secure-only-flag to true.
cookie . secure = parsed_cookie . secure_attribute_present ;
// 9. If the cookie-attribute-list contains an attribute with an attribute-name of "HttpOnly", set the cookie's http-only-flag to false.
cookie . http_only = parsed_cookie . http_only_attribute_present ;
// 10. If the cookie was received from a "non-HTTP" API and the cookie's http-only-flag is set, abort these steps and ignore the cookie entirely.
2021-04-13 17:35:26 -04:00
if ( source ! = Web : : Cookie : : Source : : Http & & cookie . http_only )
return ;
2021-04-12 23:16:27 -04:00
2023-08-07 23:33:01 +02:00
// Synchronize persisting the cookie
auto sync_promise = Core : : Promise < Empty > : : construct ( ) ;
2022-10-27 12:56:22 -04:00
select_cookie_from_database (
move ( cookie ) ,
2021-04-12 23:16:27 -04:00
2022-10-27 12:56:22 -04:00
// 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
2023-08-07 23:33:01 +02:00
[ this , source , sync_promise ] ( auto & cookie , auto old_cookie ) {
2022-10-27 12:56:22 -04:00
// 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.
if ( source ! = Web : : Cookie : : Source : : Http & & old_cookie . http_only )
return ;
2021-04-12 23:16:27 -04:00
2022-10-27 12:56:22 -04:00
// Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
cookie . creation_time = old_cookie . creation_time ;
2021-04-12 23:16:27 -04:00
2022-10-27 12:56:22 -04:00
// Remove the old-cookie from the cookie store.
// NOTE: Rather than deleting then re-inserting this cookie, we update it in-place.
update_cookie_in_database ( cookie ) ;
2023-08-07 23:33:01 +02:00
sync_promise - > resolve ( { } ) ;
2022-10-27 12:56:22 -04:00
} ,
2021-04-12 23:16:27 -04:00
2022-10-27 12:56:22 -04:00
// 12. Insert the newly created cookie into the cookie store.
2023-08-07 23:33:01 +02:00
[ this , sync_promise ] ( auto cookie ) {
2022-10-27 12:56:22 -04:00
insert_cookie_into_database ( cookie ) ;
2023-08-07 23:33:01 +02:00
sync_promise - > resolve ( { } ) ;
2022-10-27 12:56:22 -04:00
} ) ;
2023-08-07 23:33:01 +02:00
MUST ( sync_promise - > await ( ) ) ;
2021-04-12 23:16:27 -04:00
}
2024-01-26 10:47:57 -05:00
Vector < Web : : Cookie : : Cookie > CookieJar : : get_matching_cookies ( const URL & url , StringView canonicalized_domain , Web : : Cookie : : Source source , MatchingCookiesSpecMode mode )
2021-04-14 10:18:13 -04:00
{
// https://tools.ietf.org/html/rfc6265#section-5.4
// 1. Let cookie-list be the set of cookies from the cookie store that meets all of the following requirements:
2022-10-27 12:56:22 -04:00
Vector < Web : : Cookie : : Cookie > cookie_list ;
2021-04-14 10:18:13 -04:00
2022-10-27 12:56:22 -04:00
select_all_cookies_from_database ( [ & ] ( auto cookie ) {
2021-04-14 10:18:13 -04:00
// Either: The cookie's host-only-flag is true and the canonicalized request-host is identical to the cookie's domain.
// Or: The cookie's host-only-flag is false and the canonicalized request-host domain-matches the cookie's domain.
2024-01-26 10:47:57 -05:00
bool is_host_only_and_has_identical_domain = cookie . host_only & & ( canonicalized_domain = = cookie . domain ) ;
bool is_not_host_only_and_domain_matches = ! cookie . host_only & & domain_matches ( canonicalized_domain , cookie . domain ) ;
2021-04-14 10:18:13 -04:00
if ( ! is_host_only_and_has_identical_domain & & ! is_not_host_only_and_domain_matches )
2022-10-27 12:56:22 -04:00
return ;
2021-04-14 10:18:13 -04:00
// The request-uri's path path-matches the cookie's path.
2024-01-26 10:47:57 -05:00
if ( ! path_matches ( url . serialize_path ( ) , cookie . path ) )
2022-10-27 12:56:22 -04:00
return ;
2021-04-14 10:18:13 -04:00
// If the cookie's secure-only-flag is true, then the request-uri's scheme must denote a "secure" protocol.
2022-10-27 12:56:22 -04:00
if ( cookie . secure & & ( url . scheme ( ) ! = " https " ) )
return ;
2021-04-14 10:18:13 -04:00
// If the cookie's http-only-flag is true, then exclude the cookie if the cookie-string is being generated for a "non-HTTP" API.
2022-10-27 12:56:22 -04:00
if ( cookie . http_only & & ( source ! = Web : : Cookie : : Source : : Http ) )
return ;
2021-04-14 10:18:13 -04:00
2022-11-11 09:24:07 -05:00
// NOTE: The WebDriver spec expects only step 1 above to be executed to match cookies.
if ( mode = = MatchingCookiesSpecMode : : WebDriver ) {
2022-10-27 12:56:22 -04:00
cookie_list . append ( move ( cookie ) ) ;
return ;
2022-11-11 09:24:07 -05:00
}
2021-04-14 10:18:13 -04:00
// 2. The user agent SHOULD sort the cookie-list in the following order:
// - 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.
2023-11-20 21:09:58 +13:00
auto cookie_path_length = cookie . path . bytes ( ) . size ( ) ;
2023-02-24 11:51:56 -05:00
auto cookie_creation_time = cookie . creation_time ;
2022-10-27 12:56:22 -04:00
cookie_list . insert_before_matching ( move ( cookie ) , [ cookie_path_length , cookie_creation_time ] ( auto const & entry ) {
2023-11-20 21:09:58 +13:00
if ( cookie_path_length > entry . path . bytes ( ) . size ( ) ) {
2021-04-14 10:18:13 -04:00
return true ;
2023-11-20 21:09:58 +13:00
} else if ( cookie_path_length = = entry . path . bytes ( ) . size ( ) ) {
2023-02-24 11:51:56 -05:00
if ( cookie_creation_time < entry . creation_time )
2021-04-14 10:18:13 -04:00
return true ;
}
return false ;
} ) ;
2022-10-27 12:56:22 -04:00
} ) ;
// 3. Update the last-access-time of each cookie in the cookie-list to the current date and time.
2023-03-13 22:35:22 +01:00
auto now = UnixDateTime : : now ( ) ;
2021-04-14 10:18:13 -04:00
2022-10-27 12:56:22 -04:00
for ( auto & cookie : cookie_list ) {
cookie . last_access_time = now ;
update_cookie_in_database ( cookie ) ;
2021-04-14 10:18:13 -04:00
}
return cookie_list ;
}
2023-02-05 19:02:54 +00:00
static ErrorOr < Web : : Cookie : : Cookie > parse_cookie ( ReadonlySpan < SQL : : Value > row )
2021-04-13 12:28:39 -04:00
{
2022-10-27 12:56:22 -04:00
if ( row . size ( ) ! = 12 )
return Error : : from_string_view ( " Incorrect number of columns to parse cookie " sv ) ;
size_t index = 0 ;
auto convert_text = [ & ] ( auto & field , StringView name ) - > ErrorOr < void > {
auto const & value = row [ index + + ] ;
if ( value . type ( ) ! = SQL : : SQLType : : Text )
return Error : : from_string_view ( name ) ;
2024-01-26 11:39:00 -05:00
field = MUST ( value . to_string ( ) ) ;
2022-10-27 12:56:22 -04:00
return { } ;
} ;
auto convert_bool = [ & ] ( auto & field , StringView name ) - > ErrorOr < void > {
auto const & value = row [ index + + ] ;
if ( value . type ( ) ! = SQL : : SQLType : : Boolean )
return Error : : from_string_view ( name ) ;
field = value . to_bool ( ) . value ( ) ;
return { } ;
} ;
auto convert_time = [ & ] ( auto & field , StringView name ) - > ErrorOr < void > {
auto const & value = row [ index + + ] ;
2022-12-11 11:45:37 -05:00
if ( value . type ( ) ! = SQL : : SQLType : : Integer )
2022-10-27 12:56:22 -04:00
return Error : : from_string_view ( name ) ;
2021-04-13 12:28:39 -04:00
2024-01-10 07:05:15 -05:00
field = value . to_unix_date_time ( ) . value ( ) ;
2022-10-27 12:56:22 -04:00
return { } ;
} ;
auto convert_same_site = [ & ] ( auto & field , StringView name ) - > ErrorOr < void > {
auto const & value = row [ index + + ] ;
if ( value . type ( ) ! = SQL : : SQLType : : Integer )
return Error : : from_string_view ( name ) ;
2022-12-11 11:45:37 -05:00
auto same_site = value . to_int < UnderlyingType < Web : : Cookie : : SameSite > > ( ) . value ( ) ;
2022-10-27 12:56:22 -04:00
if ( same_site > to_underlying ( Web : : Cookie : : SameSite : : Lax ) )
return Error : : from_string_view ( name ) ;
field = static_cast < Web : : Cookie : : SameSite > ( same_site ) ;
return { } ;
} ;
Web : : Cookie : : Cookie cookie ;
TRY ( convert_text ( cookie . name , " name " sv ) ) ;
TRY ( convert_text ( cookie . value , " value " sv ) ) ;
TRY ( convert_same_site ( cookie . same_site , " same_site " sv ) ) ;
TRY ( convert_time ( cookie . creation_time , " creation_time " sv ) ) ;
TRY ( convert_time ( cookie . last_access_time , " last_access_time " sv ) ) ;
TRY ( convert_time ( cookie . expiry_time , " expiry_time " sv ) ) ;
TRY ( convert_text ( cookie . domain , " domain " sv ) ) ;
TRY ( convert_text ( cookie . path , " path " sv ) ) ;
TRY ( convert_bool ( cookie . secure , " secure " sv ) ) ;
TRY ( convert_bool ( cookie . http_only , " http_only " sv ) ) ;
TRY ( convert_bool ( cookie . host_only , " host_only " sv ) ) ;
TRY ( convert_bool ( cookie . persistent , " persistent " sv ) ) ;
return cookie ;
}
void CookieJar : : insert_cookie_into_database ( Web : : Cookie : : Cookie const & cookie )
{
2023-04-20 14:22:40 -04:00
m_storage . visit (
[ & ] ( PersistedStorage & storage ) {
storage . database . execute_statement (
storage . statements . insert_cookie , { } , [ this ] ( ) { purge_expired_cookies ( ) ; } , { } ,
2024-01-26 11:39:00 -05:00
cookie . name ,
cookie . value ,
2023-04-20 14:22:40 -04:00
to_underlying ( cookie . same_site ) ,
2024-01-10 07:05:15 -05:00
cookie . creation_time ,
cookie . last_access_time ,
cookie . expiry_time ,
2024-01-26 11:39:00 -05:00
cookie . domain ,
cookie . path ,
2023-04-20 14:22:40 -04:00
cookie . secure ,
cookie . http_only ,
cookie . host_only ,
cookie . persistent ) ;
} ,
[ & ] ( TransientStorage & storage ) {
2024-01-26 11:39:00 -05:00
CookieStorageKey key { cookie . name , cookie . domain , cookie . path } ;
2023-04-20 14:22:40 -04:00
storage . set ( key , cookie ) ;
} ) ;
2022-10-27 12:56:22 -04:00
}
void CookieJar : : update_cookie_in_database ( Web : : Cookie : : Cookie const & cookie )
{
2023-04-20 14:22:40 -04:00
m_storage . visit (
[ & ] ( PersistedStorage & storage ) {
storage . database . execute_statement (
storage . statements . update_cookie , { } , [ this ] ( ) { purge_expired_cookies ( ) ; } , { } ,
2024-01-26 11:39:00 -05:00
cookie . value ,
2023-04-20 14:22:40 -04:00
to_underlying ( cookie . same_site ) ,
2024-01-10 07:05:15 -05:00
cookie . creation_time ,
cookie . last_access_time ,
cookie . expiry_time ,
2023-04-20 14:22:40 -04:00
cookie . secure ,
cookie . http_only ,
cookie . host_only ,
cookie . persistent ,
2024-01-26 11:39:00 -05:00
cookie . name ,
cookie . domain ,
cookie . path ) ;
2023-04-20 14:22:40 -04:00
} ,
[ & ] ( TransientStorage & storage ) {
2024-01-26 11:39:00 -05:00
CookieStorageKey key { cookie . name , cookie . domain , cookie . path } ;
2023-04-20 14:22:40 -04:00
storage . set ( key , cookie ) ;
} ) ;
2022-10-27 12:56:22 -04:00
}
struct WrappedCookie : public RefCounted < WrappedCookie > {
explicit WrappedCookie ( Web : : Cookie : : Cookie cookie_ )
: RefCounted ( )
, cookie ( move ( cookie_ ) )
{
2021-04-13 12:28:39 -04:00
}
2022-10-27 12:56:22 -04:00
Web : : Cookie : : Cookie cookie ;
bool had_any_results { false } ;
} ;
void CookieJar : : select_cookie_from_database ( Web : : Cookie : : Cookie cookie , OnCookieFound on_result , OnCookieNotFound on_complete_without_results )
{
2023-04-20 14:22:40 -04:00
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 ) ) ;
} ,
{ } ,
2024-01-26 11:39:00 -05:00
wrapped_cookie - > cookie . name ,
wrapped_cookie - > cookie . domain ,
wrapped_cookie - > cookie . path ) ;
2023-04-20 14:22:40 -04:00
} ,
[ & ] ( TransientStorage & storage ) {
2024-01-26 11:39:00 -05:00
CookieStorageKey key { cookie . name , cookie . domain , cookie . path } ;
2022-10-27 12:56:22 -04:00
2023-04-20 14:22:40 -04:00
if ( auto it = storage . find ( key ) ; it ! = storage . end ( ) )
on_result ( cookie , it - > value ) ;
2022-10-27 12:56:22 -04:00
else
2023-04-20 14:22:40 -04:00
on_complete_without_results ( cookie ) ;
} ) ;
2022-10-27 12:56:22 -04:00
}
void CookieJar : : select_all_cookies_from_database ( OnSelectAllCookiesResult on_result )
{
// FIXME: Make surrounding APIs asynchronous.
2023-04-20 14:22:40 -04:00
m_storage . visit (
[ & ] ( PersistedStorage & storage ) {
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 ( ) ) ;
} ,
2023-08-07 23:33:01 +02:00
{ } ,
{ } ) ;
2022-10-27 12:56:22 -04:00
} ,
2023-04-20 14:22:40 -04:00
[ & ] ( TransientStorage & storage ) {
for ( auto const & cookie : storage )
on_result ( cookie . value ) ;
2022-10-27 12:56:22 -04:00
} ) ;
2021-04-13 12:28:39 -04:00
}
2022-10-27 12:56:22 -04:00
void CookieJar : : purge_expired_cookies ( )
{
2023-03-13 22:35:22 +01:00
auto now = UnixDateTime : : now ( ) ;
2023-04-20 14:22:40 -04:00
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 ) {
2023-03-13 22:35:22 +01:00
if ( cookie . value . expiry_time < now )
2023-04-20 14:22:40 -04:00
keys_to_evict . append ( cookie . key ) ;
}
for ( auto const & key : keys_to_evict )
storage . remove ( key ) ;
} ) ;
2022-10-27 12:56:22 -04:00
}
2023-02-24 11:51:56 -05:00
2021-04-11 10:43:54 -04:00
}