Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick 1.416666 ladybird PRs #25475

Merged
merged 9 commits into from
Nov 25, 2024
7 changes: 4 additions & 3 deletions Tests/LibWeb/Text/expected/cookie.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Valueless cookie: "cookie="
Nameless and valueless cookie: ""
Invalid control character: ""
Non-ASCII domain: ""
Default path: "cookie1=value; cookie2=value"
Secure cookie prefix: ""
Host cookie prefix: ""
Large value: "cookie=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Expand All @@ -14,9 +15,9 @@ Public suffix: ""
SameSite=Lax: "cookie=value"
SameSite=Strict: "cookie=value"
SameSite=None: ""
Max-Age (before expiration): "cookie-max-age=value"
Expires (before expiration): "cookie-expires=value; cookie-max-age=value"
Max-Age (after expiration): ""
Max-Age (before expiration): "cookie-max-age1=value; cookie-max-age2=value"
Expires (before expiration): "cookie-expires=value; cookie-max-age1=value; cookie-max-age2=value"
Max-Age (after expiration): "cookie-max-age2=value"
Expires (after expiration): ""
Max-Age in past: ""
Expires in past: ""
Expand Down
18 changes: 16 additions & 2 deletions Tests/LibWeb/Text/input/cookie.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
printCookies("Non-ASCII domain");
};

const defaultPathTest = () => {
document.cookie = "cookie1=value; path=";
document.cookie = "cookie2=value; path=f";

printCookies("Default path");

deleteCookie("cookie1");
deleteCookie("cookie2");
};

const secureCookiePrefixTest = () => {
document.cookie = "__Secure-cookie=value";
printCookies("Secure cookie prefix");
Expand Down Expand Up @@ -113,16 +123,19 @@
};

const maxAgeTest1 = () => {
document.cookie = "cookie-max-age=value; max-age=1";
document.cookie = "cookie-max-age1=value; max-age=1";
document.cookie = `cookie-max-age2=value; max-age=${"1".repeat(1024)}`;
printCookies("Max-Age (before expiration)");
};

const maxAgeTest2 = () => {
printCookies("Max-Age (after expiration)");
deleteCookie("cookie-max-age2");
};

const maxAgeInPastTest = () => {
document.cookie = "cookie=value; max-age=-1";
document.cookie = "cookie1=value; max-age=-1";
document.cookie = `cookie2=value; max-age=-${"1".repeat(1023)}`;
printCookies("Max-Age in past");
};

Expand Down Expand Up @@ -176,6 +189,7 @@

invalidControlCharacterTest();
nonASCIIDomainTest();
defaultPathTest();
secureCookiePrefixTest();
hostCookiePrefixTest();

Expand Down
78 changes: 56 additions & 22 deletions Userland/Libraries/LibWeb/Cookie/ParsedCookie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
#include <AK/Vector.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibURL/URL.h>
#include <LibWeb/Infra/Strings.h>
#include <ctype.h>

