xref: /aosp_15_r20/external/cronet/url/origin_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <stddef.h>
6 #include <stdint.h>
7 
8 #include "base/memory/raw_ptr.h"
9 #include "base/test/scoped_feature_list.h"
10 #include "testing/gmock/include/gmock/gmock.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "url/gurl.h"
13 #include "url/origin.h"
14 #include "url/origin_abstract_tests.h"
15 #include "url/url_features.h"
16 #include "url/url_util.h"
17 
18 namespace url {
19 
20 class OriginTest : public ::testing::TestWithParam<bool> {
21  public:
OriginTest()22   OriginTest()
23       : use_standard_compliant_non_special_scheme_url_parsing_(GetParam()) {
24     if (use_standard_compliant_non_special_scheme_url_parsing_) {
25       scoped_feature_list_.InitAndEnableFeature(
26           kStandardCompliantNonSpecialSchemeURLParsing);
27     } else {
28       scoped_feature_list_.InitAndDisableFeature(
29           kStandardCompliantNonSpecialSchemeURLParsing);
30     }
31   }
32 
SetUp()33   void SetUp() override {
34     // Add two schemes which are local but nonstandard.
35     AddLocalScheme("local-but-nonstandard");
36     AddLocalScheme("also-local-but-nonstandard");
37 
38     // Add a scheme that's both local and standard.
39     AddStandardScheme("local-and-standard", SchemeType::SCHEME_WITH_HOST);
40     AddLocalScheme("local-and-standard");
41 
42     // Add a scheme that's standard but no-access. We still want these to
43     // form valid SchemeHostPorts, even though they always commit as opaque
44     // origins, so that they can represent the source of the resource even if
45     // it's not committable as a non-opaque origin.
46     AddStandardScheme("standard-but-noaccess", SchemeType::SCHEME_WITH_HOST);
47     AddNoAccessScheme("standard-but-noaccess");
48   }
49 
DoEqualityComparisons(const url::Origin & a,const url::Origin & b,bool should_compare_equal)50   ::testing::AssertionResult DoEqualityComparisons(const url::Origin& a,
51                                                    const url::Origin& b,
52                                                    bool should_compare_equal) {
53     ::testing::AssertionResult failure = ::testing::AssertionFailure();
54     failure << "DoEqualityComparisons failure. Expecting "
55             << (should_compare_equal ? "equality" : "inequality")
56             << " between:\n  a\n    Which is: " << a
57             << "\n  b\n    Which is: " << b << "\nThe following check failed: ";
58     if (a.IsSameOriginWith(b) != should_compare_equal)
59       return failure << "a.IsSameOriginWith(b)";
60     if (b.IsSameOriginWith(a) != should_compare_equal)
61       return failure << "b.IsSameOriginWith(a)";
62     if ((a == b) != should_compare_equal)
63       return failure << "(a == b)";
64     if ((b == a) != should_compare_equal)
65       return failure << "(b == a)";
66     if ((b != a) != !should_compare_equal)
67       return failure << "(b != a)";
68     if ((a != b) != !should_compare_equal)
69       return failure << "(a != b)";
70     return ::testing::AssertionSuccess();
71   }
72 
HasNonceTokenBeenInitialized(const url::Origin & origin)73   bool HasNonceTokenBeenInitialized(const url::Origin& origin) {
74     EXPECT_TRUE(origin.opaque());
75     // Avoid calling nonce_.token() here, to not trigger lazy initialization.
76     return !origin.nonce_->token_.is_empty();
77   }
78 
CreateNonce()79   Origin::Nonce CreateNonce() { return Origin::Nonce(); }
80 
CreateNonce(base::UnguessableToken nonce)81   Origin::Nonce CreateNonce(base::UnguessableToken nonce) {
82     return Origin::Nonce(nonce);
83   }
84 
GetNonce(const Origin & origin)85   const base::UnguessableToken* GetNonce(const Origin& origin) {
86     return origin.GetNonceForSerialization();
87   }
88 
89   // Wrappers around url::Origin methods to expose it to tests.
90 
UnsafelyCreateOpaqueOriginWithoutNormalization(std::string_view precursor_scheme,std::string_view precursor_host,uint16_t precursor_port,const Origin::Nonce & nonce)91   std::optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
92       std::string_view precursor_scheme,
93       std::string_view precursor_host,
94       uint16_t precursor_port,
95       const Origin::Nonce& nonce) {
96     return Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
97         precursor_scheme, precursor_host, precursor_port, nonce);
98   }
99 
SerializeWithNonce(const Origin & origin)100   std::optional<std::string> SerializeWithNonce(const Origin& origin) {
101     return origin.SerializeWithNonce();
102   }
103 
SerializeWithNonceAndInitIfNeeded(Origin & origin)104   std::optional<std::string> SerializeWithNonceAndInitIfNeeded(Origin& origin) {
105     return origin.SerializeWithNonceAndInitIfNeeded();
106   }
107 
Deserialize(const std::string & value)108   std::optional<Origin> Deserialize(const std::string& value) {
109     return Origin::Deserialize(value);
110   }
111 
112  protected:
113   struct SerializationTestCase {
114     std::string_view url;
115     std::string_view expected;
116     std::optional<std::string_view> expected_log;
117   };
118 
TestSerialization(const SerializationTestCase & test_case) const119   void TestSerialization(const SerializationTestCase& test_case) const {
120     SCOPED_TRACE(test_case.url);
121     GURL url(test_case.url);
122     EXPECT_TRUE(url.is_valid());
123     Origin origin = Origin::Create(url);
124     std::string serialized = origin.Serialize();
125 
126     ExpectParsedUrlsEqual(GURL(serialized), origin.GetURL());
127 
128     EXPECT_EQ(test_case.expected, serialized);
129 
130     // The '<<' operator sometimes produces additional information.
131     std::stringstream out;
132     out << origin;
133     if (test_case.expected_log) {
134       EXPECT_EQ(test_case.expected_log, out.str());
135     } else {
136       EXPECT_EQ(test_case.expected, out.str());
137     }
138   }
139 
140   bool use_standard_compliant_non_special_scheme_url_parsing_;
141 
142  private:
143   base::test::ScopedFeatureList scoped_feature_list_;
144   ScopedSchemeRegistryForTests scoped_registry_;
145 };
146 
147 INSTANTIATE_TEST_SUITE_P(All, OriginTest, ::testing::Bool());
148 
TEST_P(OriginTest,OpaqueOriginComparison)149 TEST_P(OriginTest, OpaqueOriginComparison) {
150   // A default-constructed Origin should should be cross origin to everything
151   // but itself.
152   url::Origin opaque_a, opaque_b;
153   EXPECT_TRUE(opaque_a.opaque());
154   EXPECT_EQ("", opaque_a.scheme());
155   EXPECT_EQ("", opaque_a.host());
156   EXPECT_EQ(0, opaque_a.port());
157   EXPECT_EQ(SchemeHostPort(), opaque_a.GetTupleOrPrecursorTupleIfOpaque());
158   EXPECT_FALSE(opaque_a.GetTupleOrPrecursorTupleIfOpaque().IsValid());
159 
160   EXPECT_TRUE(opaque_b.opaque());
161   EXPECT_EQ("", opaque_b.scheme());
162   EXPECT_EQ("", opaque_b.host());
163   EXPECT_EQ(0, opaque_b.port());
164   EXPECT_EQ(SchemeHostPort(), opaque_b.GetTupleOrPrecursorTupleIfOpaque());
165   EXPECT_FALSE(opaque_b.GetTupleOrPrecursorTupleIfOpaque().IsValid());
166 
167   // Two default-constructed Origins should always be cross origin to each
168   // other.
169   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
170   EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
171   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
172 
173   // The streaming operator should not trigger lazy initialization to the token.
174   std::ostringstream stream;
175   stream << opaque_a;
176   EXPECT_STREQ("null [internally: (nonce TBD) anonymous]",
177                stream.str().c_str());
178   EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
179 
180   // None of the operations thus far should have triggered lazy-generation of
181   // the UnguessableToken. Copying an origin, however, should trigger this.
182   EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
183   EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_b));
184   opaque_b = opaque_a;
185 
186   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a));
187   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
188   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, true));
189   EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
190   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
191 
192   // Move-initializing to a fresh Origin should restore the lazy initialization.
193   opaque_a = url::Origin();
194   EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
195   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
196   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
197   EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
198   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
199 
200   // Comparing two opaque Origins with matching SchemeHostPorts should trigger
201   // lazy initialization.
202   EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
203   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
204   bool should_swap = opaque_b < opaque_a;
205   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a));
206   EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
207 
208   if (should_swap)
209     std::swap(opaque_a, opaque_b);
210   EXPECT_LT(opaque_a, opaque_b);
211   EXPECT_FALSE(opaque_b < opaque_a);
212 
213   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
214   EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
215   EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
216 
217   EXPECT_LT(opaque_a, url::Origin::Create(GURL("http://www.google.com")));
218   EXPECT_LT(opaque_b, url::Origin::Create(GURL("http://www.google.com")));
219 
220   EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL(), opaque_b));
221   EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:blank"), opaque_b));
222   EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:srcdoc"), opaque_b));
223   EXPECT_EQ(opaque_b,
224             url::Origin::Resolve(GURL("about:blank?hello#whee"), opaque_b));
225 }
226 
TEST_P(OriginTest,ConstructFromTuple)227 TEST_P(OriginTest, ConstructFromTuple) {
228   struct TestCases {
229     const char* const scheme;
230     const char* const host;
231     const uint16_t port;
232   } cases[] = {
233       {"http", "example.com", 80},
234       {"http", "example.com", 123},
235       {"https", "example.com", 443},
236   };
237 
238   for (const auto& test_case : cases) {
239     testing::Message scope_message;
240     scope_message << test_case.scheme << "://" << test_case.host << ":"
241                   << test_case.port;
242     SCOPED_TRACE(scope_message);
243     Origin origin = Origin::CreateFromNormalizedTuple(
244         test_case.scheme, test_case.host, test_case.port);
245 
246     EXPECT_EQ(test_case.scheme, origin.scheme());
247     EXPECT_EQ(test_case.host, origin.host());
248     EXPECT_EQ(test_case.port, origin.port());
249   }
250 }
251 
TEST_P(OriginTest,Serialization)252 TEST_P(OriginTest, Serialization) {
253   // Common test cases
254   SerializationTestCase common_cases[] = {
255       {"http://192.168.9.1/", "http://192.168.9.1"},
256       {"http://[2001:db8::1]/", "http://[2001:db8::1]"},
257       {"http://☃.net/", "http://xn--n3h.net"},
258       {"http://example.com/", "http://example.com"},
259       {"http://example.com:123/", "http://example.com:123"},
260       {"https://example.com/", "https://example.com"},
261       {"https://example.com:123/", "https://example.com:123"},
262       {"file:///etc/passwd", "file://", "file:// [internally: file://]"},
263       {"file://example.com/etc/passwd", "file://",
264        "file:// [internally: file://example.com]"},
265       {"data:,", "null", "null [internally: (nonce TBD) anonymous]"},
266       {"git:", "null", "null [internally: (nonce TBD) anonymous]"},
267       {"git:/", "null", "null [internally: (nonce TBD) anonymous]"},
268       {"git://host/path", "null", "null [internally: (nonce TBD) anonymous]"},
269       {"local-and-standard://host/path", "local-and-standard://host"},
270       // A port is omitted if the scheme doesn't have the default port.
271       // See SchemeHostPort::SerializeInternal for details.
272       {"local-and-standard://host:123/path", "local-and-standard://host"},
273       {"standard-but-noaccess://host/path", "null",
274        "null [internally: (nonce TBD) anonymous]"},
275   };
276   for (const auto& test_case : common_cases) {
277     TestSerialization(test_case);
278   }
279 
280   // Flag-dependent test cases
281   if (use_standard_compliant_non_special_scheme_url_parsing_) {
282     SerializationTestCase cases[] = {
283         {"local-but-nonstandard://host/path", "local-but-nonstandard://host"},
284         {"local-but-nonstandard://host:123/path",
285          "local-but-nonstandard://host"},
286     };
287     for (const auto& test_case : cases) {
288       TestSerialization(test_case);
289     }
290   } else {
291     SerializationTestCase cases[] = {
292         {"local-but-nonstandard://host/path", "local-but-nonstandard://"},
293         {"local-but-nonstandard://host:123/path", "local-but-nonstandard://"},
294     };
295     for (const auto& test_case : cases) {
296       TestSerialization(test_case);
297     }
298   }
299 }
300 
TEST_P(OriginTest,SerializationWithAndroidWebViewHackEnabled)301 TEST_P(OriginTest, SerializationWithAndroidWebViewHackEnabled) {
302   EnableNonStandardSchemesForAndroidWebView();
303 
304   if (use_standard_compliant_non_special_scheme_url_parsing_) {
305     SerializationTestCase cases[] = {
306         {"nonstandard://host/path", "nonstandard://"},
307         {"nonstandard://host:123/path", "nonstandard://"},
308     };
309     for (const auto& test_case : cases) {
310       TestSerialization(test_case);
311     }
312   } else {
313   }
314 }
315 
TEST_P(OriginTest,Comparison)316 TEST_P(OriginTest, Comparison) {
317   // These URLs are arranged in increasing order:
318   const char* const urls[] = {
319       "data:uniqueness", "http://a:80",  "http://b:80",
320       "https://a:80",    "https://b:80", "http://a:81",
321       "http://b:81",     "https://a:81", "https://b:81",
322   };
323   // Validate the comparison logic still works when creating a canonical origin,
324   // when any created opaque origins contain a nonce.
325   {
326     // Pre-create the origins, as the internal nonce for unique origins changes
327     // with each freshly-constructed Origin (that's not copied).
328     std::vector<Origin> origins;
329     for (const auto* test_url : urls)
330       origins.push_back(Origin::Create(GURL(test_url)));
331     for (size_t i = 0; i < origins.size(); i++) {
332       const Origin& current = origins[i];
333       for (size_t j = i; j < origins.size(); j++) {
334         const Origin& to_compare = origins[j];
335         EXPECT_EQ(i < j, current < to_compare) << i << " < " << j;
336         EXPECT_EQ(j < i, to_compare < current) << j << " < " << i;
337       }
338     }
339   }
340 }
341 
TEST_P(OriginTest,UnsafelyCreate)342 TEST_P(OriginTest, UnsafelyCreate) {
343   struct TestCase {
344     const char* scheme;
345     const char* host;
346     uint16_t port;
347   } cases[] = {
348       {"http", "example.com", 80},
349       {"http", "example.com", 123},
350       {"https", "example.com", 443},
351       {"https", "example.com", 123},
352       {"http", "example.com", 0},  // 0 is a valid port for http.
353       {"file", "", 0},             // 0 indicates "no port" for file: scheme.
354       {"file", "example.com", 0},
355   };
356 
357   for (const auto& test : cases) {
358     SCOPED_TRACE(testing::Message()
359                  << test.scheme << "://" << test.host << ":" << test.port);
360     std::optional<url::Origin> origin =
361         url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
362             test.scheme, test.host, test.port);
363     ASSERT_TRUE(origin);
364     EXPECT_EQ(test.scheme, origin->scheme());
365     EXPECT_EQ(test.host, origin->host());
366     EXPECT_EQ(test.port, origin->port());
367     EXPECT_FALSE(origin->opaque());
368     EXPECT_TRUE(origin->IsSameOriginWith(*origin));
369 
370     ExpectParsedUrlsEqual(GURL(origin->Serialize()), origin->GetURL());
371 
372     base::UnguessableToken nonce = base::UnguessableToken::Create();
373     std::optional<url::Origin> opaque_origin =
374         UnsafelyCreateOpaqueOriginWithoutNormalization(
375             test.scheme, test.host, test.port, CreateNonce(nonce));
376     ASSERT_TRUE(opaque_origin);
377     EXPECT_TRUE(opaque_origin->opaque());
378     EXPECT_FALSE(*opaque_origin == origin);
379     EXPECT_EQ(opaque_origin->GetTupleOrPrecursorTupleIfOpaque(),
380               origin->GetTupleOrPrecursorTupleIfOpaque());
381     EXPECT_EQ(opaque_origin,
382               UnsafelyCreateOpaqueOriginWithoutNormalization(
383                   test.scheme, test.host, test.port, CreateNonce(nonce)));
384     EXPECT_FALSE(*opaque_origin == origin->DeriveNewOpaqueOrigin());
385   }
386 }
387 
TEST_P(OriginTest,UnsafelyCreateUniqueOnInvalidInput)388 TEST_P(OriginTest, UnsafelyCreateUniqueOnInvalidInput) {
389   url::AddStandardScheme("host-only", url::SCHEME_WITH_HOST);
390   url::AddStandardScheme("host-port-only", url::SCHEME_WITH_HOST_AND_PORT);
391   struct TestCases {
392     const char* scheme;
393     const char* host;
394     uint16_t port = 80;
395   } cases[] = {{"", "", 33},
396                {"data", "", 0},
397                {"blob", "", 0},
398                {"filesystem", "", 0},
399                {"data", "example.com"},
400                {"http", "☃.net"},
401                {"http\nmore", "example.com"},
402                {"http\rmore", "example.com"},
403                {"http\n", "example.com"},
404                {"http\r", "example.com"},
405                {"http", "example.com\nnot-example.com"},
406                {"http", "example.com\rnot-example.com"},
407                {"http", "example.com\n"},
408                {"http", "example.com\r"},
409                {"unknown-scheme", "example.com"},
410                {"host-only", "\r", 0},
411                {"host-only", "example.com", 22},
412                {"file", "", 123}};  // file: shouldn't have a port.
413 
414   for (const auto& test : cases) {
415     SCOPED_TRACE(testing::Message()
416                  << test.scheme << "://" << test.host << ":" << test.port);
417     EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization(
418         test.scheme, test.host, test.port, CreateNonce()));
419     EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
420         test.scheme, test.host, test.port));
421   }
422 
423   // An empty scheme/host/port tuple is not a valid tuple origin.
424   EXPECT_FALSE(
425       url::Origin::UnsafelyCreateTupleOriginWithoutNormalization("", "", 0));
426 
427   // Opaque origins with unknown precursors are allowed.
428   base::UnguessableToken token = base::UnguessableToken::Create();
429   std::optional<url::Origin> anonymous_opaque =
430       UnsafelyCreateOpaqueOriginWithoutNormalization("", "", 0,
431                                                      CreateNonce(token));
432   ASSERT_TRUE(anonymous_opaque)
433       << "An invalid tuple is a valid input to "
434       << "UnsafelyCreateOpaqueOriginWithoutNormalization, so long as it is "
435       << "the canonical form of the invalid tuple.";
436   EXPECT_TRUE(anonymous_opaque->opaque());
437   EXPECT_EQ(*GetNonce(anonymous_opaque.value()), token);
438   EXPECT_EQ(anonymous_opaque->GetTupleOrPrecursorTupleIfOpaque(),
439             url::SchemeHostPort());
440 }
441 
TEST_P(OriginTest,UnsafelyCreateUniqueViaEmbeddedNulls)442 TEST_P(OriginTest, UnsafelyCreateUniqueViaEmbeddedNulls) {
443   struct TestCases {
444     std::string_view scheme;
445     std::string_view host;
446     uint16_t port = 80;
447   } cases[] = {{{"http\0more", 9}, {"example.com", 11}},
448                {{"http\0", 5}, {"example.com", 11}},
449                {{"\0http", 5}, {"example.com", 11}},
450                {{"http"}, {"example.com\0not-example.com", 27}},
451                {{"http"}, {"example.com\0", 12}},
452                {{"http"}, {"\0example.com", 12}},
453                {{""}, {"\0", 1}, 0},
454                {{"\0", 1}, {""}, 0}};
455 
456   for (const auto& test : cases) {
457     SCOPED_TRACE(testing::Message()
458                  << test.scheme << "://" << test.host << ":" << test.port);
459     EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
460         test.scheme, test.host, test.port));
461     EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization(
462         test.scheme, test.host, test.port, CreateNonce()));
463   }
464 }
465 
TEST_P(OriginTest,DomainIs)466 TEST_P(OriginTest, DomainIs) {
467   const struct {
468     const char* url;
469     const char* lower_ascii_domain;
470     bool expected_domain_is;
471   } kTestCases[] = {
472       {"http://google.com/foo", "google.com", true},
473       {"http://www.google.com:99/foo", "google.com", true},
474       {"http://www.google.com.cn/foo", "google.com", false},
475       {"http://www.google.comm", "google.com", false},
476       {"http://www.iamnotgoogle.com/foo", "google.com", false},
477       {"http://www.google.com/foo", "Google.com", false},
478 
479       // If the host ends with a dot, it matches domains with or without a dot.
480       {"http://www.google.com./foo", "google.com", true},
481       {"http://www.google.com./foo", "google.com.", true},
482       {"http://www.google.com./foo", ".com", true},
483       {"http://www.google.com./foo", ".com.", true},
484 
485       // But, if the host doesn't end with a dot and the input domain does, then
486       // it's considered to not match.
487       {"http://google.com/foo", "google.com.", false},
488 
489       // If the host ends with two dots, it doesn't match.
490       {"http://www.google.com../foo", "google.com", false},
491 
492       // Filesystem scheme.
493       {"filesystem:http://www.google.com:99/foo/", "google.com", true},
494       {"filesystem:http://www.iamnotgoogle.com/foo/", "google.com", false},
495 
496       // File scheme.
497       {"file:///home/user/text.txt", "", false},
498       {"file:///home/user/text.txt", "txt", false},
499   };
500 
501   for (const auto& test_case : kTestCases) {
502     SCOPED_TRACE(testing::Message()
503                  << "(url, domain): (" << test_case.url << ", "
504                  << test_case.lower_ascii_domain << ")");
505     GURL url(test_case.url);
506     ASSERT_TRUE(url.is_valid());
507     Origin origin = Origin::Create(url);
508 
509     EXPECT_EQ(test_case.expected_domain_is,
510               origin.DomainIs(test_case.lower_ascii_domain));
511     EXPECT_FALSE(
512         origin.DeriveNewOpaqueOrigin().DomainIs(test_case.lower_ascii_domain));
513   }
514 
515   // If the URL is invalid, DomainIs returns false.
516   GURL invalid_url("google.com");
517   ASSERT_FALSE(invalid_url.is_valid());
518   EXPECT_FALSE(Origin::Create(invalid_url).DomainIs("google.com"));
519 
520   // Unique origins.
521   EXPECT_FALSE(Origin().DomainIs(""));
522   EXPECT_FALSE(Origin().DomainIs("com"));
523 }
524 
TEST_P(OriginTest,DebugAlias)525 TEST_P(OriginTest, DebugAlias) {
526   Origin origin1 = Origin::Create(GURL("https://foo.com/bar"));
527   DEBUG_ALIAS_FOR_ORIGIN(origin1_debug_alias, origin1);
528   EXPECT_STREQ("https://foo.com", origin1_debug_alias);
529 }
530 
TEST_P(OriginTest,CanBeDerivedFrom)531 TEST_P(OriginTest, CanBeDerivedFrom) {
532   AddStandardScheme("new-standard", SchemeType::SCHEME_WITH_HOST);
533   Origin opaque_unique_origin = Origin();
534 
535   Origin regular_origin = Origin::Create(GURL("https://a.com/"));
536   Origin opaque_precursor_origin = regular_origin.DeriveNewOpaqueOrigin();
537 
538   Origin file_origin = Origin::Create(GURL("file:///foo/bar"));
539   Origin file_opaque_precursor_origin = file_origin.DeriveNewOpaqueOrigin();
540   Origin file_host_origin = Origin::Create(GURL("file://a.com/foo/bar"));
541   Origin file_host_opaque_precursor_origin =
542       file_host_origin.DeriveNewOpaqueOrigin();
543 
544   Origin non_standard_scheme_origin =
545       Origin::Create(GURL("non-standard-scheme:foo"));
546   Origin non_standard_opaque_precursor_origin =
547       non_standard_scheme_origin.DeriveNewOpaqueOrigin();
548 
549   // Also, add new standard scheme that is local to the test.
550   Origin new_standard_origin = Origin::Create(GURL("new-standard://host/"));
551   Origin new_standard_opaque_precursor_origin =
552       new_standard_origin.DeriveNewOpaqueOrigin();
553 
554   // No access schemes always get unique opaque origins.
555   Origin no_access_origin =
556       Origin::Create(GURL("standard-but-noaccess://b.com"));
557   Origin no_access_opaque_precursor_origin =
558       no_access_origin.DeriveNewOpaqueOrigin();
559 
560   Origin local_non_standard_origin =
561       Origin::Create(GURL("local-but-nonstandard://a.com"));
562   Origin local_non_standard_opaque_precursor_origin =
563       local_non_standard_origin.DeriveNewOpaqueOrigin();
564 
565   // Call origin.CanBeDerivedFrom(url) for each of the following test cases
566   // and ensure that it returns |expected_value|
567   struct TestCase {
568     const char* url;
569     raw_ptr<Origin> origin;
570     bool expected_value;
571   };
572 
573   const TestCase common_test_cases[] = {
574       {"https://a.com", &regular_origin, true},
575       // Web URL can commit in an opaque origin with precursor information.
576       // Example: iframe sandbox navigated to a.com.
577       {"https://a.com", &opaque_precursor_origin, true},
578       // URL that comes from the web can never commit in an opaque unique
579       // origin. It must have precursor information.
580       {"https://a.com", &opaque_unique_origin, false},
581 
582       // Cross-origin URLs should never work.
583       {"https://b.com", &regular_origin, false},
584       {"https://b.com", &opaque_precursor_origin, false},
585 
586       // data: URL can never commit in a regular, non-opaque origin.
587       {"data:text/html,foo", &regular_origin, false},
588       // This is the default case: data: URLs commit in opaque origin carrying
589       // precursor information for the origin that created them.
590       {"data:text/html,foo", &opaque_precursor_origin, true},
591       // Browser-initiated navigations can result in data: URL committing in
592       // opaque unique origin.
593       {"data:text/html,foo", &opaque_unique_origin, true},
594 
595       // about:blank can commit in regular origin (default case for iframes).
596       {"about:blank", &regular_origin, true},
597       // This can happen if data: URL that originated at a.com creates an
598       // about:blank iframe.
599       {"about:blank", &opaque_precursor_origin, true},
600       // Browser-initiated navigations can result in about:blank URL committing
601       // in opaque unique origin.
602       {"about:blank", &opaque_unique_origin, true},
603 
604       // Default behavior of srcdoc is to inherit the origin of the parent
605       // document.
606       {"about:srcdoc", &regular_origin, true},
607       // This happens for sandboxed srcdoc iframe.
608       {"about:srcdoc", &opaque_precursor_origin, true},
609       // This can happen with browser-initiated navigation to about:blank or
610       // data: URL, which in turn add srcdoc iframe.
611       {"about:srcdoc", &opaque_unique_origin, true},
612 
613       // Just like srcdoc, blob: URLs can be created in all the cases.
614       {"blob:https://a.com/foo", &regular_origin, true},
615       {"blob:https://a.com/foo", &opaque_precursor_origin, true},
616       {"blob:https://a.com/foo", &opaque_unique_origin, true},
617 
618       {"filesystem:https://a.com/foo", &regular_origin, true},
619       {"filesystem:https://a.com/foo", &opaque_precursor_origin, true},
620       // Unlike blob: URLs, filesystem: ones cannot be created in an unique
621       // opaque origin.
622       {"filesystem:https://a.com/foo", &opaque_unique_origin, false},
623 
624       // file: URLs cannot result in regular web origins, regardless of
625       // opaqueness.
626       {"file:///etc/passwd", &regular_origin, false},
627       {"file:///etc/passwd", &opaque_precursor_origin, false},
628       // However, they can result in regular file: origin and an opaque one
629       // containing another file: origin as precursor.
630       {"file:///etc/passwd", &file_origin, true},
631       {"file:///etc/passwd", &file_opaque_precursor_origin, true},
632       // It should not be possible to get an opaque unique origin for file:
633       // as it is a standard scheme and will always result in a tuple origin
634       // or will always be derived by other origin.
635       // Note: file:// URLs should become unique opaque origins at some point.
636       {"file:///etc/passwd", &opaque_unique_origin, false},
637 
638       // The same set as above, but including a host.
639       {"file://a.com/etc/passwd", &regular_origin, false},
640       {"file://a.com/etc/passwd", &opaque_precursor_origin, false},
641       {"file://a.com/etc/passwd", &file_host_origin, true},
642       {"file://a.com/etc/passwd", &file_host_opaque_precursor_origin, true},
643       {"file://a.com/etc/passwd", &opaque_unique_origin, false},
644 
645       // Locally registered standard scheme should behave the same way
646       // as built-in standard schemes.
647       {"new-standard://host/foo", &new_standard_origin, true},
648       {"new-standard://host/foo", &new_standard_opaque_precursor_origin, true},
649       {"new-standard://host/foo", &opaque_unique_origin, false},
650       {"new-standard://host2/foo", &new_standard_origin, false},
651       {"new-standard://host2/foo", &new_standard_opaque_precursor_origin,
652        false},
653 
654       // A non-standard scheme should never commit in an standard origin or
655       // opaque origin with standard precursor information.
656       {"non-standard-scheme://a.com/foo", &regular_origin, false},
657       {"non-standard-scheme://a.com/foo", &opaque_precursor_origin, false},
658       // However, it should be fine to commit in unique opaque origins or in its
659       // own origin.
660       // Note: since non-standard scheme URLs don't parse out anything
661       // but the scheme, using a random different hostname here would work.
662       {"non-standard-scheme://b.com/foo2", &opaque_unique_origin, true},
663       {"non-standard-scheme://b.com/foo3", &non_standard_scheme_origin, true},
664       {"non-standard-scheme://b.com/foo4",
665        &non_standard_opaque_precursor_origin, true},
666 
667       // No access scheme can only commit in opaque origin.
668       {"standard-but-noaccess://a.com/foo", &regular_origin, false},
669       {"standard-but-noaccess://a.com/foo", &opaque_precursor_origin, false},
670       {"standard-but-noaccess://a.com/foo", &opaque_unique_origin, true},
671       {"standard-but-noaccess://a.com/foo", &no_access_origin, true},
672       {"standard-but-noaccess://a.com/foo", &no_access_opaque_precursor_origin,
673        true},
674       {"standard-but-noaccess://b.com/foo", &no_access_origin, true},
675       {"standard-but-noaccess://b.com/foo", &no_access_opaque_precursor_origin,
676        true},
677 
678       // Local schemes can be non-standard, verify they also work as expected.
679       {"local-but-nonstandard://a.com", &regular_origin, false},
680       {"local-but-nonstandard://a.com", &opaque_precursor_origin, false},
681       {"local-but-nonstandard://a.com", &opaque_unique_origin, true},
682       {"local-but-nonstandard://a.com", &local_non_standard_origin, true},
683       {"local-but-nonstandard://a.com",
684        &local_non_standard_opaque_precursor_origin, true},
685   };
686 
687   for (const auto& test_case : common_test_cases) {
688     SCOPED_TRACE(testing::Message() << "(origin, url): (" << *test_case.origin
689                                     << ", " << test_case.url << ")");
690     EXPECT_EQ(test_case.expected_value,
691               test_case.origin->CanBeDerivedFrom(GURL(test_case.url)));
692   }
693 
694   // Flag-dependent tests
695   const TestCase flag_dependent_test_cases[] = {
696       {"local-but-nonstandard://b.com", &local_non_standard_origin,
697        !use_standard_compliant_non_special_scheme_url_parsing_},
698       {"local-but-nonstandard://b.com",
699        &local_non_standard_opaque_precursor_origin,
700        !use_standard_compliant_non_special_scheme_url_parsing_},
701   };
702   for (const auto& test_case : flag_dependent_test_cases) {
703     SCOPED_TRACE(testing::Message() << "(origin, url): (" << *test_case.origin
704                                     << ", " << test_case.url << ")");
705     EXPECT_EQ(test_case.expected_value,
706               test_case.origin->CanBeDerivedFrom(GURL(test_case.url)));
707   }
708 }
709 
TEST_P(OriginTest,GetDebugString)710 TEST_P(OriginTest, GetDebugString) {
711   Origin http_origin = Origin::Create(GURL("http://192.168.9.1"));
712   EXPECT_STREQ(http_origin.GetDebugString().c_str(), "http://192.168.9.1");
713 
714   Origin http_opaque_origin = http_origin.DeriveNewOpaqueOrigin();
715   EXPECT_THAT(
716       http_opaque_origin.GetDebugString().c_str(),
717       ::testing::MatchesRegex(
718           "null \\[internally: \\(\\w*\\) derived from http://192.168.9.1\\]"));
719   EXPECT_THAT(
720       http_opaque_origin.GetDebugString(false /* include_nonce */).c_str(),
721       ::testing::MatchesRegex(
722           "null \\[internally: derived from http://192.168.9.1\\]"));
723 
724   Origin data_origin = Origin::Create(GURL("data:"));
725   EXPECT_STREQ(data_origin.GetDebugString().c_str(),
726                "null [internally: (nonce TBD) anonymous]");
727 
728   // The nonce of the origin will be initialized if a new opaque origin is
729   // derived.
730   Origin data_derived_origin = data_origin.DeriveNewOpaqueOrigin();
731   EXPECT_THAT(
732       data_derived_origin.GetDebugString().c_str(),
733       ::testing::MatchesRegex("null \\[internally: \\(\\w*\\) anonymous\\]"));
734   EXPECT_THAT(
735       data_derived_origin.GetDebugString(false /* include_nonce */).c_str(),
736       ::testing::MatchesRegex("null \\[internally: anonymous\\]"));
737 
738   Origin file_origin = Origin::Create(GURL("file:///etc/passwd"));
739   EXPECT_STREQ(file_origin.GetDebugString().c_str(),
740                "file:// [internally: file://]");
741 
742   Origin file_server_origin =
743       Origin::Create(GURL("file://example.com/etc/passwd"));
744   EXPECT_STREQ(file_server_origin.GetDebugString().c_str(),
745                "file:// [internally: file://example.com]");
746 }
747 
TEST_P(OriginTest,Deserialize)748 TEST_P(OriginTest, Deserialize) {
749   std::vector<GURL> valid_urls = {
750       GURL("https://a.com"),         GURL("http://a"),
751       GURL("http://a:80"),           GURL("file://a.com/etc/passwd"),
752       GURL("file:///etc/passwd"),    GURL("http://192.168.1.1"),
753       GURL("http://[2001:db8::1]/"),
754   };
755   for (const GURL& url : valid_urls) {
756     SCOPED_TRACE(url.spec());
757     Origin origin = Origin::Create(url);
758     std::optional<std::string> serialized = SerializeWithNonce(origin);
759     ASSERT_TRUE(serialized);
760 
761     std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
762     ASSERT_TRUE(deserialized.has_value());
763 
764     EXPECT_TRUE(DoEqualityComparisons(origin, deserialized.value(), true));
765     EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString());
766   }
767 }
768 
TEST_P(OriginTest,DeserializeInvalid)769 TEST_P(OriginTest, DeserializeInvalid) {
770   EXPECT_EQ(std::nullopt, Deserialize(std::string()));
771   EXPECT_EQ(std::nullopt, Deserialize("deadbeef"));
772   EXPECT_EQ(std::nullopt, Deserialize("0123456789"));
773   EXPECT_EQ(std::nullopt, Deserialize("https://a.com"));
774   EXPECT_EQ(std::nullopt, Deserialize("https://192.168.1.1"));
775 }
776 
TEST_P(OriginTest,SerializeTBDNonce)777 TEST_P(OriginTest, SerializeTBDNonce) {
778   std::vector<GURL> invalid_urls = {
779       GURL("data:uniqueness"),       GURL("data:,"),
780       GURL("data:text/html,Hello!"), GURL("javascript:alert(1)"),
781       GURL("about:blank"),           GURL("google.com"),
782   };
783   for (const GURL& url : invalid_urls) {
784     SCOPED_TRACE(url.spec());
785     Origin origin = Origin::Create(url);
786     std::optional<std::string> serialized = SerializeWithNonce(origin);
787     std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
788     ASSERT_TRUE(deserialized.has_value());
789 
790     // Can't use DoEqualityComparisons here since empty nonces are never ==
791     // unless they are the same object.
792     EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString());
793   }
794 
795   {
796     // Same basic test as above, but without a GURL to create tuple_.
797     Origin opaque;
798     std::optional<std::string> serialized = SerializeWithNonce(opaque);
799     ASSERT_TRUE(serialized);
800 
801     std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
802     ASSERT_TRUE(deserialized.has_value());
803 
804     // Can't use DoEqualityComparisons here since empty nonces are never ==
805     // unless they are the same object.
806     EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString());
807   }
808 
809   // Now force initialization of the nonce prior to serialization.
810   for (const GURL& url : invalid_urls) {
811     SCOPED_TRACE(url.spec());
812     Origin origin = Origin::Create(url);
813     std::optional<std::string> serialized =
814         SerializeWithNonceAndInitIfNeeded(origin);
815     std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
816     ASSERT_TRUE(deserialized.has_value());
817 
818     // The nonce should have been initialized prior to Serialization().
819     EXPECT_EQ(origin, deserialized.value());
820   }
821 }
822 
TEST_P(OriginTest,DeserializeValidNonce)823 TEST_P(OriginTest, DeserializeValidNonce) {
824   Origin opaque;
825   GetNonce(opaque);
826 
827   std::optional<std::string> serialized = SerializeWithNonce(opaque);
828   ASSERT_TRUE(serialized);
829 
830   std::optional<Origin> deserialized = Deserialize(std::move(*serialized));
831   ASSERT_TRUE(deserialized.has_value());
832 
833   EXPECT_TRUE(DoEqualityComparisons(opaque, deserialized.value(), true));
834   EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString());
835 }
836 
TEST_P(OriginTest,IsSameOriginWith)837 TEST_P(OriginTest, IsSameOriginWith) {
838   url::Origin opaque_origin;
839   GURL foo_url = GURL("https://foo.com/path");
840   url::Origin foo_origin = url::Origin::Create(foo_url);
841   GURL bar_url = GURL("https://bar.com/path");
842   url::Origin bar_origin = url::Origin::Create(bar_url);
843 
844   EXPECT_FALSE(opaque_origin.IsSameOriginWith(foo_origin));
845   EXPECT_FALSE(opaque_origin.IsSameOriginWith(foo_url));
846 
847   EXPECT_TRUE(foo_origin.IsSameOriginWith(foo_origin));
848   EXPECT_TRUE(foo_origin.IsSameOriginWith(foo_url));
849 
850   EXPECT_FALSE(foo_origin.IsSameOriginWith(bar_origin));
851   EXPECT_FALSE(foo_origin.IsSameOriginWith(bar_url));
852 
853   // Documenting legacy behavior.  This doesn't necessarily mean that the legacy
854   // behavior is correct (or desirable in the long-term).
855   EXPECT_FALSE(foo_origin.IsSameOriginWith(GURL("about:blank")));
856   EXPECT_FALSE(foo_origin.IsSameOriginWith(GURL()));  // Invalid GURL.
857   EXPECT_TRUE(foo_origin.IsSameOriginWith(GURL("blob:https://foo.com/guid")));
858 }
859 
TEST_P(OriginTest,IsSameOriginLocalNonStandardScheme)860 TEST_P(OriginTest, IsSameOriginLocalNonStandardScheme) {
861   GURL a_url = GURL("local-but-nonstandard://a.com/");
862   GURL b_url = GURL("local-but-nonstandard://b.com/");
863   url::Origin a_origin = url::Origin::Create(a_url);
864   url::Origin b_origin = url::Origin::Create(b_url);
865 
866   EXPECT_TRUE(a_origin.IsSameOriginWith(a_origin));
867   EXPECT_TRUE(a_origin.IsSameOriginWith(a_url));
868 
869   if (use_standard_compliant_non_special_scheme_url_parsing_) {
870     // If the flag is enabled, host and port are also checked.
871     EXPECT_FALSE(a_origin.IsSameOriginWith(b_origin));
872     EXPECT_FALSE(a_origin.IsSameOriginWith(b_url));
873   } else {
874     EXPECT_TRUE(a_origin.IsSameOriginWith(b_origin));
875     EXPECT_TRUE(a_origin.IsSameOriginWith(b_url));
876   }
877 }
878 
TEST_P(OriginTest,OriginWithAndroidWebViewHackEnabled)879 TEST_P(OriginTest, OriginWithAndroidWebViewHackEnabled) {
880   EnableNonStandardSchemesForAndroidWebView();
881 
882   GURL a_url = GURL("nonstandard://a.com/");
883   GURL b_url = GURL("nonstandard://b.com/");
884   url::Origin a_origin = url::Origin::Create(a_url);
885   url::Origin b_origin = url::Origin::Create(b_url);
886 
887   EXPECT_TRUE(a_origin.IsSameOriginWith(a_origin));
888   EXPECT_TRUE(a_origin.IsSameOriginWith(a_url));
889 
890   // When AndroidWebViewHack is enabled, only a scheme part is checked. Thus,
891   // "nonstandard://a.com/" and "nonstandard://b.com/" are considered as the
892   // same origin. This is not ideal, given that a host and a port are available
893   // when kStandardCompliantNonSpecialSchemeURLParsing flag is enabled, but we
894   // can't check a host nor a port to avoid breaking existing WebView code.
895   // See https://crbug.com/40063064 for details.
896   EXPECT_TRUE(a_origin.IsSameOriginWith(b_origin));
897   EXPECT_TRUE(a_origin.IsSameOriginWith(b_url));
898   EXPECT_TRUE(a_origin.CanBeDerivedFrom(b_url));
899 
900   GURL another_scheme_url = GURL("another-nonstandard://a.com/");
901   url::Origin another_scheme_origin = url::Origin::Create(another_scheme_url);
902   EXPECT_FALSE(a_origin.IsSameOriginWith(another_scheme_origin));
903   EXPECT_FALSE(a_origin.IsSameOriginWith(another_scheme_url));
904   EXPECT_FALSE(a_origin.CanBeDerivedFrom(another_scheme_url));
905 }
906 
907 INSTANTIATE_TYPED_TEST_SUITE_P(UrlOrigin,
908                                AbstractOriginTest,
909                                UrlOriginTestTraits);
910 
911 }  // namespace url
912