1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 #include "base/macros.h"
9 #include "testing/gmock/include/gmock/gmock.h"
10 #include "testing/gtest/include/gtest/gtest.h"
12 #include "url/origin.h"
13 #include "url/url_util.h"
17 void ExpectParsedUrlsEqual(const GURL& a, const GURL& b) {
19 const Parsed& a_parsed = a.parsed_for_possibly_invalid_spec();
20 const Parsed& b_parsed = b.parsed_for_possibly_invalid_spec();
21 EXPECT_EQ(a_parsed.scheme.begin, b_parsed.scheme.begin);
22 EXPECT_EQ(a_parsed.scheme.len, b_parsed.scheme.len);
23 EXPECT_EQ(a_parsed.username.begin, b_parsed.username.begin);
24 EXPECT_EQ(a_parsed.username.len, b_parsed.username.len);
25 EXPECT_EQ(a_parsed.password.begin, b_parsed.password.begin);
26 EXPECT_EQ(a_parsed.password.len, b_parsed.password.len);
27 EXPECT_EQ(a_parsed.host.begin, b_parsed.host.begin);
28 EXPECT_EQ(a_parsed.host.len, b_parsed.host.len);
29 EXPECT_EQ(a_parsed.port.begin, b_parsed.port.begin);
30 EXPECT_EQ(a_parsed.port.len, b_parsed.port.len);
31 EXPECT_EQ(a_parsed.path.begin, b_parsed.path.begin);
32 EXPECT_EQ(a_parsed.path.len, b_parsed.path.len);
33 EXPECT_EQ(a_parsed.query.begin, b_parsed.query.begin);
34 EXPECT_EQ(a_parsed.query.len, b_parsed.query.len);
35 EXPECT_EQ(a_parsed.ref.begin, b_parsed.ref.begin);
36 EXPECT_EQ(a_parsed.ref.len, b_parsed.ref.len);
39 class OriginTest : public ::testing::Test {
41 void SetUp() override {
42 // Add two schemes which are local but nonstandard.
43 AddLocalScheme("local-but-nonstandard");
44 AddLocalScheme("also-local-but-nonstandard");
46 // Add a scheme that's both local and standard.
47 AddStandardScheme("local-and-standard", SchemeType::SCHEME_WITH_HOST);
48 AddLocalScheme("local-and-standard");
50 // Add a scheme that's standard but no-access. We still want these to
51 // form valid SchemeHostPorts, even though they always commit as opaque
52 // origins, so that they can represent the source of the resource even if
53 // it's not committable as a non-opaque origin.
54 AddStandardScheme("standard-but-noaccess", SchemeType::SCHEME_WITH_HOST);
55 AddNoAccessScheme("standard-but-noaccess");
58 ::testing::AssertionResult DoEqualityComparisons(const url::Origin& a,
60 bool should_compare_equal) {
61 ::testing::AssertionResult failure = ::testing::AssertionFailure();
62 failure << "DoEqualityComparisons failure. Expecting "
63 << (should_compare_equal ? "equality" : "inequality")
64 << " between:\n a\n Which is: " << a
65 << "\n b\n Which is: " << b << "\nThe following check failed: ";
66 if (a.IsSameOriginWith(b) != should_compare_equal)
67 return failure << "a.IsSameOriginWith(b)";
68 if (b.IsSameOriginWith(a) != should_compare_equal)
69 return failure << "b.IsSameOriginWith(a)";
70 if ((a == b) != should_compare_equal)
71 return failure << "(a == b)";
72 if ((b == a) != should_compare_equal)
73 return failure << "(b == a)";
74 if ((b != a) != !should_compare_equal)
75 return failure << "(b != a)";
76 if ((a != b) != !should_compare_equal)
77 return failure << "(a != b)";
78 return ::testing::AssertionSuccess();
81 bool HasNonceTokenBeenInitialized(const url::Origin& origin) {
82 EXPECT_TRUE(origin.opaque());
83 // Avoid calling nonce_.token() here, to not trigger lazy initialization.
84 return !origin.nonce_->token_.is_empty();
87 Origin::Nonce CreateNonce() { return Origin::Nonce(); }
89 Origin::Nonce CreateNonce(base::UnguessableToken nonce) {
90 return Origin::Nonce(nonce);
93 base::Optional<base::UnguessableToken> GetNonce(const Origin& origin) {
94 return origin.GetNonceForSerialization();
97 // Wrappers around url::Origin methods to expose it to tests.
99 base::Optional<Origin> UnsafelyCreateOpaqueOriginWithoutNormalization(
100 base::StringPiece precursor_scheme,
101 base::StringPiece precursor_host,
102 uint16_t precursor_port,
103 const Origin::Nonce& nonce) {
104 return Origin::UnsafelyCreateOpaqueOriginWithoutNormalization(
105 precursor_scheme, precursor_host, precursor_port, nonce);
108 base::Optional<std::string> SerializeWithNonce(const Origin& origin) {
109 return origin.SerializeWithNonce();
112 base::Optional<Origin> Deserialize(const std::string& value) {
113 return Origin::Deserialize(value);
117 ScopedSchemeRegistryForTests scoped_registry_;
120 TEST_F(OriginTest, OpaqueOriginComparison) {
121 // A default-constructed Origin should should be cross origin to everything
123 url::Origin opaque_a, opaque_b;
124 EXPECT_TRUE(opaque_a.opaque());
125 EXPECT_EQ("", opaque_a.scheme());
126 EXPECT_EQ("", opaque_a.host());
127 EXPECT_EQ(0, opaque_a.port());
128 EXPECT_EQ(SchemeHostPort(), opaque_a.GetTupleOrPrecursorTupleIfOpaque());
129 EXPECT_FALSE(opaque_a.GetTupleOrPrecursorTupleIfOpaque().IsValid());
131 EXPECT_TRUE(opaque_b.opaque());
132 EXPECT_EQ("", opaque_b.scheme());
133 EXPECT_EQ("", opaque_b.host());
134 EXPECT_EQ(0, opaque_b.port());
135 EXPECT_EQ(SchemeHostPort(), opaque_b.GetTupleOrPrecursorTupleIfOpaque());
136 EXPECT_FALSE(opaque_b.GetTupleOrPrecursorTupleIfOpaque().IsValid());
138 // Two default-constructed Origins should always be cross origin to each
140 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
141 EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
142 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
144 // The streaming operator should not trigger lazy initialization to the token.
145 std::ostringstream stream;
147 EXPECT_STREQ("null [internally: (nonce TBD) anonymous]",
148 stream.str().c_str());
149 EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
151 // None of the operations thus far should have triggered lazy-generation of
152 // the UnguessableToken. Copying an origin, however, should trigger this.
153 EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
154 EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_b));
157 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a));
158 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
159 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, true));
160 EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
161 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
163 // Move-initializing to a fresh Origin should restore the lazy initialization.
164 opaque_a = url::Origin();
165 EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
166 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
167 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
168 EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
169 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
171 // Comparing two opaque Origins with matching SchemeHostPorts should trigger
172 // lazy initialization.
173 EXPECT_FALSE(HasNonceTokenBeenInitialized(opaque_a));
174 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
175 bool should_swap = opaque_b < opaque_a;
176 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_a));
177 EXPECT_TRUE(HasNonceTokenBeenInitialized(opaque_b));
180 std::swap(opaque_a, opaque_b);
181 EXPECT_LT(opaque_a, opaque_b);
182 EXPECT_FALSE(opaque_b < opaque_a);
184 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_b, false));
185 EXPECT_TRUE(DoEqualityComparisons(opaque_b, opaque_b, true));
186 EXPECT_TRUE(DoEqualityComparisons(opaque_a, opaque_a, true));
188 EXPECT_LT(opaque_a, url::Origin::Create(GURL("http://www.google.com")));
189 EXPECT_LT(opaque_b, url::Origin::Create(GURL("http://www.google.com")));
191 EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:blank"), opaque_b));
192 EXPECT_EQ(opaque_b, url::Origin::Resolve(GURL("about:srcdoc"), opaque_b));
194 url::Origin::Resolve(GURL("about:blank?hello#whee"), opaque_b));
196 const char* const urls[] = {
197 "data:text/html,Hello!",
198 "javascript:alert(1)",
200 "file://example.com:443/etc/passwd",
201 "unknown-scheme:foo",
202 "unknown-scheme://bar",
210 "http::///invalid.example.com/",
211 "blob:null/foo", // blob:null (actually a valid URL)
212 "blob:data:foo", // blob + data (which is nonstandard)
213 "blob:about://blank/", // blob + about (which is nonstandard)
214 "blob:about:blank/", // blob + about (which is nonstandard)
215 "filesystem:http://example.com/", // Invalid (missing /type/)
216 "filesystem:local-but-nonstandard:baz./type/", // fs requires standard
217 "filesystem:local-but-nonstandard://hostname/type/",
218 "filesystem:unknown-scheme://hostname/type/",
219 "local-but-nonstandar:foo", // Prefix of registered scheme.
220 "but-nonstandard:foo", // Suffix of registered scheme.
221 "local-and-standard:", // Standard scheme needs a hostname.
222 "standard-but-noaccess:", // Standard scheme needs a hostname.
223 "blob:blob:http://www.example.com/guid-goes-here", // Double blob.
226 for (auto* test_url : urls) {
227 SCOPED_TRACE(test_url);
229 const url::Origin opaque_origin;
231 // Opaque origins returned by Origin::Create().
233 Origin origin = Origin::Create(url);
234 EXPECT_EQ("", origin.scheme());
235 EXPECT_EQ("", origin.host());
236 EXPECT_EQ(0, origin.port());
237 EXPECT_TRUE(origin.opaque());
238 // An origin is always same-origin with itself.
239 EXPECT_EQ(origin, origin);
240 EXPECT_NE(origin, url::Origin());
241 EXPECT_EQ(SchemeHostPort(), origin.GetTupleOrPrecursorTupleIfOpaque());
242 // A copy of |origin| should be same-origin as well.
243 Origin origin_copy = origin;
244 EXPECT_EQ("", origin_copy.scheme());
245 EXPECT_EQ("", origin_copy.host());
246 EXPECT_EQ(0, origin_copy.port());
247 EXPECT_TRUE(origin_copy.opaque());
248 EXPECT_EQ(origin, origin_copy);
249 // And it should always be cross-origin to another opaque Origin.
250 EXPECT_NE(origin, opaque_origin);
251 // Re-creating from the URL should also be cross-origin.
252 EXPECT_NE(origin, Origin::Create(url));
254 ExpectParsedUrlsEqual(GURL(origin.Serialize()), origin.GetURL());
259 TEST_F(OriginTest, ConstructFromTuple) {
261 const char* const scheme;
262 const char* const host;
265 {"http", "example.com", 80},
266 {"http", "example.com", 123},
267 {"https", "example.com", 443},
270 for (const auto& test_case : cases) {
271 testing::Message scope_message;
272 scope_message << test_case.scheme << "://" << test_case.host << ":"
274 SCOPED_TRACE(scope_message);
275 Origin origin = Origin::CreateFromNormalizedTuple(
276 test_case.scheme, test_case.host, test_case.port);
278 EXPECT_EQ(test_case.scheme, origin.scheme());
279 EXPECT_EQ(test_case.host, origin.host());
280 EXPECT_EQ(test_case.port, origin.port());
284 TEST_F(OriginTest, ConstructFromGURL) {
285 Origin different_origin =
286 Origin::Create(GURL("https://not-in-the-list.test/"));
289 const char* const url;
290 const char* const expected_scheme;
291 const char* const expected_host;
292 const uint16_t expected_port;
295 {"http://192.168.9.1/", "http", "192.168.9.1", 80},
296 {"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80},
297 {"http://1/", "http", "0.0.0.1", 80},
298 {"http://1:1/", "http", "0.0.0.1", 1},
299 {"http://3232237825/", "http", "192.168.9.1", 80},
302 {"http://☃.net/", "http", "xn--n3h.net", 80},
303 {"blob:http://☃.net/", "http", "xn--n3h.net", 80},
306 {"http://example.com/", "http", "example.com", 80},
307 {"http://example.com:123/", "http", "example.com", 123},
308 {"https://example.com/", "https", "example.com", 443},
309 {"https://example.com:123/", "https", "example.com", 123},
310 {"http://user:pass@example.com/", "http", "example.com", 80},
311 {"http://example.com:123/?query", "http", "example.com", 123},
312 {"https://example.com/#1234", "https", "example.com", 443},
313 {"https://u:p@example.com:123/?query#1234", "https", "example.com", 123},
316 {"ftp://example.com/", "ftp", "example.com", 21},
317 {"ws://example.com/", "ws", "example.com", 80},
318 {"wss://example.com/", "wss", "example.com", 443},
319 {"wss://user:pass@example.com/", "wss", "example.com", 443},
321 // Scheme (registered in SetUp()) that's both local and standard.
322 // TODO: Is it really appropriate to do network-host canonicalization of
323 // schemes without ports?
324 {"local-and-standard:20", "local-and-standard", "0.0.0.20", 0},
325 {"local-and-standard:20.", "local-and-standard", "0.0.0.20", 0},
326 {"local-and-standard:↑↑↓↓←→←→ba.↑↑↓↓←→←→ba.0.bg", "local-and-standard",
327 "xn--ba-rzuadaibfa.xn--ba-rzuadaibfa.0.bg", 0},
328 {"local-and-standard:foo", "local-and-standard", "foo", 0},
329 {"local-and-standard://bar:20", "local-and-standard", "bar", 0},
330 {"local-and-standard:baz.", "local-and-standard", "baz.", 0},
331 {"local-and-standard:baz..", "local-and-standard", "baz..", 0},
332 {"local-and-standard:baz..bar", "local-and-standard", "baz..bar", 0},
333 {"local-and-standard:baz...", "local-and-standard", "baz...", 0},
335 // Scheme (registered in SetUp()) that's local but nonstandard. These
336 // always have empty hostnames, but are allowed to be url::Origins.
337 {"local-but-nonstandard:", "local-but-nonstandard", "", 0},
338 {"local-but-nonstandard:foo", "local-but-nonstandard", "", 0},
339 {"local-but-nonstandard://bar", "local-but-nonstandard", "", 0},
340 {"also-local-but-nonstandard://bar", "also-local-but-nonstandard", "", 0},
342 // Scheme (registered in SetUp()) that's standard but marked as noaccess.
343 // url::Origin doesn't currently take the noaccess property into account,
344 // so these aren't expected to result in opaque origins.
345 {"standard-but-noaccess:foo", "standard-but-noaccess", "foo", 0},
346 {"standard-but-noaccess://bar", "standard-but-noaccess", "bar", 0},
349 {"file:///etc/passwd", "file", "", 0},
350 {"file://example.com/etc/passwd", "file", "example.com", 0},
353 {"filesystem:http://example.com/type/", "http", "example.com", 80},
354 {"filesystem:http://example.com:123/type/", "http", "example.com", 123},
355 {"filesystem:https://example.com/type/", "https", "example.com", 443},
356 {"filesystem:https://example.com:123/type/", "https", "example.com", 123},
357 {"filesystem:local-and-standard:baz./type/", "local-and-standard", "baz.",
361 {"blob:http://example.com/guid-goes-here", "http", "example.com", 80},
362 {"blob:http://example.com:123/guid-goes-here", "http", "example.com",
364 {"blob:https://example.com/guid-goes-here", "https", "example.com", 443},
365 {"blob:http://u:p@example.com/guid-goes-here", "http", "example.com", 80},
368 for (const auto& test_case : cases) {
369 SCOPED_TRACE(test_case.url);
370 GURL url(test_case.url);
371 EXPECT_TRUE(url.is_valid());
372 Origin origin = Origin::Create(url);
373 EXPECT_EQ(test_case.expected_scheme, origin.scheme());
374 EXPECT_EQ(test_case.expected_host, origin.host());
375 EXPECT_EQ(test_case.expected_port, origin.port());
376 EXPECT_FALSE(origin.opaque());
377 EXPECT_EQ(origin, origin);
378 EXPECT_NE(different_origin, origin);
379 EXPECT_NE(origin, different_origin);
380 EXPECT_EQ(origin, Origin::Resolve(GURL("about:blank"), origin));
381 EXPECT_EQ(origin, Origin::Resolve(GURL("about:blank?bar#foo"), origin));
383 ExpectParsedUrlsEqual(GURL(origin.Serialize()), origin.GetURL());
385 url::Origin derived_opaque =
386 Origin::Resolve(GURL("about:blank?bar#foo"), origin)
387 .DeriveNewOpaqueOrigin();
388 EXPECT_TRUE(derived_opaque.opaque());
389 EXPECT_NE(origin, derived_opaque);
390 EXPECT_TRUE(derived_opaque.GetTupleOrPrecursorTupleIfOpaque().IsValid());
391 EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(),
392 derived_opaque.GetTupleOrPrecursorTupleIfOpaque());
393 EXPECT_EQ(derived_opaque, derived_opaque);
395 url::Origin derived_opaque_via_data_url =
396 Origin::Resolve(GURL("data:text/html,baz"), origin);
397 EXPECT_TRUE(derived_opaque_via_data_url.opaque());
398 EXPECT_NE(origin, derived_opaque_via_data_url);
399 EXPECT_TRUE(derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque()
401 EXPECT_EQ(origin.GetTupleOrPrecursorTupleIfOpaque(),
402 derived_opaque_via_data_url.GetTupleOrPrecursorTupleIfOpaque());
403 EXPECT_NE(derived_opaque, derived_opaque_via_data_url);
404 EXPECT_NE(derived_opaque_via_data_url, derived_opaque);
405 EXPECT_NE(derived_opaque.DeriveNewOpaqueOrigin(), derived_opaque);
406 EXPECT_EQ(derived_opaque_via_data_url, derived_opaque_via_data_url);
410 TEST_F(OriginTest, Serialization) {
412 const char* const url;
413 const char* const expected;
414 const char* const expected_log;
416 {"http://192.168.9.1/", "http://192.168.9.1"},
417 {"http://[2001:db8::1]/", "http://[2001:db8::1]"},
418 {"http://☃.net/", "http://xn--n3h.net"},
419 {"http://example.com/", "http://example.com"},
420 {"http://example.com:123/", "http://example.com:123"},
421 {"https://example.com/", "https://example.com"},
422 {"https://example.com:123/", "https://example.com:123"},
423 {"file:///etc/passwd", "file://", "file:// [internally: file://]"},
424 {"file://example.com/etc/passwd", "file://",
425 "file:// [internally: file://example.com]"},
426 {"data:,", "null", "null [internally: (nonce TBD) anonymous]"},
429 for (const auto& test_case : cases) {
430 SCOPED_TRACE(test_case.url);
431 GURL url(test_case.url);
432 EXPECT_TRUE(url.is_valid());
433 Origin origin = Origin::Create(url);
434 std::string serialized = origin.Serialize();
435 ExpectParsedUrlsEqual(GURL(serialized), origin.GetURL());
437 EXPECT_EQ(test_case.expected, serialized);
439 // The '<<' operator sometimes produces additional information.
440 std::stringstream out;
442 if (test_case.expected_log)
443 EXPECT_EQ(test_case.expected_log, out.str());
445 EXPECT_EQ(test_case.expected, out.str());
449 TEST_F(OriginTest, Comparison) {
450 // These URLs are arranged in increasing order:
451 const char* const urls[] = {
452 "data:uniqueness", "http://a:80", "http://b:80",
453 "https://a:80", "https://b:80", "http://a:81",
454 "http://b:81", "https://a:81", "https://b:81",
456 // Validate the comparison logic still works when creating a canonical origin,
457 // when any created opaque origins contain a nonce.
459 // Pre-create the origins, as the internal nonce for unique origins changes
460 // with each freshly-constructed Origin (that's not copied).
461 std::vector<Origin> origins;
462 for (const auto* test_url : urls)
463 origins.push_back(Origin::Create(GURL(test_url)));
464 for (size_t i = 0; i < origins.size(); i++) {
465 const Origin& current = origins[i];
466 for (size_t j = i; j < origins.size(); j++) {
467 const Origin& to_compare = origins[j];
468 EXPECT_EQ(i < j, current < to_compare) << i << " < " << j;
469 EXPECT_EQ(j < i, to_compare < current) << j << " < " << i;
475 TEST_F(OriginTest, UnsafelyCreate) {
481 {"http", "example.com", 80},
482 {"http", "example.com", 123},
483 {"https", "example.com", 443},
484 {"https", "example.com", 123},
485 {"http", "example.com", 0}, // 0 is a valid port for http.
486 {"file", "", 0}, // 0 indicates "no port" for file: scheme.
487 {"file", "example.com", 0},
490 for (const auto& test : cases) {
491 SCOPED_TRACE(testing::Message()
492 << test.scheme << "://" << test.host << ":" << test.port);
493 base::Optional<url::Origin> origin =
494 url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
495 test.scheme, test.host, test.port);
497 EXPECT_EQ(test.scheme, origin->scheme());
498 EXPECT_EQ(test.host, origin->host());
499 EXPECT_EQ(test.port, origin->port());
500 EXPECT_FALSE(origin->opaque());
501 EXPECT_TRUE(origin->IsSameOriginWith(*origin));
503 ExpectParsedUrlsEqual(GURL(origin->Serialize()), origin->GetURL());
505 base::UnguessableToken nonce = base::UnguessableToken::Create();
506 base::Optional<url::Origin> opaque_origin =
507 UnsafelyCreateOpaqueOriginWithoutNormalization(
508 test.scheme, test.host, test.port, CreateNonce(nonce));
509 ASSERT_TRUE(opaque_origin);
510 EXPECT_TRUE(opaque_origin->opaque());
511 EXPECT_FALSE(*opaque_origin == origin);
512 EXPECT_EQ(opaque_origin->GetTupleOrPrecursorTupleIfOpaque(),
513 origin->GetTupleOrPrecursorTupleIfOpaque());
514 EXPECT_EQ(opaque_origin,
515 UnsafelyCreateOpaqueOriginWithoutNormalization(
516 test.scheme, test.host, test.port, CreateNonce(nonce)));
517 EXPECT_FALSE(*opaque_origin == origin->DeriveNewOpaqueOrigin());
521 TEST_F(OriginTest, UnsafelyCreateUniqueOnInvalidInput) {
522 url::AddStandardScheme("host-only", url::SCHEME_WITH_HOST);
523 url::AddStandardScheme("host-port-only", url::SCHEME_WITH_HOST_AND_PORT);
528 } cases[] = {{"", "", 33},
531 {"filesystem", "", 0},
532 {"data", "example.com"},
534 {"http\nmore", "example.com"},
535 {"http\rmore", "example.com"},
536 {"http\n", "example.com"},
537 {"http\r", "example.com"},
538 {"http", "example.com\nnot-example.com"},
539 {"http", "example.com\rnot-example.com"},
540 {"http", "example.com\n"},
541 {"http", "example.com\r"},
542 {"unknown-scheme", "example.com"},
543 {"host-only", "\r", 0},
544 {"host-only", "example.com", 22},
545 {"file", "", 123}}; // file: shouldn't have a port.
547 for (const auto& test : cases) {
548 SCOPED_TRACE(testing::Message()
549 << test.scheme << "://" << test.host << ":" << test.port);
550 EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization(
551 test.scheme, test.host, test.port, CreateNonce()));
552 EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
553 test.scheme, test.host, test.port));
556 // An empty scheme/host/port tuple is not a valid tuple origin.
558 url::Origin::UnsafelyCreateTupleOriginWithoutNormalization("", "", 0));
560 // Opaque origins with unknown precursors are allowed.
561 base::UnguessableToken token = base::UnguessableToken::Create();
562 base::Optional<url::Origin> anonymous_opaque =
563 UnsafelyCreateOpaqueOriginWithoutNormalization("", "", 0,
565 ASSERT_TRUE(anonymous_opaque)
566 << "An invalid tuple is a valid input to "
567 << "UnsafelyCreateOpaqueOriginWithoutNormalization, so long as it is "
568 << "the canonical form of the invalid tuple.";
569 EXPECT_TRUE(anonymous_opaque->opaque());
570 EXPECT_EQ(GetNonce(anonymous_opaque.value()), token);
571 EXPECT_EQ(anonymous_opaque->GetTupleOrPrecursorTupleIfOpaque(),
572 url::SchemeHostPort());
575 TEST_F(OriginTest, UnsafelyCreateUniqueViaEmbeddedNulls) {
577 base::StringPiece scheme;
578 base::StringPiece host;
580 } cases[] = {{{"http\0more", 9}, {"example.com", 11}},
581 {{"http\0", 5}, {"example.com", 11}},
582 {{"\0http", 5}, {"example.com", 11}},
583 {{"http"}, {"example.com\0not-example.com", 27}},
584 {{"http"}, {"example.com\0", 12}},
585 {{"http"}, {"\0example.com", 12}},
586 {{""}, {"\0", 1}, 0},
587 {{"\0", 1}, {""}, 0}};
589 for (const auto& test : cases) {
590 SCOPED_TRACE(testing::Message()
591 << test.scheme << "://" << test.host << ":" << test.port);
592 EXPECT_FALSE(url::Origin::UnsafelyCreateTupleOriginWithoutNormalization(
593 test.scheme, test.host, test.port));
594 EXPECT_FALSE(UnsafelyCreateOpaqueOriginWithoutNormalization(
595 test.scheme, test.host, test.port, CreateNonce()));
599 TEST_F(OriginTest, DomainIs) {
602 const char* lower_ascii_domain;
603 bool expected_domain_is;
605 {"http://google.com/foo", "google.com", true},
606 {"http://www.google.com:99/foo", "google.com", true},
607 {"http://www.google.com.cn/foo", "google.com", false},
608 {"http://www.google.comm", "google.com", false},
609 {"http://www.iamnotgoogle.com/foo", "google.com", false},
610 {"http://www.google.com/foo", "Google.com", false},
612 // If the host ends with a dot, it matches domains with or without a dot.
613 {"http://www.google.com./foo", "google.com", true},
614 {"http://www.google.com./foo", "google.com.", true},
615 {"http://www.google.com./foo", ".com", true},
616 {"http://www.google.com./foo", ".com.", true},
618 // But, if the host doesn't end with a dot and the input domain does, then
619 // it's considered to not match.
620 {"http://google.com/foo", "google.com.", false},
622 // If the host ends with two dots, it doesn't match.
623 {"http://www.google.com../foo", "google.com", false},
625 // Filesystem scheme.
626 {"filesystem:http://www.google.com:99/foo/", "google.com", true},
627 {"filesystem:http://www.iamnotgoogle.com/foo/", "google.com", false},
630 {"file:///home/user/text.txt", "", false},
631 {"file:///home/user/text.txt", "txt", false},
634 for (const auto& test_case : kTestCases) {
635 SCOPED_TRACE(testing::Message()
636 << "(url, domain): (" << test_case.url << ", "
637 << test_case.lower_ascii_domain << ")");
638 GURL url(test_case.url);
639 ASSERT_TRUE(url.is_valid());
640 Origin origin = Origin::Create(url);
642 EXPECT_EQ(test_case.expected_domain_is,
643 origin.DomainIs(test_case.lower_ascii_domain));
645 origin.DeriveNewOpaqueOrigin().DomainIs(test_case.lower_ascii_domain));
648 // If the URL is invalid, DomainIs returns false.
649 GURL invalid_url("google.com");
650 ASSERT_FALSE(invalid_url.is_valid());
651 EXPECT_FALSE(Origin::Create(invalid_url).DomainIs("google.com"));
654 EXPECT_FALSE(Origin().DomainIs(""));
655 EXPECT_FALSE(Origin().DomainIs("com"));
658 TEST_F(OriginTest, DebugAlias) {
659 Origin origin1 = Origin::Create(GURL("https://foo.com/bar"));
660 DEBUG_ALIAS_FOR_ORIGIN(origin1_debug_alias, origin1);
661 EXPECT_STREQ("https://foo.com", origin1_debug_alias);
664 TEST_F(OriginTest, NonStandardScheme) {
665 Origin origin = Origin::Create(GURL("cow://"));
666 EXPECT_TRUE(origin.opaque());
669 TEST_F(OriginTest, NonStandardSchemeWithAndroidWebViewHack) {
670 EnableNonStandardSchemesForAndroidWebView();
671 Origin origin = Origin::Create(GURL("cow://"));
672 EXPECT_FALSE(origin.opaque());
673 EXPECT_EQ("cow", origin.scheme());
674 EXPECT_EQ("", origin.host());
675 EXPECT_EQ(0, origin.port());
678 TEST_F(OriginTest, CanBeDerivedFrom) {
679 AddStandardScheme("new-standard", SchemeType::SCHEME_WITH_HOST);
680 Origin opaque_unique_origin = Origin();
682 Origin regular_origin = Origin::Create(GURL("https://a.com/"));
683 Origin opaque_precursor_origin = regular_origin.DeriveNewOpaqueOrigin();
685 Origin file_origin = Origin::Create(GURL("file:///foo/bar"));
686 Origin file_opaque_precursor_origin = file_origin.DeriveNewOpaqueOrigin();
687 Origin file_host_origin = Origin::Create(GURL("file://a.com/foo/bar"));
688 Origin file_host_opaque_precursor_origin =
689 file_host_origin.DeriveNewOpaqueOrigin();
691 Origin non_standard_scheme_origin =
692 Origin::Create(GURL("non-standard-scheme:foo"));
693 Origin non_standard_opaque_precursor_origin =
694 non_standard_scheme_origin.DeriveNewOpaqueOrigin();
696 // Also, add new standard scheme that is local to the test.
697 Origin new_standard_origin = Origin::Create(GURL("new-standard://host/"));
698 Origin new_standard_opaque_precursor_origin =
699 new_standard_origin.DeriveNewOpaqueOrigin();
701 // No access schemes always get unique opaque origins.
702 Origin no_access_origin =
703 Origin::Create(GURL("standard-but-noaccess://b.com"));
704 Origin no_access_opaque_precursor_origin =
705 no_access_origin.DeriveNewOpaqueOrigin();
707 Origin local_non_standard_origin =
708 Origin::Create(GURL("local-but-nonstandard://a.com"));
709 Origin local_non_standard_opaque_precursor_origin =
710 local_non_standard_origin.DeriveNewOpaqueOrigin();
712 // Call origin.CanBeDerivedFrom(url) for each of the following test cases
713 // and ensure that it returns |expected_value|
719 {"https://a.com", ®ular_origin, true},
720 // Web URL can commit in an opaque origin with precursor information.
721 // Example: iframe sandbox navigated to a.com.
722 {"https://a.com", &opaque_precursor_origin, true},
723 // URL that comes from the web can never commit in an opaque unique
724 // origin. It must have precursor information.
725 {"https://a.com", &opaque_unique_origin, false},
727 // Cross-origin URLs should never work.
728 {"https://b.com", ®ular_origin, false},
729 {"https://b.com", &opaque_precursor_origin, false},
731 // data: URL can never commit in a regular, non-opaque origin.
732 {"data:text/html,foo", ®ular_origin, false},
733 // This is the default case: data: URLs commit in opaque origin carrying
734 // precursor information for the origin that created them.
735 {"data:text/html,foo", &opaque_precursor_origin, true},
736 // Browser-initiated navigations can result in data: URL committing in
737 // opaque unique origin.
738 {"data:text/html,foo", &opaque_unique_origin, true},
740 // about:blank can commit in regular origin (default case for iframes).
741 {"about:blank", ®ular_origin, true},
742 // This can happen if data: URL that originated at a.com creates an
743 // about:blank iframe.
744 {"about:blank", &opaque_precursor_origin, true},
745 // Browser-initiated navigations can result in about:blank URL committing
746 // in opaque unique origin.
747 {"about:blank", &opaque_unique_origin, true},
749 // Default behavior of srcdoc is to inherit the origin of the parent
751 {"about:srcdoc", ®ular_origin, true},
752 // This happens for sandboxed srcdoc iframe.
753 {"about:srcdoc", &opaque_precursor_origin, true},
754 // This can happen with browser-initiated navigation to about:blank or
755 // data: URL, which in turn add srcdoc iframe.
756 {"about:srcdoc", &opaque_unique_origin, true},
758 // Just like srcdoc, blob: URLs can be created in all the cases.
759 {"blob:https://a.com/foo", ®ular_origin, true},
760 {"blob:https://a.com/foo", &opaque_precursor_origin, true},
761 {"blob:https://a.com/foo", &opaque_unique_origin, true},
763 {"filesystem:https://a.com/foo", ®ular_origin, true},
764 {"filesystem:https://a.com/foo", &opaque_precursor_origin, true},
765 // Unlike blob: URLs, filesystem: ones cannot be created in an unique
767 {"filesystem:https://a.com/foo", &opaque_unique_origin, false},
769 // file: URLs cannot result in regular web origins, regardless of
771 {"file:///etc/passwd", ®ular_origin, false},
772 {"file:///etc/passwd", &opaque_precursor_origin, false},
773 // However, they can result in regular file: origin and an opaque one
774 // containing another file: origin as precursor.
775 {"file:///etc/passwd", &file_origin, true},
776 {"file:///etc/passwd", &file_opaque_precursor_origin, true},
777 // It should not be possible to get an opaque unique origin for file:
778 // as it is a standard scheme and will always result in a tuple origin
779 // or will always be derived by other origin.
780 // Note: file:// URLs should become unique opaque origins at some point.
781 {"file:///etc/passwd", &opaque_unique_origin, false},
783 // The same set as above, but including a host.
784 {"file://a.com/etc/passwd", ®ular_origin, false},
785 {"file://a.com/etc/passwd", &opaque_precursor_origin, false},
786 {"file://a.com/etc/passwd", &file_host_origin, true},
787 {"file://a.com/etc/passwd", &file_host_opaque_precursor_origin, true},
788 {"file://a.com/etc/passwd", &opaque_unique_origin, false},
790 // Locally registered standard scheme should behave the same way
791 // as built-in standard schemes.
792 {"new-standard://host/foo", &new_standard_origin, true},
793 {"new-standard://host/foo", &new_standard_opaque_precursor_origin, true},
794 {"new-standard://host/foo", &opaque_unique_origin, false},
795 {"new-standard://host2/foo", &new_standard_origin, false},
796 {"new-standard://host2/foo", &new_standard_opaque_precursor_origin,
799 // A non-standard scheme should never commit in an standard origin or
800 // opaque origin with standard precursor information.
801 {"non-standard-scheme://a.com/foo", ®ular_origin, false},
802 {"non-standard-scheme://a.com/foo", &opaque_precursor_origin, false},
803 // However, it should be fine to commit in unique opaque origins or in its
805 // Note: since non-standard scheme URLs don't parse out anything
806 // but the scheme, using a random different hostname here would work.
807 {"non-standard-scheme://b.com/foo2", &opaque_unique_origin, true},
808 {"non-standard-scheme://b.com/foo3", &non_standard_scheme_origin, true},
809 {"non-standard-scheme://b.com/foo4",
810 &non_standard_opaque_precursor_origin, true},
812 // No access scheme can only commit in opaque origin.
813 {"standard-but-noaccess://a.com/foo", ®ular_origin, false},
814 {"standard-but-noaccess://a.com/foo", &opaque_precursor_origin, false},
815 {"standard-but-noaccess://a.com/foo", &opaque_unique_origin, true},
816 {"standard-but-noaccess://a.com/foo", &no_access_origin, false},
817 {"standard-but-noaccess://a.com/foo", &no_access_opaque_precursor_origin,
819 {"standard-but-noaccess://b.com/foo", &no_access_origin, false},
820 {"standard-but-noaccess://b.com/foo", &no_access_opaque_precursor_origin,
823 // Local schemes can be non-standard, verify they also work as expected.
824 {"local-but-nonstandard://a.com", ®ular_origin, false},
825 {"local-but-nonstandard://a.com", &opaque_precursor_origin, false},
826 {"local-but-nonstandard://a.com", &opaque_unique_origin, true},
827 {"local-but-nonstandard://a.com", &local_non_standard_origin, true},
828 {"local-but-nonstandard://a.com",
829 &local_non_standard_opaque_precursor_origin, true},
832 for (const auto& test_case : kTestCases) {
833 SCOPED_TRACE(testing::Message() << "(origin, url): (" << *test_case.origin
834 << ", " << test_case.url << ")");
835 EXPECT_EQ(test_case.expected_value,
836 test_case.origin->CanBeDerivedFrom(GURL(test_case.url)));
840 TEST_F(OriginTest, GetDebugString) {
841 Origin http_origin = Origin::Create(GURL("http://192.168.9.1"));
842 EXPECT_STREQ(http_origin.GetDebugString().c_str(), "http://192.168.9.1");
844 Origin http_opaque_origin = http_origin.DeriveNewOpaqueOrigin();
846 http_opaque_origin.GetDebugString().c_str(),
847 ::testing::MatchesRegex(
848 "null \\[internally: \\(\\w*\\) derived from http://192.168.9.1\\]"));
850 Origin data_origin = Origin::Create(GURL("data:"));
851 EXPECT_STREQ(data_origin.GetDebugString().c_str(),
852 "null [internally: (nonce TBD) anonymous]");
854 // The nonce of the origin will be initialized if a new opaque origin is
856 Origin data_derived_origin = data_origin.DeriveNewOpaqueOrigin();
858 data_derived_origin.GetDebugString().c_str(),
859 ::testing::MatchesRegex("null \\[internally: \\(\\w*\\) anonymous\\]"));
861 Origin file_origin = Origin::Create(GURL("file:///etc/passwd"));
862 EXPECT_STREQ(file_origin.GetDebugString().c_str(),
863 "file:// [internally: file://]");
865 Origin file_server_origin =
866 Origin::Create(GURL("file://example.com/etc/passwd"));
867 EXPECT_STREQ(file_server_origin.GetDebugString().c_str(),
868 "file:// [internally: file://example.com]");
871 TEST_F(OriginTest, Deserialize) {
872 std::vector<GURL> valid_urls = {
873 GURL("https://a.com"), GURL("http://a"),
874 GURL("http://a:80"), GURL("file://a.com/etc/passwd"),
875 GURL("file:///etc/passwd"), GURL("http://192.168.1.1"),
876 GURL("http://[2001:db8::1]/"),
878 for (const GURL& url : valid_urls) {
879 SCOPED_TRACE(url.spec());
880 Origin origin = Origin::Create(url);
881 base::Optional<std::string> serialized = SerializeWithNonce(origin);
882 ASSERT_TRUE(serialized);
884 base::Optional<Origin> deserialized = Deserialize(std::move(*serialized));
885 ASSERT_TRUE(deserialized.has_value());
887 EXPECT_TRUE(DoEqualityComparisons(origin, deserialized.value(), true));
888 EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString());
892 TEST_F(OriginTest, DeserializeInvalid) {
893 EXPECT_EQ(base::nullopt, Deserialize(std::string()));
894 EXPECT_EQ(base::nullopt, Deserialize("deadbeef"));
895 EXPECT_EQ(base::nullopt, Deserialize("0123456789"));
896 EXPECT_EQ(base::nullopt, Deserialize("https://a.com"));
897 EXPECT_EQ(base::nullopt, Deserialize("https://192.168.1.1"));
900 TEST_F(OriginTest, SerializeTBDNonce) {
901 std::vector<GURL> invalid_urls = {
902 GURL("data:uniqueness"), GURL("data:,"),
903 GURL("data:text/html,Hello!"), GURL("javascript:alert(1)"),
904 GURL("about:blank"), GURL("google.com"),
906 for (const GURL& url : invalid_urls) {
907 SCOPED_TRACE(url.spec());
908 Origin origin = Origin::Create(url);
909 base::Optional<std::string> serialized = SerializeWithNonce(origin);
910 base::Optional<Origin> deserialized = Deserialize(std::move(*serialized));
911 ASSERT_TRUE(deserialized.has_value());
913 // Can't use DoEqualityComparisons here since empty nonces are never ==
914 // unless they are the same object.
915 EXPECT_EQ(origin.GetDebugString(), deserialized.value().GetDebugString());
918 // Same basic test as above, but without a GURL to create tuple_.
920 base::Optional<std::string> serialized = SerializeWithNonce(opaque);
921 ASSERT_TRUE(serialized);
923 base::Optional<Origin> deserialized = Deserialize(std::move(*serialized));
924 ASSERT_TRUE(deserialized.has_value());
926 // Can't use DoEqualityComparisons here since empty nonces are never == unless
927 // they are the same object.
928 EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString());
931 TEST_F(OriginTest, DeserializeValidNonce) {
935 base::Optional<std::string> serialized = SerializeWithNonce(opaque);
936 ASSERT_TRUE(serialized);
938 base::Optional<Origin> deserialized = Deserialize(std::move(*serialized));
939 ASSERT_TRUE(deserialized.has_value());
941 EXPECT_TRUE(DoEqualityComparisons(opaque, deserialized.value(), true));
942 EXPECT_EQ(opaque.GetDebugString(), deserialized.value().GetDebugString());