namespace Web::Cookie {

static void parse_attributes(ParsedCookie& parsed_cookie, StringView unparsed_attributes);
static void process_attribute(ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value);
static void parse_attributes(URL::URL const&, ParsedCookie& parsed_cookie, StringView unparsed_attributes);
static void process_attribute(URL::URL const&, ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value);
static void on_expires_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
static void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
static void on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
static void on_path_attribute(ParsedCookie& parsed_cookie, StringView attribute_value);
static void on_path_attribute(URL::URL const&, ParsedCookie& parsed_cookie, StringView attribute_value);
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);
Expand All @@ -43,7 +44,7 @@ bool cookie_contains_invalid_control_character(StringView cookie_string)
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6-6
Optional<ParsedCookie> parse_cookie(StringView cookie_string)
Optional<ParsedCookie> parse_cookie(URL::URL const& url, StringView cookie_string)
{
// 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F character (CTL characters excluding HTAB):
// Abort these steps and ignore the set-cookie-string entirely.
Expand Down Expand Up @@ -96,12 +97,12 @@ Optional<ParsedCookie> parse_cookie(StringView cookie_string)
// 6. The cookie-name is the name string, and the cookie-value is the value string.
ParsedCookie parsed_cookie { MUST(String::from_utf8(name)), MUST(String::from_utf8(value)) };

parse_attributes(parsed_cookie, unparsed_attributes);
parse_attributes(url, parsed_cookie, unparsed_attributes);
return parsed_cookie;
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6-8
void parse_attributes(ParsedCookie& parsed_cookie, StringView unparsed_attributes)
void parse_attributes(URL::URL const& url, ParsedCookie& parsed_cookie, StringView unparsed_attributes)
{
// 1. If the unparsed-attributes string is empty, skip the rest of these steps.
if (unparsed_attributes.is_empty())
Expand Down Expand Up @@ -152,19 +153,19 @@ void parse_attributes(ParsedCookie& parsed_cookie, StringView unparsed_attribute
// 6. If the attribute-value is longer than 1024 octets, ignore the cookie-av string and return to Step 1 of this
// algorithm.
if (attribute_value.length() > 1024) {
parse_attributes(parsed_cookie, unparsed_attributes);
parse_attributes(url, parsed_cookie, unparsed_attributes);
return;
}

// 7. Process the attribute-name and attribute-value according to the requirements in the following subsections.
// (Notice that attributes with unrecognized attribute-names are ignored.)
process_attribute(parsed_cookie, attribute_name, attribute_value);
process_attribute(url, parsed_cookie, attribute_name, attribute_value);

// 8. Return to Step 1 of this algorithm.
parse_attributes(parsed_cookie, unparsed_attributes);
parse_attributes(url, parsed_cookie, unparsed_attributes);
}

void process_attribute(ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value)
void process_attribute(URL::URL const& url, ParsedCookie& parsed_cookie, StringView attribute_name, StringView attribute_value)
{
if (attribute_name.equals_ignoring_ascii_case("Expires"sv)) {
on_expires_attribute(parsed_cookie, attribute_value);
Expand All @@ -173,7 +174,7 @@ void process_attribute(ParsedCookie& parsed_cookie, StringView attribute_name, S
} else if (attribute_name.equals_ignoring_ascii_case("Domain"sv)) {
on_domain_attribute(parsed_cookie, attribute_value);
} else if (attribute_name.equals_ignoring_ascii_case("Path"sv)) {
on_path_attribute(parsed_cookie, attribute_value);
on_path_attribute(url, parsed_cookie, attribute_value);
} else if (attribute_name.equals_ignoring_ascii_case("Secure"sv)) {
on_secure_attribute(parsed_cookie);
} else if (attribute_name.equals_ignoring_ascii_case("HttpOnly"sv)) {
Expand Down Expand Up @@ -226,14 +227,20 @@ void on_max_age_attribute(ParsedCookie& parsed_cookie, StringView attribute_valu

// 2. If the first character of the attribute-value is neither a DIGIT, nor a "-" character followed by a DIGIT,
// ignore the cookie-av.
if (!is_ascii_digit(attribute_value[0]) && attribute_value[0] != '-')
// 3. If the remainder of attribute-value contains a non-DIGIT character, ignore the cookie-av.
auto digits = attribute_value[0] == '-' ? attribute_value.substring_view(1) : attribute_value;

if (digits.is_empty() || !all_of(digits, is_ascii_digit))
return;

// 3. If the remainder of attribute-value contains a non-DIGIT character, ignore the cookie-av.
// 4. Let delta-seconds be the attribute-value converted to a base 10 integer.
auto delta_seconds = attribute_value.to_number<i64>();
if (!delta_seconds.has_value())
return;
if (!delta_seconds.has_value()) {
// We know the attribute value only contains digits, so if we failed to parse, it is because the result did not
// fit in an i64. Set the value to the i64 limits in that case. The positive limit will be further capped below,
// and the negative limit will be immediately expired in the cookie jar.
delta_seconds = attribute_value[0] == '-' ? NumericLimits<i64>::min() : NumericLimits<i64>::max();
}

// 5. Let cookie-age-limit be the maximum age of the cookie (which SHOULD be 400 days or less, see Section 5.5).
auto cookie_age_limit = maximum_cookie_age();
Expand Down Expand Up @@ -272,21 +279,24 @@ void on_domain_attribute(ParsedCookie& parsed_cookie, StringView attribute_value
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.4
void on_path_attribute(ParsedCookie& parsed_cookie, StringView attribute_value)
void on_path_attribute(URL::URL const& url, ParsedCookie& parsed_cookie, StringView attribute_value)
{
String cookie_path;

// 1. If the attribute-value is empty or if the first character of the attribute-value is not %x2F ("/"):
if (attribute_value.is_empty() || attribute_value[0] != '/') {
// Let cookie-path be the default-path.
return;
// 1. Let cookie-path be the default-path.
cookie_path = default_path(url);
}

// Otherwise:
// 1. Let cookie-path be the attribute-value.
auto cookie_path = attribute_value;
else {
// 1. Let cookie-path be the attribute-value.
cookie_path = MUST(String::from_utf8(attribute_value));
}

// 2. Append an attribute to the cookie-attribute-list with an attribute-name of Path and an attribute-value of
// cookie-path.
parsed_cookie.path = MUST(String::from_utf8(cookie_path));
parsed_cookie.path = move(cookie_path);
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.6.5
Expand Down Expand Up @@ -458,6 +468,30 @@ Optional<UnixDateTime> parse_date_time(StringView date_string)
return parsed_cookie_date;
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.4
String default_path(URL::URL const& url)
{
// 1. Let uri-path be the path portion of the request-uri if such a portion exists (and empty otherwise).
auto uri_path = URL::percent_decode(url.serialize_path());

// 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] != '/'))
return "/"_string;

StringView uri_path_view = uri_path;
size_t last_separator = uri_path_view.find_last('/').value();

// 3. If the uri-path contains no more than one %x2F ("/") character, output %x2F ("/") and skip the remaining step.
if (last_separator == 0)
return "/"_string;

// 4. Output the characters of the uri-path from the first character up to, but not including, the right-most
// %x2F ("/").
// FIXME: The path might not be valid UTF-8.
return MUST(String::from_utf8(uri_path.substring_view(0, last_separator)));
}

}

template<>
Expand Down
4 changes: 3 additions & 1 deletion Userland/Libraries/LibWeb/Cookie/ParsedCookie.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <AK/String.h>
#include <AK/Time.h>
#include <LibIPC/Forward.h>
#include <LibURL/Forward.h>
#include <LibWeb/Cookie/Cookie.h>

namespace Web::Cookie {
Expand All @@ -26,8 +27,9 @@ struct ParsedCookie {
bool http_only_attribute_present { false };
};

Optional<ParsedCookie> parse_cookie(StringView cookie_string);
Optional<ParsedCookie> parse_cookie(URL::URL const&, StringView cookie_string);
bool cookie_contains_invalid_control_character(StringView);
String default_path(URL::URL const&);

}

Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2459,7 +2459,7 @@ String Document::cookie(Cookie::Source source)

void Document::set_cookie(StringView cookie_string, Cookie::Source source)
{
auto cookie = Cookie::parse_cookie(cookie_string);
auto cookie = Cookie::parse_cookie(url(), cookie_string);
if (!cookie.has_value())
return;

Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/HTML/BrowsingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ SandboxingFlagSet determine_the_creation_sandboxing_flags(BrowsingContext const&
bool BrowsingContext::has_navigable_been_destroyed() const
{
auto navigable = active_document()->navigable();
return navigable && navigable->has_been_destroyed();
return !navigable || navigable->has_been_destroyed();
}

}
2 changes: 1 addition & 1 deletion Userland/Libraries/LibWeb/Loader/ResourceLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ static void emit_signpost(ByteString const& message, int id)

static void store_response_cookies(Page& page, URL::URL const& url, ByteString const& set_cookie_entry)
{
auto cookie = Cookie::parse_cookie(set_cookie_entry);
auto cookie = Cookie::parse_cookie(url, set_cookie_entry);
if (!cookie.has_value())
return;
page.client().page_did_set_cookie(url, cookie.value(), Cookie::Source::Http); // FIXME: Determine cookie source correctly
Expand Down
12 changes: 12 additions & 0 deletions Userland/Libraries/LibWeb/WebDriver/Actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1014,4 +1014,16 @@ ErrorOr<void, WebDriver::Error> dispatch_tick_actions(InputState& input_state, R
return {};
}

// https://w3c.github.io/webdriver/#dfn-dispatch-a-list-of-actions
JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState& input_state, Vector<ActionObject> actions, HTML::BrowsingContext& browsing_context, ActionsOptions actions_options, OnActionsComplete on_complete)
{
// 1. Let tick actions be the list «actions»
// 2. Let actions by tick be the list «tick actions».
Vector<Vector<ActionObject>> actions_by_tick;
actions_by_tick.append(move(actions));

// 3. Return the result of dispatch actions with input state, actions by tick, browsing context, and actions options.
return dispatch_actions(input_state, move(actions_by_tick), browsing_context, move(actions_options), on_complete);
}

}
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/WebDriver/Actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,6 @@ ErrorOr<Vector<Vector<ActionObject>>, WebDriver::Error> extract_an_action_sequen

JS::NonnullGCPtr<JS::Cell> dispatch_actions(InputState&, Vector<Vector<ActionObject>>, HTML::BrowsingContext&, ActionsOptions, OnActionsComplete);
ErrorOr<void, WebDriver::Error> dispatch_tick_actions(InputState&, ReadonlySpan<ActionObject>, AK::Duration, HTML::BrowsingContext&, ActionsOptions const&);
JS::NonnullGCPtr<JS::Cell> dispatch_list_of_actions(InputState&, Vector<ActionObject>, HTML::BrowsingContext&, ActionsOptions, OnActionsComplete);

}
27 changes: 27 additions & 0 deletions Userland/Libraries/LibWeb/WebDriver/InputSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibWeb/WebDriver/Actions.h>
#include <LibWeb/WebDriver/InputSource.h>
#include <LibWeb/WebDriver/InputState.h>

Expand Down Expand Up @@ -128,6 +129,32 @@ InputSource create_input_source(InputState const& input_state, InputSourceType t
VERIFY_NOT_REACHED();
}

// https://w3c.github.io/webdriver/#dfn-remove-an-input-source
void add_input_source(InputState& input_state, String id, InputSource source)
{
// 1. Let input state map be input state's input state map.
// 2. Set input state map[input id] to source.
input_state.input_state_map.set(move(id), move(source));
}

// https://w3c.github.io/webdriver/#dfn-remove-an-input-source
void remove_input_source(InputState& input_state, StringView id)
{
// 1. Assert: None of the items in input state's input cancel list has id equal to input id.
// FIXME: Spec issue: This assertion cannot be correct. For example, when Element Click is executed, the initial
// pointer down action will append a pointer up action to the input cancel list, and the input cancel list
// is never subsequently cleared. So instead of performing this assertion, we remove any action from the
// input cancel list with the provided input ID.
// https://github.com/w3c/webdriver/issues/1809
input_state.input_cancel_list.remove_all_matching([&](ActionObject const& action) {
return action.id == id;
});

// 2. Let input state map be input state's input state map.
// 3. Remove input state map[input id].
input_state.input_state_map.remove(id);
}

// https://w3c.github.io/webdriver/#dfn-get-an-input-source
Optional<InputSource&> get_input_source(InputState& input_state, StringView id)
{
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibWeb/WebDriver/InputSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Optional<InputSourceType> input_source_type_from_string(StringView);
Optional<PointerInputSource::Subtype> pointer_input_source_subtype_from_string(StringView);

InputSource create_input_source(InputState const&, InputSourceType, Optional<PointerInputSource::Subtype>);
void add_input_source(InputState&, String id, InputSource);
void remove_input_source(InputState&, StringView id);
Optional<InputSource&> get_input_source(InputState&, StringView id);
ErrorOr<InputSource*, WebDriver::Error> get_or_create_input_source(InputState&, InputSourceType, StringView id, Optional<PointerInputSource::Subtype>);

Expand Down
26 changes: 1 addition & 25 deletions Userland/Libraries/LibWebView/CookieJar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,30 +294,6 @@ bool CookieJar::path_matches(StringView request_path, StringView cookie_path)
return false;
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.4
String CookieJar::default_path(const URL::URL& url)
{
// 1. Let uri-path be the path portion of the request-uri if such a portion exists (and empty otherwise).
auto uri_path = URL::percent_decode(url.serialize_path());

// 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] != '/'))
return "/"_string;

StringView uri_path_view = uri_path;
size_t last_separator = uri_path_view.find_last('/').value();

// 3. If the uri-path contains no more than one %x2F ("/") character, output %x2F ("/") and skip the remaining step.
if (last_separator == 0)
return "/"_string;

// 4. Output the characters of the uri-path from the first character up to, but not including, the right-most
// %x2F ("/").
// FIXME: The path might not be valid UTF-8.
return MUST(String::from_utf8(uri_path.substring_view(0, last_separator)));
}

// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#name-storage-model
void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, const URL::URL& url, String canonicalized_domain, Web::Cookie::Source source)
{
Expand Down Expand Up @@ -442,7 +418,7 @@ void CookieJar::store_cookie(Web::Cookie::ParsedCookie const& parsed_cookie, con
if (parsed_cookie.path->byte_count() <= 1024)
cookie.path = parsed_cookie.path.value();
} else {
cookie.path = default_path(url);
cookie.path = Web::Cookie::default_path(url);
}

// 12. If the cookie-attribute-list contains an attribute with an attribute-name of "Secure", set the cookie's
Expand Down
Loading