123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- // Copyright 2020 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.
- #ifndef URL_ORIGIN_ABSTRACT_TESTS_H_
- #define URL_ORIGIN_ABSTRACT_TESTS_H_
- #include <string>
- #include <type_traits>
- #include "base/containers/contains.h"
- #include "base/strings/string_piece.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "url/gurl.h"
- #include "url/origin.h"
- #include "url/scheme_host_port.h"
- #include "url/url_util.h"
- namespace url {
- void ExpectParsedUrlsEqual(const GURL& a, const GURL& b);
- // AbstractOriginTest below abstracts away differences between url::Origin and
- // blink::SecurityOrigin by parametrizing the tests with a class that has to
- // expose the same public members as UrlOriginTestTraits below.
- class UrlOriginTestTraits {
- public:
- using OriginType = Origin;
- // Constructing an origin.
- static OriginType CreateOriginFromString(base::StringPiece s);
- static OriginType CreateUniqueOpaqueOrigin();
- static OriginType CreateWithReferenceOrigin(
- base::StringPiece url,
- const OriginType& reference_origin);
- static OriginType DeriveNewOpaqueOrigin(const OriginType& reference_origin);
- // Accessors for origin properties.
- static bool IsOpaque(const OriginType& origin);
- static std::string GetScheme(const OriginType& origin);
- static std::string GetHost(const OriginType& origin);
- static uint16_t GetPort(const OriginType& origin);
- static SchemeHostPort GetTupleOrPrecursorTupleIfOpaque(
- const OriginType& origin);
- // Wrappers for other instance methods of OriginType.
- static bool IsSameOrigin(const OriginType& a, const OriginType& b);
- static std::string Serialize(const OriginType& origin);
- // "Accessors" of URL properties.
- //
- // TODO(lukasza): Consider merging together OriginTraitsBase here and
- // UrlTraitsBase in //url/gurl_abstract_tests.h.
- static bool IsValidUrl(base::StringPiece str);
- // Only static members = no constructors are needed.
- UrlOriginTestTraits() = delete;
- };
- // Test suite for tests that cover both url::Origin and blink::SecurityOrigin.
- template <typename TOriginTraits>
- class AbstractOriginTest : public testing::Test {
- public:
- void SetUp() override {
- const char* kSchemesToRegister[] = {
- "noaccess",
- "std-with-host",
- "noaccess-std-with-host",
- "local",
- "local-noaccess",
- "local-std-with-host",
- "local-noaccess-std-with-host",
- "also-local",
- "sec",
- "sec-std-with-host",
- "sec-noaccess",
- };
- for (const char* kScheme : kSchemesToRegister) {
- std::string scheme(kScheme);
- if (base::Contains(scheme, "noaccess"))
- AddNoAccessScheme(kScheme);
- if (base::Contains(scheme, "std-with-host"))
- AddStandardScheme(kScheme, SchemeType::SCHEME_WITH_HOST);
- if (base::Contains(scheme, "local"))
- AddLocalScheme(kScheme);
- if (base::Contains(scheme, "sec"))
- AddSecureScheme(kScheme);
- }
- }
- protected:
- // Wrappers that help ellide away TOriginTraits.
- //
- // Note that calling the wrappers needs to be prefixed with `this->...` to
- // avoid hitting: explicit qualification required to use member 'IsOpaque'
- // from dependent base class.
- using OriginType = typename TOriginTraits::OriginType;
- OriginType CreateOriginFromString(base::StringPiece s) {
- return TOriginTraits::CreateOriginFromString(s);
- }
- OriginType CreateUniqueOpaqueOrigin() {
- return TOriginTraits::CreateUniqueOpaqueOrigin();
- }
- OriginType CreateWithReferenceOrigin(base::StringPiece url,
- const OriginType& reference_origin) {
- return TOriginTraits::CreateWithReferenceOrigin(url, reference_origin);
- }
- OriginType DeriveNewOpaqueOrigin(const OriginType& reference_origin) {
- return TOriginTraits::DeriveNewOpaqueOrigin(reference_origin);
- }
- bool IsOpaque(const OriginType& origin) {
- return TOriginTraits::IsOpaque(origin);
- }
- std::string GetScheme(const OriginType& origin) {
- return TOriginTraits::GetScheme(origin);
- }
- std::string GetHost(const OriginType& origin) {
- return TOriginTraits::GetHost(origin);
- }
- uint16_t GetPort(const OriginType& origin) {
- return TOriginTraits::GetPort(origin);
- }
- SchemeHostPort GetTupleOrPrecursorTupleIfOpaque(const OriginType& origin) {
- return TOriginTraits::GetTupleOrPrecursorTupleIfOpaque(origin);
- }
- bool IsSameOrigin(const OriginType& a, const OriginType& b) {
- bool is_a_same_with_b = TOriginTraits::IsSameOrigin(a, b);
- bool is_b_same_with_a = TOriginTraits::IsSameOrigin(b, a);
- EXPECT_EQ(is_a_same_with_b, is_b_same_with_a);
- return is_a_same_with_b;
- }
- std::string Serialize(const OriginType& origin) {
- return TOriginTraits::Serialize(origin);
- }
- bool IsValidUrl(base::StringPiece str) {
- return TOriginTraits::IsValidUrl(str);
- }
- #define EXPECT_SAME_ORIGIN(a, b) \
- EXPECT_TRUE(this->IsSameOrigin((a), (b))) \
- << "When checking if \"" << this->Serialize(a) << "\" is " \
- << "same-origin with \"" << this->Serialize(b) << "\""
- #define EXPECT_CROSS_ORIGIN(a, b) \
- EXPECT_FALSE(this->IsSameOrigin((a), (b))) \
- << "When checking if \"" << this->Serialize(a) << "\" is " \
- << "cross-origin from \"" << this->Serialize(b) << "\""
- void VerifyOriginInvariants(const OriginType& origin) {
- // An origin is always same-origin with itself.
- EXPECT_SAME_ORIGIN(origin, origin);
- // A copy of |origin| should be same-origin as well.
- auto origin_copy = origin;
- EXPECT_EQ(this->GetScheme(origin), this->GetScheme(origin_copy));
- EXPECT_EQ(this->GetHost(origin), this->GetHost(origin_copy));
- EXPECT_EQ(this->GetPort(origin), this->GetPort(origin_copy));
- EXPECT_EQ(this->IsOpaque(origin), this->IsOpaque(origin_copy));
- EXPECT_SAME_ORIGIN(origin, origin_copy);
- // An origin is always cross-origin from another, unique, opaque origin.
- EXPECT_CROSS_ORIGIN(origin, this->CreateUniqueOpaqueOrigin());
- // An origin is always cross-origin from another tuple origin.
- auto different_tuple_origin =
- this->CreateOriginFromString("https://not-in-the-list.test/");
- EXPECT_CROSS_ORIGIN(origin, different_tuple_origin);
- // Deriving an origin for "about:blank".
- auto about_blank_origin1 =
- this->CreateWithReferenceOrigin("about:blank", origin);
- auto about_blank_origin2 =
- this->CreateWithReferenceOrigin("about:blank?bar#foo", origin);
- EXPECT_SAME_ORIGIN(origin, about_blank_origin1);
- EXPECT_SAME_ORIGIN(origin, about_blank_origin2);
- // Derived opaque origins.
- std::vector<OriginType> derived_origins = {
- this->DeriveNewOpaqueOrigin(origin),
- this->CreateWithReferenceOrigin("data:text/html,baz", origin),
- this->DeriveNewOpaqueOrigin(about_blank_origin1),
- };
- for (size_t i = 0; i < derived_origins.size(); i++) {
- SCOPED_TRACE(testing::Message() << "Derived origin #" << i);
- const OriginType& derived_origin = derived_origins[i];
- EXPECT_TRUE(this->IsOpaque(derived_origin));
- EXPECT_SAME_ORIGIN(derived_origin, derived_origin);
- EXPECT_CROSS_ORIGIN(origin, derived_origin);
- EXPECT_EQ(this->GetTupleOrPrecursorTupleIfOpaque(origin),
- this->GetTupleOrPrecursorTupleIfOpaque(derived_origin));
- }
- }
- void VerifyUniqueOpaqueOriginInvariants(const OriginType& origin) {
- if (!this->IsOpaque(origin)) {
- ADD_FAILURE() << "Got unexpectedly non-opaque origin: "
- << this->Serialize(origin);
- return; // Skip other test assertions.
- }
- // Opaque origins should have an "empty" scheme, host and port.
- EXPECT_EQ("", this->GetScheme(origin));
- EXPECT_EQ("", this->GetHost(origin));
- EXPECT_EQ(0, this->GetPort(origin));
- // Unique opaque origins should have an empty precursor tuple.
- EXPECT_EQ(SchemeHostPort(), this->GetTupleOrPrecursorTupleIfOpaque(origin));
- // Serialization test.
- EXPECT_EQ("null", this->Serialize(origin));
- // Invariants that should hold for any origin.
- VerifyOriginInvariants(origin);
- }
- void TestUniqueOpaqueOrigin(base::StringPiece test_input) {
- auto origin = this->CreateOriginFromString(test_input);
- this->VerifyUniqueOpaqueOriginInvariants(origin);
- // Re-creating from the URL should be cross-origin.
- auto origin_recreated_from_same_input =
- this->CreateOriginFromString(test_input);
- EXPECT_CROSS_ORIGIN(origin, origin_recreated_from_same_input);
- }
- void VerifyTupleOriginInvariants(const OriginType& origin,
- const SchemeHostPort& expected_tuple) {
- if (this->IsOpaque(origin)) {
- ADD_FAILURE() << "Got unexpectedly opaque origin";
- return; // Skip other test assertions.
- }
- SCOPED_TRACE(testing::Message()
- << "Actual origin: " << this->Serialize(origin));
- // Compare `origin` against the `expected_tuple`.
- EXPECT_EQ(expected_tuple.scheme(), this->GetScheme(origin));
- EXPECT_EQ(expected_tuple.host(), this->GetHost(origin));
- EXPECT_EQ(expected_tuple.port(), this->GetPort(origin));
- EXPECT_EQ(expected_tuple, this->GetTupleOrPrecursorTupleIfOpaque(origin));
- // Serialization test.
- //
- // TODO(lukasza): Consider preserving the hostname when serializing file:
- // URLs. Dropping the hostname seems incompatible with section 6 of
- // rfc6454. Even though section 4 says that "the implementation MAY
- // return an implementation-defined value", it seems that Chromium
- // implementation *does* include the hostname in the origin SchemeHostPort
- // tuple.
- if (expected_tuple.scheme() != kFileScheme || expected_tuple.host() == "") {
- EXPECT_SAME_ORIGIN(origin,
- this->CreateOriginFromString(this->Serialize(origin)));
- }
- // Invariants that should hold for any origin.
- VerifyOriginInvariants(origin);
- }
- private:
- ScopedSchemeRegistryForTests scoped_scheme_registry_;
- };
- TYPED_TEST_SUITE_P(AbstractOriginTest);
- TYPED_TEST_P(AbstractOriginTest, NonStandardSchemeWithAndroidWebViewHack) {
- EnableNonStandardSchemesForAndroidWebView();
- // Regression test for https://crbug.com/896059.
- auto origin = this->CreateOriginFromString("unknown-scheme://");
- EXPECT_FALSE(this->IsOpaque(origin));
- EXPECT_EQ("unknown-scheme", this->GetScheme(origin));
- EXPECT_EQ("", this->GetHost(origin));
- EXPECT_EQ(0, this->GetPort(origin));
- // about:blank translates into an opaque origin, even in presence of
- // EnableNonStandardSchemesForAndroidWebView.
- origin = this->CreateOriginFromString("about:blank");
- EXPECT_TRUE(this->IsOpaque(origin));
- }
- TYPED_TEST_P(AbstractOriginTest, OpaqueOriginsFromValidUrls) {
- const char* kTestCases[] = {
- // Built-in noaccess schemes.
- "data:text/html,Hello!",
- "javascript:alert(1)",
- "about:blank",
- // Opaque blob URLs.
- "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)
- "blob:blob:http://www.example.com/guid-goes-here",
- "blob:filesystem:ws:b/.",
- "blob:filesystem:ftp://a/b",
- "blob:blob:file://localhost/foo/bar",
- };
- for (const char* test_input : kTestCases) {
- SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
- // Verify that `origin` is opaque not just because `test_input` results is
- // an invalid URL (because of a typo in the scheme name, or because of a
- // technicality like having no host in a noaccess-std-with-host: scheme).
- EXPECT_TRUE(this->IsValidUrl(test_input));
- this->TestUniqueOpaqueOrigin(test_input);
- }
- }
- TYPED_TEST_P(AbstractOriginTest, OpaqueOriginsFromInvalidUrls) {
- // TODO(lukasza): Consider moving those to GURL/KURL tests that verify what
- // inputs are parsed as an invalid URL.
- const char* kTestCases[] = {
- // Invalid file: URLs.
- "file://example.com:443/etc/passwd", // No port expected.
- // Invalid HTTP URLs.
- "http",
- "http:",
- "http:/",
- "http://",
- "http://:",
- "http://:1",
- "http::///invalid.example.com/",
- "http://example.com:65536/", // Port out of range.
- "http://example.com:-1/", // Port out of range.
- "http://example.com:18446744073709551616/", // Port = 2^64.
- "http://example.com:18446744073709551616999/", // Lots of port digits.
- // Invalid filesystem URLs.
- "filesystem:http://example.com/", // Missing /type/.
- "filesystem:local:baz./type/",
- "filesystem:local://hostname/type/",
- "filesystem:unknown-scheme://hostname/type/",
- "filesystem:filesystem:http://example.org:88/foo/bar",
- // Invalid IP addresses
- "http://[]/",
- "http://[2001:0db8:0000:0000:0000:0000:0000:0000:0001]/", // 9 groups.
- // Unknown scheme without a colon character (":") gives an invalid URL.
- "unknown-scheme",
- // Standard schemes require a hostname (and result in an opaque origin if
- // the hostname is missing).
- "local-std-with-host:",
- "noaccess-std-with-host:",
- };
- for (const char* test_input : kTestCases) {
- SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
- // All testcases here are expected to represent invalid URLs.
- // an invalid URL (because of a type in scheme name, or because of a
- // technicality like having no host in a noaccess-std-with-host: scheme).
- EXPECT_FALSE(this->IsValidUrl(test_input));
- // Invalid URLs should always result in an opaque origin.
- this->TestUniqueOpaqueOrigin(test_input);
- }
- }
- TYPED_TEST_P(AbstractOriginTest, TupleOrigins) {
- struct TestCase {
- const char* input;
- SchemeHostPort expected_tuple;
- } kTestCases[] = {
- // file: URLs
- {"file:///etc/passwd", {"file", "", 0}},
- {"file://example.com/etc/passwd", {"file", "example.com", 0}},
- {"file:///", {"file", "", 0}},
- {"file://hostname/C:/dir/file.txt", {"file", "hostname", 0}},
- // HTTP URLs
- {"http://example.com/", {"http", "example.com", 80}},
- {"http://example.com:80/", {"http", "example.com", 80}},
- {"http://example.com:123/", {"http", "example.com", 123}},
- {"http://example.com:0/", {"http", "example.com", 0}},
- {"http://example.com:65535/", {"http", "example.com", 65535}},
- {"https://example.com/", {"https", "example.com", 443}},
- {"https://example.com:443/", {"https", "example.com", 443}},
- {"https://example.com:123/", {"https", "example.com", 123}},
- {"https://example.com:0/", {"https", "example.com", 0}},
- {"https://example.com:65535/", {"https", "example.com", 65535}},
- {"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}},
- {"http://example/", {"http", "example", 80}},
- // Blob URLs.
- {"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}},
- // Filesystem URLs.
- {"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-std-with-host:baz./type/",
- {"local-std-with-host", "baz.", 0}},
- // IP Addresses
- {"http://192.168.9.1/", {"http", "192.168.9.1", 80}},
- {"http://[2001:db8::1]/", {"http", "[2001:db8::1]", 80}},
- {"http://[2001:0db8:0000:0000:0000:0000:0000:0001]/",
- {"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}},
- {"local-std-with-host:↑↑↓↓←→←→ba.↑↑↓↓←→←→ba.0.bg",
- {"local-std-with-host", "xn--ba-rzuadaibfa.xn--ba-rzuadaibfa.0.bg", 0}},
- // Registered URLs
- {"ftp://example.com/", {"ftp", "example.com", 21}},
- {"ws://example.com/", {"ws", "example.com", 80}},
- {"wss://example.com/", {"wss", "example.com", 443}},
- {"wss://user:pass@example.com/", {"wss", "example.com", 443}},
- };
- for (const TestCase& test : kTestCases) {
- SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
- // Only valid URLs should translate into valid, non-opaque origins.
- EXPECT_TRUE(this->IsValidUrl(test.input));
- auto origin = this->CreateOriginFromString(test.input);
- this->VerifyTupleOriginInvariants(origin, test.expected_tuple);
- }
- }
- TYPED_TEST_P(AbstractOriginTest, CustomSchemes_OpaqueOrigins) {
- const char* kTestCases[] = {
- // Unknown scheme
- "unknown-scheme:foo",
- "unknown-scheme://bar",
- // Unknown scheme that is a prefix or suffix of a registered scheme.
- "loca:foo",
- "ocal:foo",
- "local-suffix:foo",
- "prefix-local:foo",
- // Custom no-access schemes translate into an opaque origin (just like the
- // built-in no-access schemes such as about:blank or data:).
- "noaccess-std-with-host:foo",
- "noaccess-std-with-host://bar",
- "noaccess://host",
- "local-noaccess://host",
- "local-noaccess-std-with-host://host",
- };
- for (const char* test_input : kTestCases) {
- SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
- // Verify that `origin` is opaque not just because `test_input` results is
- // an invalid URL (because of a typo in the scheme name, or because of a
- // technicality like having no host in a noaccess-std-with-host: scheme).
- EXPECT_TRUE(this->IsValidUrl(test_input));
- this->TestUniqueOpaqueOrigin(test_input);
- }
- }
- TYPED_TEST_P(AbstractOriginTest, CustomSchemes_TupleOrigins) {
- struct TestCase {
- const char* input;
- SchemeHostPort expected_tuple;
- } kTestCases[] = {
- // 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-std-with-host:20", {"local-std-with-host", "0.0.0.20", 0}},
- {"local-std-with-host:20.", {"local-std-with-host", "0.0.0.20", 0}},
- {"local-std-with-host:foo", {"local-std-with-host", "foo", 0}},
- {"local-std-with-host://bar:20", {"local-std-with-host", "bar", 0}},
- {"local-std-with-host:baz.", {"local-std-with-host", "baz.", 0}},
- {"local-std-with-host:baz..", {"local-std-with-host", "baz..", 0}},
- {"local-std-with-host:baz..bar", {"local-std-with-host", "baz..bar", 0}},
- {"local-std-with-host:baz...", {"local-std-with-host", "baz...", 0}},
- // Scheme (registered in SetUp()) that's local but nonstandard. These
- // always have empty hostnames, but are allowed to be url::Origins.
- {"local:", {"local", "", 0}},
- {"local:foo", {"local", "", 0}},
- {"local://bar", {"local", "", 0}},
- {"also-local://bar", {"also-local", "", 0}},
- {"std-with-host://host", {"std-with-host", "host", 0}},
- {"local://host", {"local", "", 0}},
- {"local-std-with-host://host", {"local-std-with-host", "host", 0}},
- };
- for (const TestCase& test : kTestCases) {
- SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
- // Only valid URLs should translate into valid, non-opaque origins.
- EXPECT_TRUE(this->IsValidUrl(test.input));
- auto origin = this->CreateOriginFromString(test.input);
- this->VerifyTupleOriginInvariants(origin, test.expected_tuple);
- }
- }
- REGISTER_TYPED_TEST_SUITE_P(AbstractOriginTest,
- NonStandardSchemeWithAndroidWebViewHack,
- OpaqueOriginsFromValidUrls,
- OpaqueOriginsFromInvalidUrls,
- TupleOrigins,
- CustomSchemes_OpaqueOrigins,
- CustomSchemes_TupleOrigins);
- } // namespace url
- #endif // URL_ORIGIN_ABSTRACT_TESTS_H_
|