mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 14:57:35 +00:00
Browser: Implement spec-compliant cookie storage
https://tools.ietf.org/html/rfc6265#section-5.3 This includes a bit of an update to how cookies are first parsed. The storage spec requires some extra information from the parsing steps than just the actual values that were parsed. For example, it needs to know whether Max-Age or Expires (or both) were specified to give precedence to Max-Age. To accommodate this, the parser now uses an intermediate struct for storing this information. The final Cookie struct is not created until the storage steps. The storage itself is also updated to be keyed by a combo of the cookie name, domain, and path. Retrieving cookies was updated to use the spec's domain-matching algorithm, but otherwise is not written to the spec yet. This also does not handle evicting expired cookies yet.
This commit is contained in:
parent
a0111f6a3e
commit
86bdfa1edf
2 changed files with 216 additions and 77 deletions
|
@ -26,12 +26,25 @@
|
||||||
|
|
||||||
#include "CookieJar.h"
|
#include "CookieJar.h"
|
||||||
#include <AK/AllOf.h>
|
#include <AK/AllOf.h>
|
||||||
|
#include <AK/IPv4Address.h>
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <AK/URL.h>
|
#include <AK/URL.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
namespace Browser {
|
namespace Browser {
|
||||||
|
|
||||||
|
struct ParsedCookie {
|
||||||
|
String name;
|
||||||
|
String value;
|
||||||
|
Optional<Core::DateTime> expiry_time_from_expires_attribute {};
|
||||||
|
Optional<Core::DateTime> expiry_time_from_max_age_attribute {};
|
||||||
|
Optional<String> domain {};
|
||||||
|
Optional<String> path {};
|
||||||
|
bool secure_attribute_present { false };
|
||||||
|
bool http_only_attribute_present { false };
|
||||||
|
};
|
||||||
|
|
||||||
String CookieJar::get_cookie(const URL& url) const
|
String CookieJar::get_cookie(const URL& url) const
|
||||||
{
|
{
|
||||||
auto domain = canonicalize_domain(url);
|
auto domain = canonicalize_domain(url);
|
||||||
|
@ -40,12 +53,13 @@ String CookieJar::get_cookie(const URL& url) const
|
||||||
|
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
|
|
||||||
if (auto it = m_cookies.find(*domain); it != m_cookies.end()) {
|
for (const auto& cookie : m_cookies) {
|
||||||
for (const auto& cookie : it->value) {
|
if (!domain_matches(domain.value(), cookie.value.domain))
|
||||||
if (!builder.is_empty())
|
continue;
|
||||||
builder.append("; ");
|
|
||||||
builder.appendff("{}={}", cookie.name, cookie.value);
|
if (!builder.is_empty())
|
||||||
}
|
builder.append("; ");
|
||||||
|
builder.appendff("{}={}", cookie.value.name, cookie.value.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@ -57,47 +71,35 @@ void CookieJar::set_cookie(const URL& url, const String& cookie_string)
|
||||||
if (!domain.has_value())
|
if (!domain.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto new_cookie = parse_cookie(cookie_string, *domain, default_path(url));
|
auto parsed_cookie = parse_cookie(cookie_string);
|
||||||
if (!new_cookie.has_value())
|
if (!parsed_cookie.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto it = m_cookies.find(*domain);
|
store_cookie(parsed_cookie.value(), url, move(domain.value()));
|
||||||
if (it == m_cookies.end()) {
|
|
||||||
m_cookies.set(*domain, { move(*new_cookie) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& cookie : it->value) {
|
|
||||||
if (cookie.name == new_cookie->name) {
|
|
||||||
cookie = move(*new_cookie);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it->value.append(move(*new_cookie));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::dump_cookies() const
|
void CookieJar::dump_cookies() const
|
||||||
{
|
{
|
||||||
static const char* url_color = "\033[34;1m";
|
static const char* key_color = "\033[34;1m";
|
||||||
static const char* cookie_color = "\033[31m";
|
|
||||||
static const char* attribute_color = "\033[33m";
|
static const char* attribute_color = "\033[33m";
|
||||||
static const char* no_color = "\033[0m";
|
static const char* no_color = "\033[0m";
|
||||||
|
|
||||||
StringBuilder builder;
|
StringBuilder builder;
|
||||||
builder.appendff("{} URLs with cookies\n", m_cookies.size());
|
builder.appendff("{} cookies stored\n", m_cookies.size());
|
||||||
|
|
||||||
for (const auto& url_and_cookies : m_cookies) {
|
for (const auto& cookie : m_cookies) {
|
||||||
builder.appendff("{}Cookies for:{} {}\n", url_color, no_color, url_and_cookies.key.is_empty() ? "file://" : url_and_cookies.key);
|
builder.appendff("{}{}{} - ", key_color, cookie.key.name, no_color);
|
||||||
|
builder.appendff("{}{}{} - ", key_color, cookie.key.domain, no_color);
|
||||||
|
builder.appendff("{}{}{}\n", key_color, cookie.key.path, no_color);
|
||||||
|
|
||||||
for (const auto& cookie : url_and_cookies.value) {
|
builder.appendff("\t{}Value{} = {}\n", attribute_color, no_color, cookie.value.value);
|
||||||
builder.appendff("\t{}{}{} = {}{}{}\n", cookie_color, cookie.name, no_color, cookie_color, cookie.value, no_color);
|
builder.appendff("\t{}CreationTime{} = {}\n", attribute_color, no_color, cookie.value.creation_time.to_string());
|
||||||
builder.appendff("\t\t{}Expiry{} = {}\n", attribute_color, no_color, cookie.expiry_time.to_string());
|
builder.appendff("\t{}LastAccessTime{} = {}\n", attribute_color, no_color, cookie.value.last_access_time.to_string());
|
||||||
builder.appendff("\t\t{}Domain{} = {}\n", attribute_color, no_color, cookie.domain);
|
builder.appendff("\t{}ExpiryTime{} = {}\n", attribute_color, no_color, cookie.value.expiry_time.to_string());
|
||||||
builder.appendff("\t\t{}Path{} = {}\n", attribute_color, no_color, cookie.path);
|
builder.appendff("\t{}Secure{} = {:s}\n", attribute_color, no_color, cookie.value.secure);
|
||||||
builder.appendff("\t\t{}Secure{} = {:s}\n", attribute_color, no_color, cookie.secure);
|
builder.appendff("\t{}HttpOnly{} = {:s}\n", attribute_color, no_color, cookie.value.http_only);
|
||||||
builder.appendff("\t\t{}HttpOnly{} = {:s}\n", attribute_color, no_color, cookie.http_only);
|
builder.appendff("\t{}HostOnly{} = {:s}\n", attribute_color, no_color, cookie.value.host_only);
|
||||||
}
|
builder.appendff("\t{}Persistent{} = {:s}\n", attribute_color, no_color, cookie.value.persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
dbgln("{}", builder.build());
|
dbgln("{}", builder.build());
|
||||||
|
@ -135,7 +137,7 @@ String CookieJar::default_path(const URL& url)
|
||||||
return uri_path.substring(0, last_separator);
|
return uri_path.substring(0, last_separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Cookie> CookieJar::parse_cookie(const String& cookie_string, String default_domain, String default_path)
|
Optional<ParsedCookie> CookieJar::parse_cookie(const String& cookie_string)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2
|
// https://tools.ietf.org/html/rfc6265#section-5.2
|
||||||
StringView name_value_pair;
|
StringView name_value_pair;
|
||||||
|
@ -177,17 +179,13 @@ Optional<Cookie> CookieJar::parse_cookie(const String& cookie_string, String def
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
// 6. The cookie-name is the name string, and the cookie-value is the value string.
|
// 6. The cookie-name is the name string, and the cookie-value is the value string.
|
||||||
Cookie cookie { name, value };
|
ParsedCookie parsed_cookie { name, value };
|
||||||
|
|
||||||
cookie.expiry_time = Core::DateTime::create(9999, 12, 31, 23, 59, 59);
|
parse_attributes(parsed_cookie, unparsed_attributes);
|
||||||
cookie.domain = move(default_domain);
|
return parsed_cookie;
|
||||||
cookie.path = move(default_path);
|
|
||||||
|
|
||||||
parse_attributes(cookie, unparsed_attributes);
|
|
||||||
return cookie;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::parse_attributes(Cookie& cookie, StringView unparsed_attributes)
|
void CookieJar::parse_attributes(ParsedCookie& parsed_cookie, StringView unparsed_attributes)
|
||||||
{
|
{
|
||||||
// 1. If the unparsed-attributes string is empty, skip the rest of these steps.
|
// 1. If the unparsed-attributes string is empty, skip the rest of these steps.
|
||||||
if (unparsed_attributes.is_empty())
|
if (unparsed_attributes.is_empty())
|
||||||
|
@ -231,37 +229,37 @@ void CookieJar::parse_attributes(Cookie& cookie, StringView unparsed_attributes)
|
||||||
|
|
||||||
// 6. Process the attribute-name and attribute-value according to the requirements in the following subsections.
|
// 6. Process the attribute-name and attribute-value according to the requirements in the following subsections.
|
||||||
// (Notice that attributes with unrecognized attribute-names are ignored.)
|
// (Notice that attributes with unrecognized attribute-names are ignored.)
|
||||||
process_attribute(cookie, attribute_name, attribute_value);
|
process_attribute(parsed_cookie, attribute_name, attribute_value);
|
||||||
|
|
||||||
// 7. Return to Step 1 of this algorithm.
|
// 7. Return to Step 1 of this algorithm.
|
||||||
parse_attributes(cookie, unparsed_attributes);
|
parse_attributes(parsed_cookie, unparsed_attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::process_attribute(Cookie& cookie, StringView attribute_name, StringView attribute_value)
|
void CookieJar::process_attribute(ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value)
|
||||||
{
|
{
|
||||||
if (attribute_name.equals_ignoring_case("Expires")) {
|
if (attribute_name.equals_ignoring_case("Expires")) {
|
||||||
on_expires_attribute(cookie, attribute_value);
|
on_expires_attribute(parsed_cookie, attribute_value);
|
||||||
} else if (attribute_name.equals_ignoring_case("Max-Age")) {
|
} else if (attribute_name.equals_ignoring_case("Max-Age")) {
|
||||||
on_max_age_attribute(cookie, attribute_value);
|
on_max_age_attribute(parsed_cookie, attribute_value);
|
||||||
} else if (attribute_name.equals_ignoring_case("Domain")) {
|
} else if (attribute_name.equals_ignoring_case("Domain")) {
|
||||||
on_domain_attribute(cookie, attribute_value);
|
on_domain_attribute(parsed_cookie, attribute_value);
|
||||||
} else if (attribute_name.equals_ignoring_case("Path")) {
|
} else if (attribute_name.equals_ignoring_case("Path")) {
|
||||||
on_path_attribute(cookie, attribute_value);
|
on_path_attribute(parsed_cookie, attribute_value);
|
||||||
} else if (attribute_name.equals_ignoring_case("Secure")) {
|
} else if (attribute_name.equals_ignoring_case("Secure")) {
|
||||||
on_secure_attribute(cookie);
|
on_secure_attribute(parsed_cookie);
|
||||||
} else if (attribute_name.equals_ignoring_case("HttpOnly")) {
|
} else if (attribute_name.equals_ignoring_case("HttpOnly")) {
|
||||||
on_http_only_attribute(cookie);
|
on_http_only_attribute(parsed_cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_expires_attribute(Cookie& cookie, StringView attribute_value)
|
void CookieJar::on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.1
|
// https://tools.ietf.org/html/rfc6265#section-5.2.1
|
||||||
if (auto expiry_time = parse_date_time(attribute_value); expiry_time.has_value())
|
if (auto expiry_time = parse_date_time(attribute_value); expiry_time.has_value())
|
||||||
cookie.expiry_time = *expiry_time;
|
parsed_cookie.expiry_time_from_expires_attribute = move(*expiry_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_max_age_attribute(Cookie& cookie, StringView attribute_value)
|
void CookieJar::on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.2
|
// https://tools.ietf.org/html/rfc6265#section-5.2.2
|
||||||
|
|
||||||
|
@ -275,16 +273,16 @@ void CookieJar::on_max_age_attribute(Cookie& cookie, StringView attribute_value)
|
||||||
|
|
||||||
if (*delta_seconds <= 0) {
|
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.
|
// If delta-seconds is less than or equal to zero (0), let expiry-time be the earliest representable date and time.
|
||||||
cookie.expiry_time = Core::DateTime::from_timestamp(0);
|
parsed_cookie.expiry_time_from_max_age_attribute = Core::DateTime::from_timestamp(0);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, let the expiry-time be the current date and time plus delta-seconds seconds.
|
// Otherwise, let the expiry-time be the current date and time plus delta-seconds seconds.
|
||||||
time_t now = Core::DateTime::now().timestamp();
|
time_t now = Core::DateTime::now().timestamp();
|
||||||
cookie.expiry_time = Core::DateTime::from_timestamp(now + *delta_seconds);
|
parsed_cookie.expiry_time_from_max_age_attribute = Core::DateTime::from_timestamp(now + *delta_seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_domain_attribute(Cookie& cookie, StringView attribute_value)
|
void CookieJar::on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.3
|
// https://tools.ietf.org/html/rfc6265#section-5.2.3
|
||||||
|
|
||||||
|
@ -304,10 +302,10 @@ void CookieJar::on_domain_attribute(Cookie& cookie, StringView attribute_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the cookie-domain to lower case.
|
// Convert the cookie-domain to lower case.
|
||||||
cookie.domain = String(cookie_domain).to_lowercase();
|
parsed_cookie.domain = String(cookie_domain).to_lowercase();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_path_attribute(Cookie& cookie, StringView attribute_value)
|
void CookieJar::on_path_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.4
|
// https://tools.ietf.org/html/rfc6265#section-5.2.4
|
||||||
|
|
||||||
|
@ -317,19 +315,19 @@ void CookieJar::on_path_attribute(Cookie& cookie, StringView attribute_value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Let cookie-path be the attribute-value
|
// Let cookie-path be the attribute-value
|
||||||
cookie.path = attribute_value;
|
parsed_cookie.path = attribute_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_secure_attribute(Cookie& cookie)
|
void CookieJar::on_secure_attribute(ParsedCookie& parsed_cookie)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.5
|
// https://tools.ietf.org/html/rfc6265#section-5.2.5
|
||||||
cookie.secure = true;
|
parsed_cookie.secure_attribute_present = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CookieJar::on_http_only_attribute(Cookie& cookie)
|
void CookieJar::on_http_only_attribute(ParsedCookie& parsed_cookie)
|
||||||
{
|
{
|
||||||
// https://tools.ietf.org/html/rfc6265#section-5.2.6
|
// https://tools.ietf.org/html/rfc6265#section-5.2.6
|
||||||
cookie.http_only = true;
|
parsed_cookie.http_only_attribute_present = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<Core::DateTime> CookieJar::parse_date_time(StringView date_string)
|
Optional<Core::DateTime> CookieJar::parse_date_time(StringView date_string)
|
||||||
|
@ -443,4 +441,112 @@ Optional<Core::DateTime> CookieJar::parse_date_time(StringView date_string)
|
||||||
return Core::DateTime::create(year, month, day_of_month, hour, minute, second);
|
return Core::DateTime::create(year, month, day_of_month, hour, minute, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CookieJar::domain_matches(const String& string, const String& domain_string)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CookieJar::store_cookie(ParsedCookie& parsed_cookie, const URL& url, String canonicalized_domain)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
Cookie cookie { move(parsed_cookie.name), move(parsed_cookie.value) };
|
||||||
|
cookie.creation_time = Core::DateTime::now();
|
||||||
|
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;
|
||||||
|
cookie.expiry_time = move(parsed_cookie.expiry_time_from_max_age_attribute.value());
|
||||||
|
} 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;
|
||||||
|
cookie.expiry_time = move(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.
|
||||||
|
cookie.persistent = false;
|
||||||
|
cookie.expiry_time = Core::DateTime::create(9999, 12, 31, 23, 59, 59);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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".
|
||||||
|
cookie.domain = move(parsed_cookie.domain.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. If the user agent is configured to reject "public suffixes" and the domain-attribute is a public suffix:
|
||||||
|
// FIXME: Support rejection of public suffixes. The full list is here: https://publicsuffix.org/list/public_suffix_list.dat
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if (!domain_matches(canonicalized_domain, cookie.domain))
|
||||||
|
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;
|
||||||
|
cookie.domain = move(canonicalized_domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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".
|
||||||
|
cookie.path = move(parsed_cookie.path.value());
|
||||||
|
} else {
|
||||||
|
cookie.path = default_path(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// FIXME: Update CookieJar to track where the cookie originated (an HTTP request vs document.cookie).
|
||||||
|
|
||||||
|
// 11. If the cookie store contains a cookie with the same name, domain, and path as the newly created cookie:
|
||||||
|
CookieStorageKey key { cookie.name, cookie.domain, cookie.path };
|
||||||
|
|
||||||
|
if (auto old_cookie = m_cookies.find(key); old_cookie != m_cookies.end()) {
|
||||||
|
// 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.
|
||||||
|
// FIXME: Similar to step 10, CookieJar needs to track where the cookie originated.
|
||||||
|
|
||||||
|
// Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
|
||||||
|
cookie.creation_time = old_cookie->value.creation_time;
|
||||||
|
|
||||||
|
// Remove the old-cookie from the cookie store.
|
||||||
|
m_cookies.remove(old_cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. Insert the newly created cookie into the cookie store.
|
||||||
|
m_cookies.set(key, move(cookie));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Traits.h>
|
||||||
#include <LibCore/DateTime.h>
|
#include <LibCore/DateTime.h>
|
||||||
|
|
||||||
namespace Browser {
|
namespace Browser {
|
||||||
|
@ -37,11 +37,25 @@ namespace Browser {
|
||||||
struct Cookie {
|
struct Cookie {
|
||||||
String name;
|
String name;
|
||||||
String value;
|
String value;
|
||||||
|
Core::DateTime creation_time {};
|
||||||
|
Core::DateTime last_access_time {};
|
||||||
Core::DateTime expiry_time {};
|
Core::DateTime expiry_time {};
|
||||||
String domain {};
|
String domain {};
|
||||||
String path {};
|
String path {};
|
||||||
bool secure { false };
|
bool secure { false };
|
||||||
bool http_only { false };
|
bool http_only { false };
|
||||||
|
bool host_only { false };
|
||||||
|
bool persistent { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedCookie;
|
||||||
|
|
||||||
|
struct CookieStorageKey {
|
||||||
|
bool operator==(const CookieStorageKey&) const = default;
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String domain;
|
||||||
|
String path;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CookieJar {
|
class CookieJar {
|
||||||
|
@ -53,18 +67,37 @@ public:
|
||||||
private:
|
private:
|
||||||
static Optional<String> canonicalize_domain(const URL& url);
|
static Optional<String> canonicalize_domain(const URL& url);
|
||||||
static String default_path(const URL& url);
|
static String default_path(const URL& url);
|
||||||
static Optional<Cookie> parse_cookie(const String& cookie_string, String default_domain, String default_path);
|
static Optional<ParsedCookie> parse_cookie(const String& cookie_string);
|
||||||
static void parse_attributes(Cookie& cookie, StringView unparsed_attributes);
|
static void parse_attributes(ParsedCookie& parsed_cookie, StringView unparsed_attributes);
|
||||||
static void process_attribute(Cookie& cookie, StringView attribute_name, StringView attribute_value);
|
static void process_attribute(ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value);
|
||||||
static void on_expires_attribute(Cookie& cookie, StringView attribute_value);
|
static void on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
|
||||||
static void on_max_age_attribute(Cookie& cookie, StringView attribute_value);
|
static void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
|
||||||
static void on_domain_attribute(Cookie& cookie, StringView attribute_value);
|
static void on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
|
||||||
static void on_path_attribute(Cookie& cookie, StringView attribute_value);
|
static void on_path_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
|
||||||
static void on_secure_attribute(Cookie& cookie);
|
static void on_secure_attribute(ParsedCookie& parsed_cookie);
|
||||||
static void on_http_only_attribute(Cookie& cookie);
|
static void on_http_only_attribute(ParsedCookie& parsed_cookie);
|
||||||
static Optional<Core::DateTime> parse_date_time(StringView date_string);
|
static Optional<Core::DateTime> parse_date_time(StringView date_string);
|
||||||
|
static bool domain_matches(const String& string, const String& domain_string);
|
||||||
|
|
||||||
HashMap<String, Vector<Cookie>> m_cookies;
|
void store_cookie(ParsedCookie& parsed_cookie, const URL& url, String canonicalized_domain);
|
||||||
|
|
||||||
|
HashMap<CookieStorageKey, Cookie> m_cookies;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Traits<Browser::CookieStorageKey> : public GenericTraits<Browser::CookieStorageKey> {
|
||||||
|
static unsigned hash(const Browser::CookieStorageKey& 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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue