LibURL: Replace Host's Empty state with making Url's Host optional

A couple of reasons:
- Origin's Host (when in the tuple state) can't be null
- There's an "empty host" concept in the spec which is NOT the same as a
  null Host, and that was confusing me.
This commit is contained in:
Sam Atkins 2024-11-27 12:48:28 +00:00 committed by Andreas Kling
parent 8b984c0c57
commit 90e763de4c
Notes: github-actions[bot] 2024-11-30 11:24:09 +00:00
17 changed files with 44 additions and 42 deletions

View file

@ -36,9 +36,9 @@ struct ProxyData {
proxy_data.type = ProxyData::Type::SOCKS5;
if (!url.host().has<URL::IPv4Address>())
if (!url.host().has_value() || !url.host()->has<URL::IPv4Address>())
return Error::from_string_literal("Invalid proxy host, must be an IPv4 address");
proxy_data.host_ipv4 = url.host().get<URL::IPv4Address>();
proxy_data.host_ipv4 = url.host()->get<URL::IPv4Address>();
auto port = url.port();
if (!port.has_value())

View file

@ -26,6 +26,6 @@ using IPv6Address = Array<u16, 8>;
// https://url.spec.whatwg.org/#concept-host
// A host is a domain, an IP address, an opaque host, or an empty host. Typically a host serves as a network address,
// but it is sometimes used as opaque identifier in URLs where a network address is not necessary.
using Host = Variant<IPv4Address, IPv6Address, String, Empty>;
using Host = Variant<IPv4Address, IPv6Address, String>;
}

View file

