// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/schemeful_site.h" #include "base/test/metrics/histogram_tester.h" #include "net/base/url_util.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" #include "url/url_util.h" namespace net { TEST(SchemefulSiteTest, DifferentOriginSameRegisterableDomain) { // List of origins which should all share a schemeful site. url::Origin kTestOrigins[] = { url::Origin::Create(GURL("http://a.foo.test")), url::Origin::Create(GURL("http://b.foo.test")), url::Origin::Create(GURL("http://foo.test")), url::Origin::Create(GURL("http://a.b.foo.test"))}; for (const auto& origin_a : kTestOrigins) { for (const auto& origin_b : kTestOrigins) { EXPECT_EQ(SchemefulSite(origin_a), SchemefulSite(origin_b)); } } } TEST(SchemefulSiteTest, Operators) { // Create a list of origins that should all have different schemeful sites. // These are in ascending order. url::Origin kTestOrigins[] = { url::Origin::Create(GURL("data:text/html,Hello World")), url::Origin::Create(GURL("file://foo")), url::Origin::Create(GURL("http://a.bar.test")), url::Origin::Create(GURL("http://c.test")), url::Origin::Create(GURL("http://d.test")), url::Origin::Create(GURL("http://a.foo.test")), url::Origin::Create(GURL("https://a.bar.test")), url::Origin::Create(GURL("https://c.test")), url::Origin::Create(GURL("https://d.test")), url::Origin::Create(GURL("https://a.foo.test"))}; // Compare each origin to every other origin and ensure the operators work as // expected. for (size_t first = 0; first < std::size(kTestOrigins); ++first) { SchemefulSite site1 = SchemefulSite(kTestOrigins[first]); SCOPED_TRACE(site1.GetDebugString()); EXPECT_EQ(site1, site1); EXPECT_FALSE(site1 < site1); // Check the operators work on copies. SchemefulSite site1_copy = site1; EXPECT_EQ(site1, site1_copy); EXPECT_FALSE(site1 < site1_copy); for (size_t second = first + 1; second < std::size(kTestOrigins); ++second) { SchemefulSite site2 = SchemefulSite(kTestOrigins[second]); SCOPED_TRACE(site2.GetDebugString()); EXPECT_TRUE(site1 < site2); EXPECT_FALSE(site2 < site1); EXPECT_FALSE(site1 == site2); EXPECT_FALSE(site2 == site1); } } } TEST(SchemefulSiteTest, SchemeUsed) { url::Origin origin_a = url::Origin::Create(GURL("https://foo.test")); url::Origin origin_b = url::Origin::Create(GURL("http://foo.test")); EXPECT_NE(SchemefulSite(origin_a), SchemefulSite(origin_b)); } TEST(SchemefulSiteTest, PortIgnored) { // Both origins are non-opaque. url::Origin origin_a = url::Origin::Create(GURL("https://foo.test:80")); url::Origin origin_b = url::Origin::Create(GURL("https://foo.test:2395")); EXPECT_EQ(SchemefulSite(origin_a), SchemefulSite(origin_b)); } TEST(SchemefulSiteTest, TopLevelDomainsNotModified) { url::Origin origin_tld = url::Origin::Create(GURL("https://com")); EXPECT_EQ(url::Origin::Create(GURL("https://com")), SchemefulSite(origin_tld).GetInternalOriginForTesting()); // Unknown TLD's should not be modified. url::Origin origin_tld_unknown = url::Origin::Create(GURL("https://bar:1234")); EXPECT_EQ(url::Origin::Create(GURL("https://bar")), SchemefulSite(origin_tld_unknown).GetInternalOriginForTesting()); // Check for two-part TLDs. url::Origin origin_two_part_tld = url::Origin::Create(GURL("http://a.co.uk")); EXPECT_EQ(url::Origin::Create(GURL("http://a.co.uk")), SchemefulSite(origin_two_part_tld).GetInternalOriginForTesting()); } TEST(SchemefulSiteTest, NonStandardScheme) { url::ScopedSchemeRegistryForTests scoped_registry; url::AddStandardScheme("foo", url::SCHEME_WITH_HOST); url::Origin origin = url::Origin::Create(GURL("foo://a.b.test")); EXPECT_FALSE(origin.opaque()); // We should not use registerable domains for non-standard schemes, even if // one exists for the host. EXPECT_EQ(url::Origin::Create(GURL("foo://a.b.test")), SchemefulSite(origin).GetInternalOriginForTesting()); } TEST(SchemefulSiteTest, IPBasedOriginsRemovePort) { // IPv4 and IPv6 origins should not be modified, except for removing their // ports. url::Origin origin_ipv4_a = url::Origin::Create(GURL("http://127.0.0.1:1234")); url::Origin origin_ipv4_b = url::Origin::Create(GURL("http://127.0.0.1")); EXPECT_EQ(url::Origin::Create(GURL("http://127.0.0.1")), SchemefulSite(origin_ipv4_a).GetInternalOriginForTesting()); EXPECT_EQ(SchemefulSite(origin_ipv4_a), SchemefulSite(origin_ipv4_b)); url::Origin origin_ipv6 = url::Origin::Create(GURL("https://[::1]")); EXPECT_EQ(url::Origin::Create(GURL("https://[::1]")), SchemefulSite(origin_ipv6).GetInternalOriginForTesting()); } TEST(SchemefulSiteTest, LocalhostOriginsRemovePort) { // Localhost origins should not be modified, except for removing their ports. url::Origin localhost_http = url::Origin::Create(GURL("http://localhost:1234")); EXPECT_EQ(url::Origin::Create(GURL("http://localhost")), SchemefulSite(localhost_http).GetInternalOriginForTesting()); url::Origin localhost_https = url::Origin::Create(GURL("https://localhost:1234")); EXPECT_EQ(url::Origin::Create(GURL("https://localhost")), SchemefulSite(localhost_https).GetInternalOriginForTesting()); } TEST(SchemefulSiteTest, OpaqueOrigins) { url::Origin opaque_origin_a = url::Origin::Create(GURL("data:text/html,Hello World")); // The schemeful site of an opaque origin should always equal other schemeful // site instances of the same origin. EXPECT_EQ(SchemefulSite(opaque_origin_a), SchemefulSite(opaque_origin_a)); url::Origin opaque_origin_b = url::Origin::Create(GURL("data:text/html,Hello World")); // Two different opaque origins should never have the same SchemefulSite. EXPECT_NE(SchemefulSite(opaque_origin_a), SchemefulSite(opaque_origin_b)); } TEST(SchemefulSiteTest, FileOriginWithoutHostname) { SchemefulSite site1(url::Origin::Create(GURL("file:///"))); SchemefulSite site2(url::Origin::Create(GURL("file:///path/"))); EXPECT_EQ(site1, site2); EXPECT_TRUE(site1.GetInternalOriginForTesting().host().empty()); } TEST(SchemefulSiteTest, SchemeWithNetworkHost) { url::ScopedSchemeRegistryForTests scheme_registry; AddStandardScheme("network", url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION); AddStandardScheme("non-network", url::SCHEME_WITH_HOST); ASSERT_TRUE(IsStandardSchemeWithNetworkHost("network")); ASSERT_FALSE(IsStandardSchemeWithNetworkHost("non-network")); std::optional network_host_site = SchemefulSite::CreateIfHasRegisterableDomain( url::Origin::Create(GURL("network://site.example.test:1337"))); EXPECT_TRUE(network_host_site.has_value()); EXPECT_EQ("network", network_host_site->GetInternalOriginForTesting().scheme()); EXPECT_EQ("example.test", network_host_site->GetInternalOriginForTesting().host()); std::optional non_network_host_site_null = SchemefulSite::CreateIfHasRegisterableDomain( url::Origin::Create(GURL("non-network://site.example.test"))); EXPECT_FALSE(non_network_host_site_null.has_value()); SchemefulSite non_network_host_site(GURL("non-network://site.example.test")); EXPECT_EQ("non-network", non_network_host_site.GetInternalOriginForTesting().scheme()); // The host is used as-is, without attempting to get a registrable domain. EXPECT_EQ("site.example.test", non_network_host_site.GetInternalOriginForTesting().host()); } TEST(SchemefulSiteTest, FileSchemeHasRegistrableDomain) { // Test file origin without host. url::Origin origin_file = url::Origin::Create(GURL("file:///dir1/dir2/file.txt")); EXPECT_TRUE(origin_file.host().empty()); SchemefulSite site_file(origin_file); EXPECT_EQ(url::Origin::Create(GURL("file:///")), site_file.GetInternalOriginForTesting()); // Test file origin with host (with registrable domain). url::Origin origin_file_with_host = url::Origin::Create(GURL("file://host.example.test/file")); ASSERT_EQ("host.example.test", origin_file_with_host.host()); SchemefulSite site_file_with_host(origin_file_with_host); EXPECT_EQ(url::Origin::Create(GURL("file://example.test")), site_file_with_host.GetInternalOriginForTesting()); // Test file origin with host same as registrable domain. url::Origin origin_file_registrable_domain = url::Origin::Create(GURL("file://example.test/file")); ASSERT_EQ("example.test", origin_file_registrable_domain.host()); SchemefulSite site_file_registrable_domain(origin_file_registrable_domain); EXPECT_EQ(url::Origin::Create(GURL("file://example.test")), site_file_registrable_domain.GetInternalOriginForTesting()); EXPECT_NE(site_file, site_file_with_host); EXPECT_NE(site_file, site_file_registrable_domain); EXPECT_EQ(site_file_with_host, site_file_registrable_domain); } TEST(SchemefulSiteTest, SerializationConsistent) { url::ScopedSchemeRegistryForTests scoped_registry; url::AddStandardScheme("chrome", url::SCHEME_WITH_HOST); // List of origins which should all share a schemeful site. SchemefulSite kTestSites[] = { SchemefulSite(url::Origin::Create(GURL("http://a.foo.test"))), SchemefulSite(url::Origin::Create(GURL("https://b.foo.test"))), SchemefulSite(url::Origin::Create(GURL("http://b.foo.test"))), SchemefulSite(url::Origin::Create(GURL("http://a.b.foo.test"))), SchemefulSite(url::Origin::Create(GURL("chrome://a.b.test")))}; for (const auto& site : kTestSites) { SCOPED_TRACE(site.GetDebugString()); EXPECT_FALSE(site.GetInternalOriginForTesting().opaque()); std::optional deserialized_site = SchemefulSite::Deserialize(site.Serialize()); EXPECT_TRUE(deserialized_site); EXPECT_EQ(site, deserialized_site); } } TEST(SchemefulSiteTest, SerializationFileSiteWithHost) { const struct { SchemefulSite site; std::string expected; } kTestCases[] = { {SchemefulSite(GURL("file:///etc/passwd")), "file://"}, {SchemefulSite(GURL("file://example.com/etc/passwd")), "file://example.com"}, {SchemefulSite(GURL("file://example.com")), "file://example.com"}, }; for (const auto& test_case : kTestCases) { SCOPED_TRACE(test_case.site.GetDebugString()); std::string serialized_site = test_case.site.SerializeFileSiteWithHost(); EXPECT_EQ(test_case.expected, serialized_site); std::optional deserialized_site = SchemefulSite::Deserialize(serialized_site); EXPECT_TRUE(deserialized_site); EXPECT_EQ(test_case.site, deserialized_site); } } TEST(SchemefulSiteTest, FileURLWithHostEquality) { // Two file URLs with different hosts should result in unequal SchemefulSites. SchemefulSite site1(GURL("file://foo/some/path.txt")); SchemefulSite site2(GURL("file://bar/some/path.txt")); EXPECT_NE(site1, site2); // Two file URLs with the same host should result in equal SchemefulSites. SchemefulSite site3(GURL("file://foo/another/path.pdf")); EXPECT_EQ(site1, site3); } TEST(SchemefulSiteTest, OpaqueSerialization) { // List of origins which should all share a schemeful site. SchemefulSite kTestSites[] = { SchemefulSite(), SchemefulSite(url::Origin()), SchemefulSite(GURL("data:text/html,Hello World"))}; for (auto& site : kTestSites) { std::optional deserialized_site = SchemefulSite::DeserializeWithNonce(*site.SerializeWithNonce()); EXPECT_TRUE(deserialized_site); EXPECT_EQ(site, *deserialized_site); } } TEST(SchemefulSiteTest, FromWire) { SchemefulSite out; // Opaque origin. EXPECT_TRUE(SchemefulSite::FromWire(url::Origin(), &out)); EXPECT_TRUE(out.opaque()); // Valid origin. EXPECT_TRUE(SchemefulSite::FromWire( url::Origin::Create(GURL("https://example.test")), &out)); EXPECT_EQ(SchemefulSite(url::Origin::Create(GURL("https://example.test"))), out); // Invalid origin (not a registrable domain). EXPECT_FALSE(SchemefulSite::FromWire( url::Origin::Create(GURL("https://sub.example.test")), &out)); // Invalid origin (non-default port). EXPECT_FALSE(SchemefulSite::FromWire( url::Origin::Create(GURL("https://example.test:1337")), &out)); } TEST(SchemefulSiteTest, CreateIfHasRegisterableDomain) { for (const auto& site : std::initializer_list{ "http://a.bar.test", "http://c.test", "http://a.foo.test", "https://a.bar.test", "https://c.test", "https://a.foo.test", }) { url::Origin origin = url::Origin::Create(GURL(site)); EXPECT_THAT(SchemefulSite::CreateIfHasRegisterableDomain(origin), testing::Optional(SchemefulSite(origin))) << "site = \"" << site << "\""; } for (const auto& site : std::initializer_list{ "data:text/html,Hello World", "file:///", "file://foo", "http://127.0.0.1:1234", "https://127.0.0.1:1234", }) { url::Origin origin = url::Origin::Create(GURL(site)); EXPECT_EQ(SchemefulSite::CreateIfHasRegisterableDomain(origin), std::nullopt) << "site = \"" << site << "\""; } } TEST(SchemefulSiteTest, ConvertWebSocketToHttp) { SchemefulSite ws_site(url::Origin::Create(GURL("ws://site.example.test"))); SchemefulSite http_site( url::Origin::Create(GURL("http://site.example.test"))); SchemefulSite wss_site(url::Origin::Create(GURL("wss://site.example.test"))); SchemefulSite https_site( url::Origin::Create(GURL("https://site.example.test"))); ASSERT_NE(ws_site, wss_site); ASSERT_NE(ws_site, http_site); ASSERT_NE(ws_site, https_site); ASSERT_NE(wss_site, http_site); ASSERT_NE(wss_site, https_site); ws_site.ConvertWebSocketToHttp(); wss_site.ConvertWebSocketToHttp(); EXPECT_EQ(ws_site, http_site); EXPECT_EQ(wss_site, https_site); // Does not change non-WebSocket sites. SchemefulSite http_site_copy(http_site); http_site_copy.ConvertWebSocketToHttp(); EXPECT_EQ(http_site, http_site_copy); EXPECT_EQ(url::kHttpScheme, http_site_copy.GetInternalOriginForTesting().scheme()); SchemefulSite file_site(url::Origin::Create(GURL("file:///"))); file_site.ConvertWebSocketToHttp(); EXPECT_EQ(url::kFileScheme, file_site.GetInternalOriginForTesting().scheme()); } TEST(SchemefulSiteTest, GetGURL) { struct { url::Origin origin; GURL wantGURL; } kTestCases[] = { { url::Origin::Create(GURL("data:text/html,Hello World")), GURL(), }, {url::Origin::Create(GURL("file://foo")), GURL("file:///")}, {url::Origin::Create(GURL("http://a.bar.test")), GURL("http://bar.test")}, {url::Origin::Create(GURL("http://c.test")), GURL("http://c.test")}, {url::Origin::Create(GURL("http://c.test:8000")), GURL("http://c.test")}, { url::Origin::Create(GURL("https://a.bar.test")), GURL("https://bar.test"), }, { url::Origin::Create(GURL("https://c.test")), GURL("https://c.test"), }, { url::Origin::Create(GURL("https://c.test:1337")), GURL("https://c.test"), }, }; for (const auto& testcase : kTestCases) { SchemefulSite site(testcase.origin); EXPECT_EQ(site.GetURL(), testcase.wantGURL); } } TEST(SchemefulSiteTest, InternalValue) { url::Origin origin = url::Origin::Create(GURL("https://example.com")); SchemefulSite site(origin); EXPECT_EQ(site.internal_value(), origin); url::Origin opaque_origin; SchemefulSite opaque_site(opaque_origin); EXPECT_EQ(opaque_site.internal_value(), opaque_origin); } } // namespace net