LibURL: Move registrable-domain PSL lookup to PublicSuffixData

Move the registrable-domain helper from URL into PublicSuffixData and
name it find_matching_registrable_domain().

This keeps it alongside find_matching_public_suffix(), making it clear
that both APIs only return results matched from the PSL data, while
Host::public_suffix() implements the URL Standard fallback to the
top-level domain.
This commit is contained in:
Shannon Booth 2026-06-07 20:36:26 +02:00 committed by Shannon Booth
parent ac344a5196
commit 9b80ac00be
7 changed files with 41 additions and 46 deletions

View file

@ -10,6 +10,7 @@
#include <LibHTTP/Cookie/Cookie.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibURL/PublicSuffixData.h>
#include <LibURL/URL.h>
namespace HTTP::Cookie {
@ -161,7 +162,7 @@ bool cookie_matches_url(Cookie const& cookie, URL::URL const& url, String const&
// the cookie's domain.
// - The cookie's domain is not a public suffix, for user agents configured to reject "public suffixes".
bool is_not_host_only_and_domain_matches = (!cookie.host_only && domain_matches(retrieval_host_canonical, cookie.domain))
&& !URL::is_public_suffix(cookie.domain);
&& !URL::PublicSuffixData::the()->is_matching_public_suffix(cookie.domain);
if (!is_host_only_and_has_identical_domain && !is_not_host_only_and_domain_matches)
return false;

View file

@ -238,7 +238,7 @@ Optional<String> Host::registrable_domain() const
host_without_trailing_dot = host_without_trailing_dot.substring_view(0, host_without_trailing_dot.length() - 1);
// NB: If we do not find a registrable domain via the PSL, use everything after the second to last dot.
auto registrable_domain = get_registrable_domain(host_without_trailing_dot);
auto registrable_domain = PublicSuffixData::the()->find_matching_registrable_domain(host_without_trailing_dot);
if (!registrable_domain.has_value()) {
auto last_dot = host_without_trailing_dot.find_last('.');
if (last_dot.has_value()) {

View file

@ -15,7 +15,6 @@
#include <AK/Utf8View.h>
#include <LibURL/Parser.h>
#include <LibURL/PublicSuffixData.h>
#include <LibURL/URL.h>
namespace URL {
@ -505,33 +504,4 @@ ByteString percent_decode(StringView input)
return builder.to_byte_string();
}
bool is_public_suffix(StringView host)
{
return PublicSuffixData::the()->is_matching_public_suffix(host);
}
// https://github.com/publicsuffix/list/wiki/Format#algorithm
Optional<String> get_registrable_domain(StringView host)
{
// The registered or registrable domain is the public suffix plus one additional label.
auto public_suffix = PublicSuffixData::the()->find_matching_public_suffix(host);
if (!public_suffix.has_value() || !host.ends_with(*public_suffix))
return {};
if (host == *public_suffix)
return {};
auto subhost = host.substring_view(0, host.length() - public_suffix->bytes_as_string_view().length());
subhost = subhost.trim("."sv, TrimMode::Right);
if (subhost.is_empty())
return {};
size_t start_index = 0;
if (auto index = subhost.find_last('.'); index.has_value())
start_index = *index + 1;
return MUST(String::from_utf8(host.substring_view(start_index)));
}
}

View file

@ -210,9 +210,6 @@ Optional<URL> create_with_url_or_path(ByteString const&);
Optional<URL> create_with_file_scheme(ByteString const& path, ByteString const& fragment = {}, ByteString const& hostname = {});
URL create_with_data(StringView mime_type, StringView payload, bool is_base64 = false);
bool is_public_suffix(StringView host);
Optional<String> get_registrable_domain(StringView host);
inline URL about_blank() { return URL::about("blank"_string); }
inline URL about_srcdoc() { return URL::about("srcdoc"_string); }

View file

@ -13,6 +13,7 @@
#include <AK/Vector.h>
#include <LibDatabase/Database.h>
#include <LibHTTP/Cookie/ParsedCookie.h>
#include <LibURL/PublicSuffixData.h>
#include <LibURL/URL.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/ViewImplementation.h>
@ -198,7 +199,7 @@ void CookieJar::set_cookie(URL::URL const& url, HTTP::Cookie::ParsedCookie const
return;
// 9. If the user agent is configured to reject "public suffixes" and the domain-attribute is a public suffix:
if (URL::is_public_suffix(domain_attribute)) {
if (URL::PublicSuffixData::the()->is_matching_public_suffix(domain_attribute)) {
// 1. Let request-host-canonical be the canonicalized request-host.
// 2. If request-host fails to be canonicalized then abort this algorithm and ignore the cookie entirely.

View file

@ -37,6 +37,7 @@ public:
bool is_matching_public_suffix(StringView host);
Optional<String> find_matching_public_suffix(StringView string);
Optional<String> find_matching_registrable_domain(StringView string);
};
@ -139,6 +140,30 @@ Optional<String> PublicSuffixData::find_matching_public_suffix(StringView string
return MUST(return_string_builder.to_string());
}
// https://github.com/publicsuffix/list/wiki/Format#algorithm
Optional<String> PublicSuffixData::find_matching_registrable_domain(StringView host)
{
// The registered or registrable domain is the public suffix plus one additional label.
auto public_suffix = find_matching_public_suffix(host);
if (!public_suffix.has_value() || !host.ends_with(*public_suffix))
return {};
if (host == *public_suffix)
return {};
auto subhost = host.substring_view(0, host.length() - public_suffix->bytes_as_string_view().length());
subhost = subhost.trim("."sv, TrimMode::Right);
if (subhost.is_empty())
return {};
size_t start_index = 0;
if (auto index = subhost.find_last('.'); index.has_value())
start_index = *index + 1;
return MUST(String::from_utf8(host.substring_view(start_index)));
}
}
"""

View file

@ -9,6 +9,7 @@
#include <LibTest/TestCase.h>
#include <LibURL/Parser.h>
#include <LibURL/PublicSuffixData.h>
#include <LibURL/URL.h>
TEST_CASE(basic)
@ -633,47 +634,47 @@ TEST_CASE(invalid_domain_code_points)
TEST_CASE(get_registrable_domain)
{
{
auto domain = URL::get_registrable_domain({});
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain({});
EXPECT(!domain.has_value());
}
{
auto domain = URL::get_registrable_domain("foobar"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("foobar"sv);
EXPECT(!domain.has_value());
}
{
auto domain = URL::get_registrable_domain("com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("com"sv);
EXPECT(!domain.has_value());
}
{
auto domain = URL::get_registrable_domain(".com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain(".com"sv);
EXPECT(!domain.has_value());
}
{
auto domain = URL::get_registrable_domain("example.com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("example.com"sv);
VERIFY(domain.has_value());
EXPECT_EQ(*domain, "example.com"sv);
}
{
auto domain = URL::get_registrable_domain(".example.com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain(".example.com"sv);
VERIFY(domain.has_value());
EXPECT_EQ(*domain, "example.com"sv);
}
{
auto domain = URL::get_registrable_domain("www.example.com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("www.example.com"sv);
VERIFY(domain.has_value());
EXPECT_EQ(*domain, "example.com"sv);
}
{
auto domain = URL::get_registrable_domain("sub.www.example.com"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("sub.www.example.com"sv);
VERIFY(domain.has_value());
EXPECT_EQ(*domain, "example.com"sv);
}
{
auto domain = URL::get_registrable_domain("github.io"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("github.io"sv);
EXPECT(!domain.has_value());
}
{
auto domain = URL::get_registrable_domain("ladybird.github.io"sv);
auto domain = URL::PublicSuffixData::the()->find_matching_registrable_domain("ladybird.github.io"sv);
VERIFY(domain.has_value());
EXPECT_EQ(*domain, "ladybird.github.io"sv);
}