@ -1574,7 +1574,7 @@ URL Parser::basic_parse(StringView raw_input, Optional<URL> const& base_url, URL
continue;
}
// 5. Otherwise, if state override is given and urls host is null, append the empty string to urls path.
else if (state_override.has_value() && url->host().has<Empty>()) {
else if (state_override.has_value() && !url->host().has_value()) {
url->append_slash();
}
break;

View file

@ -88,7 +88,7 @@ void URL::set_host(Host host)
// https://url.spec.whatwg.org/#concept-host-serializer
ErrorOr<String> URL::serialized_host() const
{
return Parser::serialize_host(m_data->host);
return Parser::serialize_host(m_data->host.value());
}
void URL::set_port(Optional<u16> port)
@ -119,7 +119,7 @@ void URL::append_path(StringView path)
bool URL::cannot_have_a_username_or_password_or_port() const
{
// A URL cannot have a username/password/port if its host is null or the empty string, or its scheme is "file".
return m_data->host.has<Empty>() || m_data->host == String {} || m_data->scheme == "file"sv;
return !m_data->host.has_value() || m_data->host == String {} || m_data->scheme == "file"sv;
}
// FIXME: This is by no means complete.
@ -142,8 +142,8 @@ bool URL::compute_validity() const
return false;
}
// NOTE: A file URL's host should be the empty string for localhost, not null.
if (m_data->scheme == "file" && m_data->host.has<Empty>())
// FIXME: A file URL's host should be the empty string for localhost, not null.
if (m_data->scheme == "file" && !m_data->host.has_value())
return false;
return true;
@ -252,7 +252,7 @@ ByteString URL::serialize(ExcludeFragment exclude_fragment) const
output.append(':');
// 2. If urls host is non-null:
if (!m_data->host.has<Empty>()) {
if (m_data->host.has_value()) {
// 1. Append "//" to output.
output.append("//"sv);
@ -285,7 +285,7 @@ ByteString URL::serialize(ExcludeFragment exclude_fragment) const
if (cannot_be_a_base_url()) {
output.append(m_data->paths[0]);
} else {
if (m_data->host.has<Empty>() && m_data->paths.size() > 1 && m_data->paths[0].is_empty())
if (!m_data->host.has_value() && m_data->paths.size() > 1 && m_data->paths[0].is_empty())
output.append("/."sv);
for (auto& segment : m_data->paths) {
output.append('/');
@ -321,7 +321,7 @@ ByteString URL::serialize_for_display() const
builder.append(m_data->scheme);
builder.append(':');
if (!m_data->host.has<Empty>()) {
if (m_data->host.has_value()) {
builder.append("//"sv);
builder.append(serialized_host().release_value_but_fixme_should_propagate_errors());
if (m_data->port.has_value())
@ -331,7 +331,7 @@ ByteString URL::serialize_for_display() const
if (cannot_be_a_base_url()) {
builder.append(m_data->paths[0]);
} else {
if (m_data->host.has<Empty>() && m_data->paths.size() > 1 && m_data->paths[0].is_empty())
if (!m_data->host.has_value() && m_data->paths.size() > 1 && m_data->paths[0].is_empty())
builder.append("/."sv);
for (auto& segment : m_data->paths) {
builder.append('/');
@ -391,7 +391,7 @@ Origin URL::origin() const
// -> "wss"
if (scheme().is_one_of("ftp"sv, "http"sv, "https"sv, "ws"sv, "wss"sv)) {
// Return the tuple origin (urls scheme, urls host, urls port, null).
return Origin(scheme().to_byte_string(), host(), port());
return Origin(scheme().to_byte_string(), host().value(), port());
}
// -> "file"

View file

@ -83,7 +83,7 @@ public:
String const& scheme() const { return m_data->scheme; }
String const& username() const { return m_data->username; }
String const& password() const { return m_data->password; }
Host const& host() const { return m_data->host; }
Optional<Host> const& host() const { return m_data->host; }
ErrorOr<String> serialized_host() const;
ByteString basename() const;
Optional<String> const& query() const { return m_data->query; }
@ -171,7 +171,7 @@ private:
String password;
// A URLs host is null or a host. It is initially null.
Host host;
Optional<Host> host;
// A URLs port is either null or a 16-bit unsigned integer that identifies a networking port. It is initially null.
Optional<u16> port;

View file

@ -284,7 +284,7 @@ WebIDL::ExceptionOr<String> DOMURL::host() const
auto& url = m_url;
// 2. If urls host is null, then return the empty string.
if (url.host().has<Empty>())
if (!url.host().has_value())
return String {};
// 3. If urls port is null, return urls host, serialized.
@ -312,7 +312,7 @@ WebIDL::ExceptionOr<String> DOMURL::hostname() const
auto& vm = realm().vm();
// 1. If thiss URLs host is null, then return the empty string.
if (m_url.host().has<Empty>())
if (!m_url.host().has_value())
return String {};
// 2. Return thiss URLs host, serialized.
@ -477,6 +477,7 @@ void DOMURL::set_hash(String const& hash)
}
// https://url.spec.whatwg.org/#concept-domain
// FIXME: Move into URL::Host
bool host_is_domain(URL::Host const& host)
{
// A domain is a non-empty ASCII string that identifies a realm within a network.

View file

@ -322,7 +322,7 @@ WebIDL::ExceptionOr<GC::Ptr<PendingResponse>> main_fetch(JS::Realm& realm, Infra
// - requests current URLs scheme is "http"
request->current_url().scheme() == "http"sv
// - requests current URLs host is a domain
&& DOMURL::host_is_domain(request->current_url().host())
&& request->current_url().host().has_value() && DOMURL::host_is_domain(request->current_url().host().value())
// FIXME: - Matching requests current URLs host per Known HSTS Host Domain Name Matching results in either a
// superdomain match with an asserted includeSubDomains directive or a congruent match (with or without an
// asserted includeSubDomains directive) [HSTS]; or DNS resolution for the request finds a matching HTTPS RR

View file

@ -44,7 +44,7 @@ bool url_matches_about_blank(URL::URL const& url)
&& url.paths().size() == 1 && url.paths()[0] == "blank"sv
&& url.username().is_empty()
&& url.password().is_empty()
&& url.host().has<Empty>();
&& !url.host().has_value();
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#matches-about:srcdoc
@ -56,7 +56,7 @@ bool url_matches_about_srcdoc(URL::URL const& url)
&& !url.query().has_value()
&& url.username().is_empty()
&& url.password().is_empty()
&& url.host().has<Empty>();
&& !url.host().has_value();
}
// https://html.spec.whatwg.org/multipage/document-sequences.html#determining-the-origin

View file

@ -167,7 +167,7 @@ String HTMLHyperlinkElementUtils::host() const
auto const& url = m_url;
// 3. If url or url's host is null, return the empty string.
if (!url.has_value() || url->host().has<Empty>())
if (!url.has_value() || !url->host().has_value())
return String {};
// 4. If url's port is null, return url's host, serialized.
@ -206,7 +206,8 @@ String HTMLHyperlinkElementUtils::hostname() const
URL::URL url(href());
// 3. If url or url's host is null, return the empty string.
if (url.host().has<Empty>())
// FIXME: How can url be null here?
if (!url.host().has_value())
return String {};
// 4. Return url's host, serialized.

View file

@ -203,7 +203,7 @@ WebIDL::ExceptionOr<String> Location::host() const
auto url = this->url();
// 3. If url's host is null, return the empty string.
if (url.host().has<Empty>())
if (!url.host().has_value())
return String {};
// 4. If url's port is null, return url's host, serialized.
@ -233,7 +233,7 @@ WebIDL::ExceptionOr<String> Location::hostname() const
auto url = this->url();
// 2. If this's url's host is null, return the empty string.
if (url.host().has<Empty>())
if (!url.host().has_value())
return String {};
// 3. Return this's url's host, serialized.

View file

@ -46,7 +46,7 @@ WebIDL::ExceptionOr<String> WorkerLocation::host() const
auto const& url = m_global_scope->url();
// 2. If url's host is null, return the empty string.
if (url.host().has<Empty>())
if (!url.host().has_value())
return String {};
// 3. If url's port is null, return url's host, serialized.
@ -67,11 +67,11 @@ WebIDL::ExceptionOr<String> WorkerLocation::hostname() const
auto const& host = m_global_scope->url().host();
// 2. If host is null, return the empty string.
if (host.has<Empty>())
if (!host.has_value())
return String {};
// 3. Return host, serialized.
return TRY_OR_THROW_OOM(vm, URL::Parser::serialize_host(host));
return TRY_OR_THROW_OOM(vm, URL::Parser::serialize_host(host.value()));
}
// https://html.spec.whatwg.org/multipage/workers.html#dom-workerlocation-port

View file

@ -20,7 +20,7 @@ void upgrade_a_mixed_content_request_to_a_potentially_trustworthy_url_if_appropr
SecureContexts::is_url_potentially_trustworthy(request.url()) == SecureContexts::Trustworthiness::PotentiallyTrustworthy
// 2. requests URLs host is an IP address.
|| request.url().host().has<URL::IPv4Address>() || request.url().host().has<URL::IPv6Address>()
|| (request.url().host().has_value() && (request.url().host()->has<URL::IPv4Address>() || request.url().host()->has<URL::IPv6Address>()))
// 3. §4.3 Does settings prohibit mixed security contexts? returns "Does Not Restrict Mixed Security Contents" when applied to requests client.
|| does_settings_prohibit_mixed_security_contexts(request.client()) == ProhibitsMixedSecurityContexts::DoesNotRestrictMixedSecurityContexts

View file

@ -494,7 +494,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, Stri
// NOTE: This is handled in the overload lacking the async argument.
// 8. If parsedURLs host is non-null, then:
if (!parsed_url.host().has<Empty>()) {
if (parsed_url.host().has_value()) {
// 1. If the username argument is not null, set the username given parsedURL and username.
if (username.has_value())
parsed_url.set_username(username.value());

View file

@ -220,7 +220,7 @@ void CookieJar::expire_cookies_with_time_offset(AK::Duration offset)
// https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-15.html#section-5.1.2
Optional<String> CookieJar::canonicalize_domain(const URL::URL& url)
{
if (!url.is_valid() || url.host().has<Empty>())
if (!url.is_valid() || !url.host().has_value())
return {};
// 1. Convert the host name to a sequence of individual domain name labels.

View file

@ -570,7 +570,7 @@ void ViewImplementation::handle_web_content_process_crash(LoadErrorPage load_err
builder.append(escape_html_entities(m_url.to_byte_string()));
builder.append("</title></head><body>"sv);
builder.append("<h1>Web page crashed"sv);
if (!m_url.host().has<Empty>()) {
if (m_url.host().has_value()) {
builder.appendff(" on {}", escape_html_entities(m_url.serialized_host().release_value_but_fixme_should_propagate_errors()));
}
builder.append("</h1>"sv);

View file

@ -206,7 +206,7 @@ TEST_CASE(about_url)
URL::URL url("about:blank"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "about");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize_path(), "blank");
EXPECT(!url.query().has_value());
EXPECT(!url.fragment().has_value());
@ -218,7 +218,7 @@ TEST_CASE(mailto_url)
URL::URL url("mailto:mail@example.com"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "mailto");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.port_or_default(), 0);
EXPECT_EQ(url.path_segment_count(), 1u);
EXPECT_EQ(url.path_segment_at_index(0), "mail@example.com");
@ -232,7 +232,7 @@ TEST_CASE(mailto_url_with_subject)
URL::URL url("mailto:mail@example.com?subject=test"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "mailto");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.port_or_default(), 0);
EXPECT_EQ(url.path_segment_count(), 1u);
EXPECT_EQ(url.path_segment_at_index(0), "mail@example.com");

View file

@ -14,7 +14,7 @@ TEST_CASE(data_url)
URL::URL url("data:text/html,test"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data:text/html,test");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -27,7 +27,7 @@ TEST_CASE(data_url_default_mime_type)
URL::URL url("data:,test"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data:,test");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -40,7 +40,7 @@ TEST_CASE(data_url_encoded)
URL::URL url("data:text/html,Hello%20friends%2C%0X%X0"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data:text/html,Hello%20friends%2C%0X%X0");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -53,7 +53,7 @@ TEST_CASE(data_url_base64_encoded)
URL::URL url("data:text/html;base64,dGVzdA=="sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data:text/html;base64,dGVzdA==");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -66,7 +66,7 @@ TEST_CASE(data_url_base64_encoded_default_mime_type)
URL::URL url("data:;base64,dGVzdA=="sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data:;base64,dGVzdA==");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -79,7 +79,7 @@ TEST_CASE(data_url_base64_encoded_with_whitespace)
URL::URL url("data: text/html ; bAsE64 , dGVz dA== "sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
EXPECT_EQ(url.serialize(), "data: text/html ; bAsE64 , dGVz dA==");
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
@ -92,7 +92,7 @@ TEST_CASE(data_url_base64_encoded_with_inline_whitespace)
URL::URL url("data:text/javascript;base64,%20ZD%20Qg%0D%0APS%20An%20Zm91cic%0D%0A%207%20"sv);
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
EXPECT_EQ(data_url.mime_type.serialized(), "text/javascript");
@ -105,7 +105,7 @@ TEST_CASE(data_url_completed_with_fragment)
EXPECT(url.is_valid());
EXPECT_EQ(url.scheme(), "data");
EXPECT_EQ(url.fragment(), "a");
EXPECT(url.host().has<Empty>());
EXPECT(!url.host().has_value());
auto data_url = TRY_OR_FAIL(Web::Fetch::Infrastructure::process_data_url(url));
EXPECT_EQ(data_url.mime_type.serialized(), "text/plain");