ladybird/Tests/LibURL/TestPublicSuffix.cpp
Shannon Booth 3f7b31fc78 LibURL: Let Host use PublicSuffixData star rule matching
Ever since PublicSuffixData was created, it was using "no star rule"
matching, which is what is needed for the address bar to distinguish
between a domain and a search. URL::Host on the other hand requires
the fallback star rule. Which rule is needed depends on the use case
of the PSL. Support both use cases by a flag in PublicSuffixData.
2026-06-16 06:14:07 +02:00

163 lines
8.5 KiB
C++

/*
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <LibURL/Parser.h>
#include <LibURL/PublicSuffixData.h>
TEST_CASE(public_suffix_matching_for_psl_rules)
{
struct TestCase {
StringView input;
bool is_public_suffix;
Optional<StringView> public_suffix;
Optional<StringView> registrable_domain;
};
TestCase test_cases[] {
{ "com"sv, true, "com"sv, OptionalNone {} },
{ "COM"sv, true, "com"sv, OptionalNone {} },
{ ".com"sv, true, "com"sv, OptionalNone {} },
{ "com."sv, true, "com."sv, OptionalNone {} },
{ ".com."sv, true, "com."sv, OptionalNone {} },
{ "..com."sv, true, "com."sv, OptionalNone {} },
{ "example.com"sv, false, "com"sv, "example.com"sv },
{ "EXAMPLE.COM"sv, false, "com"sv, "example.com"sv },
{ ".example.com"sv, false, "com"sv, "example.com"sv },
{ "www.example.com"sv, false, "com"sv, "example.com"sv },
{ "sub.www.example.com"sv, false, "com"sv, "example.com"sv },
{ "www.example.com."sv, false, "com."sv, "example.com."sv },
{ "not-a-public-suffix.com"sv, false, "com"sv, "not-a-public-suffix.com"sv },
{ "com.br"sv, true, "com.br"sv, OptionalNone {} },
{ "not-a-public-suffix.com.br"sv, false, "com.br"sv, "not-a-public-suffix.com.br"sv },
{ "co.uk"sv, true, "co.uk"sv, OptionalNone {} },
{ "ac.uk"sv, true, "ac.uk"sv, OptionalNone {} },
{ "gov.uk"sv, true, "gov.uk"sv, OptionalNone {} },
{ "com.au"sv, true, "com.au"sv, OptionalNone {} },
{ "co.jp"sv, true, "co.jp"sv, OptionalNone {} },
{ "bbc.co.uk"sv, false, "co.uk"sv, "bbc.co.uk"sv },
{ "www.bbc.co.uk"sv, false, "co.uk"sv, "bbc.co.uk"sv },
{ "github.io"sv, true, "github.io"sv, OptionalNone {} },
{ "ladybird.github.io"sv, false, "github.io"sv, "ladybird.github.io"sv },
{ "whatwg.github.io"sv, false, "github.io"sv, "whatwg.github.io"sv },
{ "公司.cn"sv, true, "xn--55qx5d.cn"sv, OptionalNone {} },
{ "www.公司.cn"sv, false, "xn--55qx5d.cn"sv, "www.xn--55qx5d.cn"sv },
{ "www.xn--55qx5d.cn"sv, false, "xn--55qx5d.cn"sv, "www.xn--55qx5d.cn"sv },
};
for (auto const& test_case : test_cases) {
auto exclude_star_rule = URL::PublicSuffixData::IncludeStarRule::No;
EXPECT_EQ(URL::PublicSuffixData::is_matching_public_suffix(test_case.input, exclude_star_rule), test_case.is_public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(test_case.input, exclude_star_rule), test_case.public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(test_case.input, exclude_star_rule), test_case.registrable_domain);
auto host = URL::Parser::parse_host(test_case.input);
VERIFY(host.has_value());
EXPECT_EQ(URL::PublicSuffixData::is_matching_public_suffix(*host, exclude_star_rule), test_case.is_public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(*host, exclude_star_rule), test_case.public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(*host, exclude_star_rule), test_case.registrable_domain);
EXPECT_EQ(host->public_suffix(), test_case.public_suffix);
EXPECT_EQ(host->registrable_domain(), test_case.registrable_domain);
}
}
TEST_CASE(public_suffix_matching_without_psl_rule)
{
struct TestCase {
StringView input;
bool is_public_suffix_with_star_rule;
Optional<StringView> host_public_suffix;
Optional<StringView> host_registrable_domain;
};
TestCase test_cases[] {
{ "foobar"sv, true, "foobar"sv, OptionalNone {} },
{ "foobar."sv, true, "foobar."sv, OptionalNone {} },
{ "not-a-public-suffix"sv, true, "not-a-public-suffix"sv, OptionalNone {} },
{ "a.example"sv, false, "example"sv, "a.example"sv },
{ "a.example."sv, false, "example."sv, "a.example."sv },
{ "b.b.example"sv, false, "example"sv, "b.example"sv },
{ "b.b.example."sv, false, "example."sv, "b.example."sv },
{ "foo.not-a-public-suffix"sv, false, "not-a-public-suffix"sv, "foo.not-a-public-suffix"sv },
{ "sub.foo.not-a-public-suffix"sv, false, "not-a-public-suffix"sv, "foo.not-a-public-suffix"sv },
{ "إختبار"sv, true, "xn--kgbechtv"sv, OptionalNone {} },
{ "example.إختبار"sv, false, "xn--kgbechtv"sv, "example.xn--kgbechtv"sv },
{ "example.إختبار."sv, false, "xn--kgbechtv."sv, "example.xn--kgbechtv."sv },
{ "sub.example.إختبار"sv, false, "xn--kgbechtv"sv, "example.xn--kgbechtv"sv },
};
for (auto const& test_case : test_cases) {
auto exclude_star_rule = URL::PublicSuffixData::IncludeStarRule::No;
auto include_star_rule = URL::PublicSuffixData::IncludeStarRule::Yes;
EXPECT(!URL::PublicSuffixData::is_matching_public_suffix(test_case.input, exclude_star_rule));
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(test_case.input, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(test_case.input, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::is_matching_public_suffix(test_case.input, include_star_rule), test_case.is_public_suffix_with_star_rule);
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(test_case.input, include_star_rule), test_case.host_public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(test_case.input, include_star_rule), test_case.host_registrable_domain);
auto host = URL::Parser::parse_host(test_case.input);
VERIFY(host.has_value());
EXPECT(!URL::PublicSuffixData::is_matching_public_suffix(*host, exclude_star_rule));
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(*host, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(*host, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::is_matching_public_suffix(*host, include_star_rule), test_case.is_public_suffix_with_star_rule);
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(*host, include_star_rule), test_case.host_public_suffix);
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(*host, include_star_rule), test_case.host_registrable_domain);
EXPECT_EQ(host->public_suffix(), test_case.host_public_suffix);
EXPECT_EQ(host->registrable_domain(), test_case.host_registrable_domain);
}
}
TEST_CASE(invalid_hosts)
{
StringView raw_invalid_inputs[] {
" "sv,
"/"sv,
"com/"sv,
"/com"sv,
" com"sv,
"com "sv,
};
// Above inputs are not valid hosts, so should not be able to be parsed or matched in the PSL.
for (auto const& input : raw_invalid_inputs) {
auto exclude_star_rule = URL::PublicSuffixData::IncludeStarRule::No;
auto host = URL::Parser::parse_host(input);
EXPECT(!host.has_value());
EXPECT(!URL::PublicSuffixData::is_matching_public_suffix(input, exclude_star_rule));
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(input, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(input, exclude_star_rule), OptionalNone {});
}
}
TEST_CASE(public_suffix_matching_for_ip_addresses)
{
StringView test_cases[] {
"127.0.0.1"sv,
"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"sv,
};
for (auto const& input : test_cases) {
auto exclude_star_rule = URL::PublicSuffixData::IncludeStarRule::No;
EXPECT(!URL::PublicSuffixData::is_matching_public_suffix(input, exclude_star_rule));
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(input, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(input, exclude_star_rule), OptionalNone {});
auto host = URL::Parser::parse_host(input);
VERIFY(host.has_value());
EXPECT(!URL::PublicSuffixData::is_matching_public_suffix(*host, exclude_star_rule));
EXPECT_EQ(URL::PublicSuffixData::find_matching_public_suffix(*host, exclude_star_rule), OptionalNone {});
EXPECT_EQ(URL::PublicSuffixData::find_matching_registrable_domain(*host, exclude_star_rule), OptionalNone {});
EXPECT_EQ(host->public_suffix(), OptionalNone {});
EXPECT_EQ(host->registrable_domain(), OptionalNone {});
}
}