// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/logging.h" #include "base/macros.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" #include "url/url_util.h" namespace url { void ExpectParsedUrlsEqual(const GURL& a, const GURL& b) { EXPECT_EQ(a, b); const Parsed& a_parsed = a.parsed_for_possibly_invalid_spec(); const Parsed& b_parsed = b.parsed_for_possibly_invalid_spec(); EXPECT_EQ(a_parsed.scheme.begin, b_parsed.scheme.begin); EXPECT_EQ(a_parsed.scheme.len, b_parsed.scheme.len); EXPECT_EQ(a_parsed.username.begin, b_parsed.username.begin); EXPECT_EQ(a_parsed.username.len, b_parsed.username.len); EXPECT_EQ(a_parsed.password.begin, b_parsed.password.begin); EXPECT_EQ(a_parsed.password.len, b_parsed.password.len); EXPECT_EQ(a_parsed.host.begin, b_parsed.host.begin); EXPECT_EQ(a_parsed.host.len, b_parsed.host.len); EXPECT_EQ(a_parsed.port.begin, b_parsed.port.begin); EXPECT_EQ(a_parsed.port.len, b_parsed.port.len); EXPECT_EQ(a_parsed.path.begin, b_parsed.path.begin); EXPECT_EQ(a_parsed.path.len, b_parsed.path.len); EXPECT_EQ(a_parsed.query.begin, b_parsed.query.begin); EXPECT_EQ(a_parsed.query.len, b_parsed.query.len); EXPECT_EQ(a_parsed.ref.begin, b_parsed.ref.begin); EXPECT_EQ(a_parsed.ref.len, b_parsed.ref.len); } class OriginTest : public ::testing::Test { public: void SetUp() override { // Add two schemes which are local but nonstandard. AddLocalScheme("local-but-nonstandard"); AddLocalScheme("also-local-but-nonstandard"); // Add a scheme that's both local and standard. AddStandardScheme("local-and-standard", SchemeType::SCHEME_WITH_HOST); AddLocalScheme("local-and-standard"); // Add a scheme that's standard but no-access. We still want these to // form valid SchemeHostPorts, even though they always commit as opaque // origins, so that they can represent the source of the resource even if // it's not committable as a non-opaque origin. AddStandardScheme("standard-but-noaccess", SchemeType::SCHEME_WITH_HOST); AddNoAccessScheme("standard-but-noaccess"); } void TearDown() override { url::Shutdown(); } ::testing::AssertionResult DoEqualityComparisons(const url::Origin& a, const url::Origin& b, bool should_compare_equal) { ::testing::AssertionResult failure = ::testing::AssertionFailure(); failure << "DoEqualityComparisons failure. Expecting " << (should_compare_equal ? "equality" : "inequality") << " between:\n a\n Which is: " << a << "\n b\n Which is: " << b << "\nThe following check failed: "; if (a.IsSameOriginWith(b) != should_compare_equal) return failure << "a.IsSameOriginWith(b)"; if (b.IsSameOriginWith(a) != should_compare_equal) return failure << "b.IsSameOriginWith(a)"; if ((a == b) != should_compare_equal) return failure << "(a == b)"; if ((b == a) != should_compare_equal) return failure << "(b == a)"; if ((b != a) != !should_compare_equal) return failure << "(b != a)"; if ((a != b) != !should_compare_equal) return failure << "(a != b)"; return ::testing::AssertionSuccess(); } bool HasNonceTokenBeenInitialized(const url::Origin& origin) { EXPECT_TRUE(origin.opaque()); // Avoid calling nonce_.token() here, to not trigger lazy initialization. return !origin.nonce_->token_.is_empty(); } Origin::Nonce CreateNonce() { return Origin::Nonce(); } Origin::Nonce CreateNonce(base::UnguessableToken nonce) { return Origin::Nonce(nonce); } base::Optional GetNonce(const Origin& origin) { return origin.GetNonceForSerialization(); } // Wrapper around url::Origin method to expose it to tests. base::Optional UnsafelyCreateOpaqueOriginWithoutNormalization( base::StringPiece precursor_scheme, base::StringPiece precursor_host, uint16_t precursor_port, const Origin::Nonce& nonce) { return Origin::UnsafelyCreateOpaqueOriginWithoutNormalization( precursor_scheme, precursor_host, precursor_port, nonce); } }; TEST_F(OriginTest, OpaqueOriginComparison) { // A default-constructed Origin should should be cross origin to everything // but itself. url::Origin opaque_a, opaque_b; EXPECT_TRUE(opaque_a.opaque()); EXPECT_EQ("", opaque_a.scheme()); EXPECT_EQ("", opaque_a.host()); EXPECT_EQ(0, opaque_a.port()); EXPECT_EQ(SchemeHostPort(), opaque_a.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_TRUE(opaque_a.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); EXPECT_TRUE(opaque_b.opaque()); EXPECT_EQ("", opaque_b.scheme()); EXPECT_EQ("", opaque_b.host()); EXPECT_EQ(0, opaque_b.port()); EXPECT_EQ(SchemeHostPort(), opaque_b.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_TRUE(opaque_b.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); // Two default-constructed Origins should always be cross origin to each // other. EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false)); EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true)); // The streaming operator should not trigger lazy initialization to the token. std::ostringstream stream; stream << opaque_a; EXPECT_STREQ("null [internally: (nonce TBD) anonymous]", stream.str().c_str()); EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a)); // None of the operations thus far should have triggered lazy-generation of // the UnguessableToken. Copying an origin, however, should trigger this. EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a)); EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_b)); opaque_b = opaque_a; EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a)); EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, true)); EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true)); // Move-initializing to a fresh Origin should restore the lazy initialization. opaque_a = url::Origin(); EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a)); EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false)); EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true)); // Comparing two opaque Origins with matching SchemeHostPorts should trigger // lazy initialization. EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a)); EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b)); bool should_swap = opaque_b < opaque_a; EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a)); EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b)); if (should_swap) std::swap(opaque_a, opaque_b); EXPECT_LT(opaque_a, opaque_b); EXPECT_FALSE(opaque_b < opaque_a); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false)); EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true)); EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true)); EXPECT_LT(opaque_a, url::Origin::Create(GURL("http://www.google.com"))); EXPECT_LT(opaque_b, url::Origin::Create(GURL("http://www.google.com"))); EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:blank"), opaque_b)); EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:srcdoc"), opaque_b)); EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:blank?hello#whee"), opaque_b)); const char* const urls[] = { "data:text/html,Hello!", "javascript:alert(1)", "about:blank", "file://example.com:443/etc/passwd", "unknown-scheme:foo", "unknown-scheme://bar", "http", "http:", "http:/", "http://", "http://:", "http://:1", "yay", "http::///invalid.example.com/", "blob:null/foo", // blob:null (actually a valid URL) "blob:data:foo", // blob + data (which is nonstandard) "blob:about://blank/", // blob + about (which is nonstandard) "blob:about:blank/", // blob + about (which is nonstandard) "filesystem:http://example.com/", // Invalid (missing /type/) "filesystem:local-but-nonstandard:baz./type/", // fs requires standard "filesystem:local-but-nonstandard://hostname/type/", "filesystem:unknown-scheme://hostname/type/", "local-but-nonstandar:foo", // Prefix of registered scheme. "but-nonstandard:foo", // Suffix of registered scheme. "local-and-standard:", // Standard scheme needs a hostname. "standard-but-noaccess:", // Standard scheme needs a hostname. "blob:blob:http://www.example.com/guid-goes-here", // Double blob. }; for (auto* test_url : urls) { SCOPED_TRACE(test_url); GURL url(test_url); const url::Origin opaque_origin; // Opaque origins returned by Origin::Create(). { Origin origin = Origin::Create(url); EXPECT_EQ("", origin.scheme()); EXPECT_EQ("", origin.host()); EXPECT_EQ(0, origin.port()); EXPECT_TRUE(origin.opaque()); // An origin is always same-origin with itself. EXPECT_EQ(origin, origin); EXPECT_NE(origin, url::Origin()); EXPECT_EQ(SchemeHostPort(), origin.GetTupleOrPrecursorTupleIfOpaque()); // A copy of |origin| should be same-origin as well. Origin origin_copy = origin; EXPECT_EQ("", origin_copy.scheme()); EXPECT_EQ("", origin_copy.host()); EXPECT_EQ(0, origin_copy.port()); EXPECT_TRUE(origin_copy.opaque()); EXPECT_EQ(origin, origin_copy); // And it should always be cross-origin to another opaque Origin. EXPECT_NE(origin, opaque_origin); // Re-creating from the URL should also be cross-origin. EXPECT_NE(origin, Origin::Create(url)); ExpectParsedUrlsEqual(GURL(origin.Serialize()), origin.GetURL()); } } } TEST_F(OriginTest, ConstructFromTuple) { struct TestCases { const char* const scheme; const char* const host; const uint16_t port; } cases[] = { {"http", "example.com", 80}, {"http", "example.com", 123}, {"https", "example.com", 443}, }; for (const auto& test_case : cases) { testing::Message scope_message; scope_message << test_case.scheme << "://" << test_case.host << ":" << test_case.port; SCOPED_TRACE(scope_message); Origin origin = Origin::CreateFromNormalizedTuple( test_case.scheme, test_case.host, test_case.port); EXPECT_EQ(test_case.scheme, origin.scheme()); EXPECT_EQ(test_case.host, origin.host()); EXPECT_EQ(test_case.port, origin.port()); } } TEST_F(OriginTest, ConstructFromGURL) { Origin different_origin = Origin::Create(GURL("https://not-in-the-list.test/")); struct TestCases { const char* const url; const char* const expected_scheme; const char* const expected_host; const uint16_t expected_port; } cases[] = { // IP Addresses {"http://192.168.9.1/", "http", "192.168.9.1", 80}, {"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80}, {"http://1/", "http", "0.0.0.1", 80}, {"http://1:1/", "http", "0.0.0.1", 1}, {"http://3232237825/", "http", "192.168.9.1", 80}, // Punycode {"http://☃.net/", "http", "xn--n3h.net", 80}, {"blob:http://☃.net/", "http", "xn--n3h.net", 80}, // Generic URLs {"http://example.com/", "http", "example.com", 80}, {"http://example.com:123/", "http", "example.com", 123}, {"https://example.com/", "https", "example.com", 443}, {"https://example.com:123/", "https", "example.com", 123}, {"http://user:pass@example.com/", "http", "example.com", 80}, {"http://example.com:123/?query", "http", "example.com", 123}, {"https://example.com/#1234", "https", "example.com", 443}, {"https://u:p@example.com:123/?query#1234", "https", "example.com", 123}, // Registered URLs {"ftp://example.com/", "ftp", "example.com", 21}, {"gopher://example.com/", "gopher", "example.com", 70}, {"ws://example.com/", "ws", "example.com", 80}, {"wss://example.com/", "wss", "example.com", 443}, {"wss://user:pass@example.com/", "wss", "example.com", 443}, // Scheme (registered in SetUp()) that's both local and standard. // TODO: Is it really appropriate to do network-host canonicalization of // schemes without ports? {"local-and-standard:20", "local-and-standard", "0.0.0.20", 0}, {"local-and-standard:20.", "local-and-standard", "0.0.0.20", 0}, {"local-and-standard:↑↑↓↓←→←→ba.↑↑↓↓←→←→ba.0.bg", "local-and-standard", "xn--ba-rzuadaibfa.xn--ba-rzuadaibfa.0.bg", 0}, {"local-and-standard:foo", "local-and-standard", "foo", 0}, {"local-and-standard://bar:20", "local-and-standard", "bar", 0}, {"local-and-standard:baz.", "local-and-standard", "baz.", 0}, {"local-and-standard:baz..", "local-and-standard", "baz..", 0}, {"local-and-standard:baz..bar", "local-and-standard", "baz..bar", 0}, {"local-and-standard:baz...", "local-and-standard", "baz...", 0}, // Scheme (registered in SetUp()) that's local but nonstandard. These // always have empty hostnames, but are allowed to be url::Origins. {"local-but-nonstandard:", "local-but-nonstandard", "", 0}, {"local-but-nonstandard:foo", "local-but-nonstandard", "", 0}, {"local-but-nonstandard://bar", "local-but-nonstandard", "", 0}, {"also-local-but-nonstandard://bar", "also-local-but-nonstandard", "", 0}, // Scheme (registered in SetUp()) that's standard but marked as noaccess. // url::Origin doesn't currently take the noaccess property into account, // so these aren't expected to result in opaque origins. {"standard-but-noaccess:foo", "standard-but-noaccess", "foo", 0}, {"standard-but-noaccess://bar", "standard-but-noaccess", "bar", 0}, // file: URLs {"file:///etc/passwd", "file", "", 0}, {"file://example.com/etc/passwd", "file", "example.com", 0}, // Filesystem: {"filesystem:http://example.com/type/", "http", "example.com", 80}, {"filesystem:http://example.com:123/type/", "http", "example.com", 123}, {"filesystem:https://example.com/type/", "https", "example.com", 443}, {"filesystem:https://example.com:123/type/", "https", "example.com", 123}, {"filesystem:local-and-standard:baz./type/", "local-and-standard", "baz.", 0}, // Blob: {"blob:http://example.com/guid-goes-here", "http", "example.com", 80}, {"blob:http://example.com:123/guid-goes-here", "http", "example.com", 123}, {"blob:https://example.com/guid-goes-here", "https", "example.com", 443}, {"blob:http://u:p@example.com/guid-goes-here", "http", "example.com", 80}, // Gopher: {"gopher://8u.9.Vx6", "gopher", "8u.9.vx6", 70}, }; for (const auto& test_case : cases) { SCOPED_TRACE(test_case.url); GURL url(test_case.url); EXPECT_TRUE(url.is_valid()); Origin origin = Origin::Create(url); EXPECT_EQ(test_case.expected_scheme, origin.scheme()); EXPECT_EQ(test_case.expected_host, origin.host()); EXPECT_EQ(test_case.expected_port, origin.port()); EXPECT_FALSE(origin.opaque()); EXPECT_EQ(origin, origin); EXPECT_NE(different_origin, origin); EXPECT_NE(origin, different_origin); EXPECT_EQ(origin, Origin::Resolve(GURL("about:blank"), origin)); EXPECT_EQ(origin, Origin::Resolve(GURL("about:blank?bar#foo"), origin)); ExpectParsedUrlsEqual(GURL(origin.Serialize()), origin.GetURL()); url::Origin derived_opaque = Origin::Resolve(GURL("about:blank?bar#foo"), origin) .DeriveNewOpaqueOrigin(); EXPECT_TRUE(derived_opaque.opaque()); EXPECT_NE(origin, derived_opaque); EXPECT_FALSE(derived_opaque.GetTupleOrPrecursorTupleIfOpaque().IsInvalid()); EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(), derived_opaque.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_EQ(derived_opaque, derived_opaque); url::Origin derived_opaque_via_data_url = Origin::Resolve(GURL("data:text/html,baz"), origin); EXPECT_TRUE(derived_opaque_via_data_url.opaque()); EXPECT_NE(origin, derived_opaque_via_data_url); EXPECT_FALSE(derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque() .IsInvalid()); EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(), derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque()); EXPECT_NE(derived_opaque, derived_opaque_via_data_url); EXPECT_NE(derived_opaque_via_data_url, derived_opaque); EXPECT_NE(derived_opaque.DeriveNewOpaqueOrigin(), derived_opaque); EXPECT_EQ(derived_opaque_via_data_url, derived_opaque_via_data_url); } } TEST_F(OriginTest, Serialization) { struct TestCases { const char* const url; const char* const expected; const char* const expected_log; } cases[] = { {"http://192.168.9.1/", "http://192.168.9.1"}, {"http://[2001:db8::1]/", "http://[2001:db8::1]"}, {"http://☃.net/", "http://xn--n3h.net"}, {"http://example.com/", "http://example.com"}, {"http://example.com:123/", "http://example.com:123"}, {"https://example.com/", "https://example.com"}, {"https://example.com:123/", "https://example.com:123"}, {"file:///etc/passwd", "file://", "file:// [internally: file://]"}, {"file://example.com/etc/passwd", "file://", "file:// [internally: file://example.com]"}, {"data:,", "null", "null [internally: (nonce TBD) anonymous]"}, }; for (const auto& test_case : cases) { SCOPED_TRACE(test_case.url); GURL url(test_case.url); EXPECT_TRUE(url.is_valid()); Origin origin = Origin::Create(url); std::string serialized = origin.Serialize(); ExpectParsedUrlsEqual(GURL(serialized), origin.GetURL()); EXPECT_EQ(test_case.expected, serialized); // The '<<' operator sometimes produces additional information. std::stringstream out; out << origin; if (test_case.expected_log) EXPECT_EQ(test_case.expected_log, out.str()); else EXPECT_EQ(test_case.expected, out.str()); } } TEST_F(OriginTest, Comparison) { // These URLs are arranged in increasing order: const char* const urls[] = { "data:uniqueness", "http://a:80", "http://b:80", "https://a:80", "https://b:80", "http://a:81", "http://b:81", "https://a:81", "https://b:81", }; // Validate the comparison logic still works when creating a canonical origin, // when any created opaque origins contain a nonce. { // Pre-create the origins, as the internal nonce for unique origins changes // with each freshly-constructed Origin (that's not copied). std::vector origins; for (const auto* test_url : urls) origins.push_back(Origin::Create(GURL(test_url))); for (size_t i = 0; i < origins.size(); i++) { const Origin& current = origins[i]; for (size_t j = i; j < origins.size(); j++) { const Origin& to_compare = origins[j]; EXPECT_EQ(i < j, current < to_compare) << i << " < " << j; EXPECT_EQ(j < i, to_compare < current) << j << " < " << i; } } } } TEST_F(OriginTest, UnsafelyCreate) { struct TestCase { const char* scheme; const char* host; uint16_t port; } cases[] = { {"http", "example.com", 80}, {"http", "example.com", 123}, {"https", "example.com", 443}, {"https", "example.com", 123}, {"file", "", 0}, {"file", "example.com", 0}, }; for (const auto& test : cases) { SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":" << test.port); base::Optional origin = url::Origin::UnsafelyCreateTupleOriginWithoutNormalization( test.scheme, test.host, test.port); ASSERT_TRUE(origin); EXPECT_EQ(test.scheme, origin->scheme()); EXPECT_EQ(test.host, origin->host()); EXPECT_EQ(test.port, origin->port()); EXPECT_FALSE(origin->opaque()); EXPECT_TRUE(origin->IsSameOriginWith(*origin)); ExpectParsedUrlsEqual(GURL(origin->Serialize()), origin->GetURL()); base::UnguessableToken nonce = base::UnguessableToken::Create(); base::Optional opaque_origin = UnsafelyCreateOpaqueOriginWithoutNormalization( test.scheme, test.host, test.port, CreateNonce(nonce)); ASSERT_TRUE(opaque_origin); EXPECT_TRUE(opaque_origin->opaque()); EXPECT_FALSE(*opaque_origin == origin); EXPECT_EQ(opaque_origin->GetTupleOrPrecursorTupleIfOpaque(), origin->GetTupleOrPrecursorTupleIfOpaque()); EXPECT_EQ(opaque_origin, UnsafelyCreateOpaqueOriginWithoutNormalization( test.scheme, test.host, test.port, CreateNonce(nonce))); EXPECT_FALSE(*opaque_origin == origin->DeriveNewOpaqueOrigin()); } } TEST_F(OriginTest, UnsafelyCreateUniqueOnInvalidInput) { url::AddStandardScheme("host-only", url::SCHEME_WITH_HOST); url::AddStandardScheme("host-port-only", url::SCHEME_WITH_HOST_AND_PORT); struct TestCases { const char* scheme; const char* host; uint16_t port = 80; } cases[] = {{"", "", 33}, {"data", "", 0}, {"blob", "", 0}, {"filesystem", "", 0}, {"data", "example.com"}, {"http", "☃.net"}, {"http\nmore", "example.com"}, {"http\rmore", "example.com"}, {"http\n", "example.com"}, {"http\r", "example.com"}, {"http", "example.com\nnot-example.com"}, {"http", "example.com\rnot-example.com"}, {"http", "example.com\n"}, {"http", "example.com\r"}, {"http", "example.com", 0}, {"unknown-scheme", "example.com"}, {"host-only", "\r", 0}, {"host-only", "example.com", 22}, {"host-port-only", "example.com", 0}, {"file", ""}}; for (const auto& test : cases) { SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":" << test.port); EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization( test.scheme, test.host, test.port, CreateNonce())); EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization( test.scheme, test.host, test.port)); } // An empty scheme/host/port tuple is not a valid tuple origin. EXPECT_FALSE( url::Origin::UnsafelyCreateTupleOriginWithoutNormalization("", "", 0)); // Opaque origins with unknown precursors are allowed. base::UnguessableToken token = base::UnguessableToken::Create(); base::Optional anonymous_opaque = UnsafelyCreateOpaqueOriginWithoutNormalization("", "", 0, CreateNonce(token)); ASSERT_TRUE(anonymous_opaque) << "An invalid tuple is a valid input to " << "UnsafelyCreateOpaqueOriginWithoutNormalization, so long as it is " << "the canonical form of the invalid tuple."; EXPECT_TRUE(anonymous_opaque->opaque()); EXPECT_EQ(GetNonce(anonymous_opaque.value()), token); EXPECT_EQ(anonymous_opaque->GetTupleOrPrecursorTupleIfOpaque(), url::SchemeHostPort()); } TEST_F(OriginTest, UnsafelyCreateUniqueViaEmbeddedNulls) { struct TestCases { base::StringPiece scheme; base::StringPiece host; uint16_t port = 80; } cases[] = {{{"http\0more", 9}, {"example.com", 11}}, {{"http\0", 5}, {"example.com", 11}}, {{"\0http", 5}, {"example.com", 11}}, {{"http"}, {"example.com\0not-example.com", 27}}, {{"http"}, {"example.com\0", 12}}, {{"http"}, {"\0example.com", 12}}, {{""}, {"\0", 1}, 0}, {{"\0", 1}, {""}, 0}}; for (const auto& test : cases) { SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":" << test.port); EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization( test.scheme, test.host, test.port)); EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization( test.scheme, test.host, test.port, CreateNonce())); } } TEST_F(OriginTest, DomainIs) { const struct { const char* url; const char* lower_ascii_domain; bool expected_domain_is; } kTestCases[] = { {"http://google.com/foo", "google.com", true}, {"http://www.google.com:99/foo", "google.com", true}, {"http://www.google.com.cn/foo", "google.com", false}, {"http://www.google.comm", "google.com", false}, {"http://www.iamnotgoogle.com/foo", "google.com", false}, {"http://www.google.com/foo", "Google.com", false}, // If the host ends with a dot, it matches domains with or without a dot. {"http://www.google.com./foo", "google.com", true}, {"http://www.google.com./foo", "google.com.", true}, {"http://www.google.com./foo", ".com", true}, {"http://www.google.com./foo", ".com.", true}, // But, if the host doesn't end with a dot and the input domain does, then // it's considered to not match. {"http://google.com/foo", "google.com.", false}, // If the host ends with two dots, it doesn't match. {"http://www.google.com../foo", "google.com", false}, // Filesystem scheme. {"filesystem:http://www.google.com:99/foo/", "google.com", true}, {"filesystem:http://www.iamnotgoogle.com/foo/", "google.com", false}, // File scheme. {"file:///home/user/text.txt", "", false}, {"file:///home/user/text.txt", "txt", false}, }; for (const auto& test_case : kTestCases) { SCOPED_TRACE(testing::Message() << "(url, domain): (" << test_case.url << ", " << test_case.lower_ascii_domain << ")"); GURL url(test_case.url); ASSERT_TRUE(url.is_valid()); Origin origin = Origin::Create(url); EXPECT_EQ(test_case.expected_domain_is, origin.DomainIs(test_case.lower_ascii_domain)); EXPECT_FALSE( origin.DeriveNewOpaqueOrigin().DomainIs(test_case.lower_ascii_domain)); } // If the URL is invalid, DomainIs returns false. GURL invalid_url("google.com"); ASSERT_FALSE(invalid_url.is_valid()); EXPECT_FALSE(Origin::Create(invalid_url).DomainIs("google.com")); // Unique origins. EXPECT_FALSE(Origin().DomainIs("")); EXPECT_FALSE(Origin().DomainIs("com")); } TEST_F(OriginTest, DebugAlias) { Origin origin1 = Origin::Create(GURL("https://foo.com/bar")); DEBUG_ALIAS_FOR_ORIGIN(origin1_debug_alias, origin1); EXPECT_STREQ("https://foo.com", origin1_debug_alias); } TEST_F(OriginTest, NonStandardScheme) { Origin origin = Origin::Create(GURL("cow://")); EXPECT_TRUE(origin.opaque()); } TEST_F(OriginTest, NonStandardSchemeWithAndroidWebViewHack) { EnableNonStandardSchemesForAndroidWebView(); Origin origin = Origin::Create(GURL("cow://")); EXPECT_FALSE(origin.opaque()); EXPECT_EQ("cow", origin.scheme()); EXPECT_EQ("", origin.host()); EXPECT_EQ(0, origin.port()); Shutdown(); } TEST_F(OriginTest, CanBeDerivedFrom) { Origin opaque_unique_origin = Origin(); Origin regular_origin = Origin::Create(GURL("https://a.com/")); Origin opaque_precursor_origin = regular_origin.DeriveNewOpaqueOrigin(); Origin file_origin = Origin::Create(GURL("file:///foo/bar")); Origin file_opaque_precursor_origin = file_origin.DeriveNewOpaqueOrigin(); Origin file_host_origin = Origin::Create(GURL("file://a.com/foo/bar")); Origin file_host_opaque_precursor_origin = file_host_origin.DeriveNewOpaqueOrigin(); Origin non_standard_scheme_origin = Origin::Create(GURL("non-standard-scheme:foo")); Origin non_standard_opaque_precursor_origin = non_standard_scheme_origin.DeriveNewOpaqueOrigin(); // Also, add new standard scheme that is local to the test. AddStandardScheme("new-standard", SchemeType::SCHEME_WITH_HOST); Origin new_standard_origin = Origin::Create(GURL("new-standard://host/")); Origin new_standard_opaque_precursor_origin = new_standard_origin.DeriveNewOpaqueOrigin(); // No access schemes always get unique opaque origins. Origin no_access_origin = Origin::Create(GURL("standard-but-noaccess://b.com")); Origin no_access_opaque_precursor_origin = no_access_origin.DeriveNewOpaqueOrigin(); Origin local_non_standard_origin = Origin::Create(GURL("local-but-nonstandard://a.com")); Origin local_non_standard_opaque_precursor_origin = local_non_standard_origin.DeriveNewOpaqueOrigin(); // Call origin.CanBeDerivedFrom(url) for each of the following test cases // and ensure that it returns |expected_value| const struct { const char* url; Origin* origin; bool expected_value; } kTestCases[] = { {"https://a.com", ®ular_origin, true}, // Web URL can commit in an opaque origin with precursor information. // Example: iframe sandbox navigated to a.com. {"https://a.com", &opaque_precursor_origin, true}, // URL that comes from the web can never commit in an opaque unique // origin. It must have precursor information. {"https://a.com", &opaque_unique_origin, false}, // Cross-origin URLs should never work. {"https://b.com", ®ular_origin, false}, {"https://b.com", &opaque_precursor_origin, false}, // data: URL can never commit in a regular, non-opaque origin. {"data:text/html,foo", ®ular_origin, false}, // This is the default case: data: URLs commit in opaque origin carrying // precursor information for the origin that created them. {"data:text/html,foo", &opaque_precursor_origin, true}, // Browser-initiated navigations can result in data: URL committing in // opaque unique origin. {"data:text/html,foo", &opaque_unique_origin, true}, // about:blank can commit in regular origin (default case for iframes). {"about:blank", ®ular_origin, true}, // This can happen if data: URL that originated at a.com creates an // about:blank iframe. {"about:blank", &opaque_precursor_origin, true}, // Browser-initiated navigations can result in about:blank URL committing // in opaque unique origin. {"about:blank", &opaque_unique_origin, true}, // Default behavior of srcdoc is to inherit the origin of the parent // document. {"about:srcdoc", ®ular_origin, true}, // This happens for sandboxed srcdoc iframe. {"about:srcdoc", &opaque_precursor_origin, true}, // This can happen with browser-initiated navigation to about:blank or // data: URL, which in turn add srcdoc iframe. {"about:srcdoc", &opaque_unique_origin, true}, // Just like srcdoc, blob: URLs can be created in all the cases. {"blob:https://a.com/foo", ®ular_origin, true}, {"blob:https://a.com/foo", &opaque_precursor_origin, true}, {"blob:https://a.com/foo", &opaque_unique_origin, true}, {"filesystem:https://a.com/foo", ®ular_origin, true}, {"filesystem:https://a.com/foo", &opaque_precursor_origin, true}, // Unlike blob: URLs, filesystem: ones cannot be created in an unique // opaque origin. {"filesystem:https://a.com/foo", &opaque_unique_origin, false}, // file: URLs cannot result in regular web origins, regardless of // opaqueness. {"file:///etc/passwd", ®ular_origin, false}, {"file:///etc/passwd", &opaque_precursor_origin, false}, // However, they can result in regular file: origin and an opaque one // containing another file: origin as precursor. {"file:///etc/passwd", &file_origin, true}, {"file:///etc/passwd", &file_opaque_precursor_origin, true}, // It should not be possible to get an opaque unique origin for file: // as it is a standard scheme and will always result in a tuple origin // or will always be derived by other origin. // Note: file:// URLs should become unique opaque origins at some point. {"file:///etc/passwd", &opaque_unique_origin, false}, // The same set as above, but including a host. {"file://a.com/etc/passwd", ®ular_origin, false}, {"file://a.com/etc/passwd", &opaque_precursor_origin, false}, {"file://a.com/etc/passwd", &file_host_origin, true}, {"file://a.com/etc/passwd", &file_host_opaque_precursor_origin, true}, {"file://a.com/etc/passwd", &opaque_unique_origin, false}, // Locally registered standard scheme should behave the same way // as built-in standard schemes. {"new-standard://host/foo", &new_standard_origin, true}, {"new-standard://host/foo", &new_standard_opaque_precursor_origin, true}, {"new-standard://host/foo", &opaque_unique_origin, false}, {"new-standard://host2/foo", &new_standard_origin, false}, {"new-standard://host2/foo", &new_standard_opaque_precursor_origin, false}, // A non-standard scheme should never commit in an standard origin or // opaque origin with standard precursor information. {"non-standard-scheme://a.com/foo", ®ular_origin, false}, {"non-standard-scheme://a.com/foo", &opaque_precursor_origin, false}, // However, it should be fine to commit in unique opaque origins or in its // own origin. // Note: since non-standard scheme URLs don't parse out anything // but the scheme, using a random different hostname here would work. {"non-standard-scheme://b.com/foo2", &opaque_unique_origin, true}, {"non-standard-scheme://b.com/foo3", &non_standard_scheme_origin, true}, {"non-standard-scheme://b.com/foo4", &non_standard_opaque_precursor_origin, true}, // No access scheme can only commit in opaque origin. {"standard-but-noaccess://a.com/foo", ®ular_origin, false}, {"standard-but-noaccess://a.com/foo", &opaque_precursor_origin, false}, {"standard-but-noaccess://a.com/foo", &opaque_unique_origin, true}, {"standard-but-noaccess://a.com/foo", &no_access_origin, false}, {"standard-but-noaccess://a.com/foo", &no_access_opaque_precursor_origin, false}, {"standard-but-noaccess://b.com/foo", &no_access_origin, false}, {"standard-but-noaccess://b.com/foo", &no_access_opaque_precursor_origin, true}, // Local schemes can be non-standard, verify they also work as expected. {"local-but-nonstandard://a.com", ®ular_origin, false}, {"local-but-nonstandard://a.com", &opaque_precursor_origin, false}, {"local-but-nonstandard://a.com", &opaque_unique_origin, true}, {"local-but-nonstandard://a.com", &local_non_standard_origin, true}, {"local-but-nonstandard://a.com", &local_non_standard_opaque_precursor_origin, true}, }; for (const auto& test_case : kTestCases) { SCOPED_TRACE(testing::Message() << "(origin, url): (" << *test_case.origin << ", " << test_case.url << ")"); EXPECT_EQ(test_case.expected_value, test_case.origin->CanBeDerivedFrom(GURL(test_case.url))); } } TEST_F(OriginTest, GetDebugString) { Origin http_origin = Origin::Create(GURL("http://192.168.9.1")); EXPECT_STREQ(http_origin.GetDebugString().c_str(), "http://192.168.9.1"); Origin http_opaque_origin = http_origin.DeriveNewOpaqueOrigin(); EXPECT_THAT( http_opaque_origin.GetDebugString().c_str(), ::testing::MatchesRegex( "null \\[internally: \\(\\w*\\) derived from http://192.168.9.1\\]")); Origin data_origin = Origin::Create(GURL("data:")); EXPECT_STREQ(data_origin.GetDebugString().c_str(), "null [internally: (nonce TBD) anonymous]"); // The nonce of the origin will be initialized if a new opaque origin is // derived. Origin data_derived_origin = data_origin.DeriveNewOpaqueOrigin(); EXPECT_THAT( data_derived_origin.GetDebugString().c_str(), ::testing::MatchesRegex("null \\[internally: \\(\\w*\\) anonymous\\]")); Origin file_origin = Origin::Create(GURL("file:///etc/passwd")); EXPECT_STREQ(file_origin.GetDebugString().c_str(), "file:// [internally: file://]"); Origin file_server_origin = Origin::Create(GURL("file://example.com/etc/passwd")); EXPECT_STREQ(file_server_origin.GetDebugString().c_str(), "file:// [internally: file://example.com]"); } } // namespace url