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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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", ®ular_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