1 // Copyright 2020 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 #ifndef URL_ORIGIN_ABSTRACT_TESTS_H_
6 #define URL_ORIGIN_ABSTRACT_TESTS_H_
7
8 #include <initializer_list>
9 #include <string>
10 #include <string_view>
11 #include <type_traits>
12
13 #include "base/containers/contains.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "url/gurl.h"
17 #include "url/origin.h"
18 #include "url/scheme_host_port.h"
19 #include "url/url_features.h"
20 #include "url/url_util.h"
21
22 namespace url {
23
24 void ExpectParsedUrlsEqual(const GURL& a, const GURL& b);
25
26 // AbstractOriginTest below abstracts away differences between url::Origin and
27 // blink::SecurityOrigin by parametrizing the tests with a class that has to
28 // expose the same public members as UrlOriginTestTraits below.
29 class UrlOriginTestTraits {
30 public:
31 using OriginType = Origin;
32
33 // Constructing an origin.
34 static OriginType CreateOriginFromString(std::string_view s);
35 static OriginType CreateUniqueOpaqueOrigin();
36 static OriginType CreateWithReferenceOrigin(
37 std::string_view url,
38 const OriginType& reference_origin);
39 static OriginType DeriveNewOpaqueOrigin(const OriginType& reference_origin);
40
41 // Accessors for origin properties.
42 static bool IsOpaque(const OriginType& origin);
43 static std::string GetScheme(const OriginType& origin);
44 static std::string GetHost(const OriginType& origin);
45 static uint16_t GetPort(const OriginType& origin);
46 static SchemeHostPort GetTupleOrPrecursorTupleIfOpaque(
47 const OriginType& origin);
48
49 // Wrappers for other instance methods of OriginType.
50 static bool IsSameOrigin(const OriginType& a, const OriginType& b);
51 static std::string Serialize(const OriginType& origin);
52
53 // "Accessors" of URL properties.
54 //
55 // TODO(lukasza): Consider merging together OriginTraitsBase here and
56 // UrlTraitsBase in //url/gurl_abstract_tests.h.
57 static bool IsValidUrl(std::string_view str);
58
59 // Only static members = no constructors are needed.
60 UrlOriginTestTraits() = delete;
61 };
62
63 // Test suite for tests that cover both url::Origin and blink::SecurityOrigin.
64 template <typename TOriginTraits>
65 class AbstractOriginTest : public testing::Test {
66 public:
SetUp()67 void SetUp() override {
68 const char* kSchemesToRegister[] = {
69 "noaccess",
70 "std-with-host",
71 "noaccess-std-with-host",
72 "local",
73 "local-noaccess",
74 "local-std-with-host",
75 "local-noaccess-std-with-host",
76 "also-local",
77 "sec",
78 "sec-std-with-host",
79 "sec-noaccess",
80 };
81 for (const char* kScheme : kSchemesToRegister) {
82 std::string scheme(kScheme);
83 if (base::Contains(scheme, "noaccess"))
84 AddNoAccessScheme(kScheme);
85 if (base::Contains(scheme, "std-with-host"))
86 AddStandardScheme(kScheme, SchemeType::SCHEME_WITH_HOST);
87 if (base::Contains(scheme, "local"))
88 AddLocalScheme(kScheme);
89 if (base::Contains(scheme, "sec"))
90 AddSecureScheme(kScheme);
91 }
92 }
93
94 protected:
95 // Wrappers that help ellide away TOriginTraits.
96 //
97 // Note that calling the wrappers needs to be prefixed with `this->...` to
98 // avoid hitting: explicit qualification required to use member 'IsOpaque'
99 // from dependent base class.
100 using OriginType = typename TOriginTraits::OriginType;
CreateOriginFromString(std::string_view s)101 OriginType CreateOriginFromString(std::string_view s) {
102 return TOriginTraits::CreateOriginFromString(s);
103 }
CreateUniqueOpaqueOrigin()104 OriginType CreateUniqueOpaqueOrigin() {
105 return TOriginTraits::CreateUniqueOpaqueOrigin();
106 }
CreateWithReferenceOrigin(std::string_view url,const OriginType & reference_origin)107 OriginType CreateWithReferenceOrigin(std::string_view url,
108 const OriginType& reference_origin) {
109 return TOriginTraits::CreateWithReferenceOrigin(url, reference_origin);
110 }
DeriveNewOpaqueOrigin(const OriginType & reference_origin)111 OriginType DeriveNewOpaqueOrigin(const OriginType& reference_origin) {
112 return TOriginTraits::DeriveNewOpaqueOrigin(reference_origin);
113 }
IsOpaque(const OriginType & origin)114 bool IsOpaque(const OriginType& origin) {
115 return TOriginTraits::IsOpaque(origin);
116 }
GetScheme(const OriginType & origin)117 std::string GetScheme(const OriginType& origin) {
118 return TOriginTraits::GetScheme(origin);
119 }
GetHost(const OriginType & origin)120 std::string GetHost(const OriginType& origin) {
121 return TOriginTraits::GetHost(origin);
122 }
GetPort(const OriginType & origin)123 uint16_t GetPort(const OriginType& origin) {
124 return TOriginTraits::GetPort(origin);
125 }
GetTupleOrPrecursorTupleIfOpaque(const OriginType & origin)126 SchemeHostPort GetTupleOrPrecursorTupleIfOpaque(const OriginType& origin) {
127 return TOriginTraits::GetTupleOrPrecursorTupleIfOpaque(origin);
128 }
IsSameOrigin(const OriginType & a,const OriginType & b)129 bool IsSameOrigin(const OriginType& a, const OriginType& b) {
130 bool is_a_same_with_b = TOriginTraits::IsSameOrigin(a, b);
131 bool is_b_same_with_a = TOriginTraits::IsSameOrigin(b, a);
132 EXPECT_EQ(is_a_same_with_b, is_b_same_with_a);
133 return is_a_same_with_b;
134 }
Serialize(const OriginType & origin)135 std::string Serialize(const OriginType& origin) {
136 return TOriginTraits::Serialize(origin);
137 }
IsValidUrl(std::string_view str)138 bool IsValidUrl(std::string_view str) {
139 return TOriginTraits::IsValidUrl(str);
140 }
141
142 #define EXPECT_SAME_ORIGIN(a, b) \
143 EXPECT_TRUE(this->IsSameOrigin((a), (b))) \
144 << "When checking if \"" << this->Serialize(a) << "\" is " \
145 << "same-origin with \"" << this->Serialize(b) << "\""
146
147 #define EXPECT_CROSS_ORIGIN(a, b) \
148 EXPECT_FALSE(this->IsSameOrigin((a), (b))) \
149 << "When checking if \"" << this->Serialize(a) << "\" is " \
150 << "cross-origin from \"" << this->Serialize(b) << "\""
151
VerifyOriginInvariants(const OriginType & origin)152 void VerifyOriginInvariants(const OriginType& origin) {
153 // An origin is always same-origin with itself.
154 EXPECT_SAME_ORIGIN(origin, origin);
155
156 // A copy of |origin| should be same-origin as well.
157 auto origin_copy = origin;
158 EXPECT_EQ(this->GetScheme(origin), this->GetScheme(origin_copy));
159 EXPECT_EQ(this->GetHost(origin), this->GetHost(origin_copy));
160 EXPECT_EQ(this->GetPort(origin), this->GetPort(origin_copy));
161 EXPECT_EQ(this->IsOpaque(origin), this->IsOpaque(origin_copy));
162 EXPECT_SAME_ORIGIN(origin, origin_copy);
163
164 // An origin is always cross-origin from another, unique, opaque origin.
165 EXPECT_CROSS_ORIGIN(origin, this->CreateUniqueOpaqueOrigin());
166
167 // An origin is always cross-origin from another tuple origin.
168 auto different_tuple_origin =
169 this->CreateOriginFromString("https://not-in-the-list.test/");
170 EXPECT_CROSS_ORIGIN(origin, different_tuple_origin);
171
172 // Deriving an origin for "about:blank".
173 auto about_blank_origin1 =
174 this->CreateWithReferenceOrigin("about:blank", origin);
175 auto about_blank_origin2 =
176 this->CreateWithReferenceOrigin("about:blank?bar#foo", origin);
177 EXPECT_SAME_ORIGIN(origin, about_blank_origin1);
178 EXPECT_SAME_ORIGIN(origin, about_blank_origin2);
179
180 // Derived opaque origins.
181 std::vector<OriginType> derived_origins = {
182 this->DeriveNewOpaqueOrigin(origin),
183 this->CreateWithReferenceOrigin("data:text/html,baz", origin),
184 this->DeriveNewOpaqueOrigin(about_blank_origin1),
185 };
186 for (size_t i = 0; i < derived_origins.size(); i++) {
187 SCOPED_TRACE(testing::Message() << "Derived origin #" << i);
188 const OriginType& derived_origin = derived_origins[i];
189 EXPECT_TRUE(this->IsOpaque(derived_origin));
190 EXPECT_SAME_ORIGIN(derived_origin, derived_origin);
191 EXPECT_CROSS_ORIGIN(origin, derived_origin);
192 EXPECT_EQ(this->GetTupleOrPrecursorTupleIfOpaque(origin),
193 this->GetTupleOrPrecursorTupleIfOpaque(derived_origin));
194 }
195 }
196
VerifyUniqueOpaqueOriginInvariants(const OriginType & origin)197 void VerifyUniqueOpaqueOriginInvariants(const OriginType& origin) {
198 if (!this->IsOpaque(origin)) {
199 ADD_FAILURE() << "Got unexpectedly non-opaque origin: "
200 << this->Serialize(origin);
201 return; // Skip other test assertions.
202 }
203
204 // Opaque origins should have an "empty" scheme, host and port.
205 EXPECT_EQ("", this->GetScheme(origin));
206 EXPECT_EQ("", this->GetHost(origin));
207 EXPECT_EQ(0, this->GetPort(origin));
208
209 // Unique opaque origins should have an empty precursor tuple.
210 EXPECT_EQ(SchemeHostPort(), this->GetTupleOrPrecursorTupleIfOpaque(origin));
211
212 // Serialization test.
213 EXPECT_EQ("null", this->Serialize(origin));
214
215 // Invariants that should hold for any origin.
216 VerifyOriginInvariants(origin);
217 }
218
TestUniqueOpaqueOrigin(std::string_view test_input)219 void TestUniqueOpaqueOrigin(std::string_view test_input) {
220 auto origin = this->CreateOriginFromString(test_input);
221 this->VerifyUniqueOpaqueOriginInvariants(origin);
222
223 // Re-creating from the URL should be cross-origin.
224 auto origin_recreated_from_same_input =
225 this->CreateOriginFromString(test_input);
226 EXPECT_CROSS_ORIGIN(origin, origin_recreated_from_same_input);
227 }
228
VerifyTupleOriginInvariants(const OriginType & origin,const SchemeHostPort & expected_tuple)229 void VerifyTupleOriginInvariants(const OriginType& origin,
230 const SchemeHostPort& expected_tuple) {
231 if (this->IsOpaque(origin)) {
232 ADD_FAILURE() << "Got unexpectedly opaque origin";
233 return; // Skip other test assertions.
234 }
235 SCOPED_TRACE(testing::Message()
236 << "Actual origin: " << this->Serialize(origin));
237
238 // Compare `origin` against the `expected_tuple`.
239 EXPECT_EQ(expected_tuple.scheme(), this->GetScheme(origin));
240 EXPECT_EQ(expected_tuple.host(), this->GetHost(origin));
241 EXPECT_EQ(expected_tuple.port(), this->GetPort(origin));
242 EXPECT_EQ(expected_tuple, this->GetTupleOrPrecursorTupleIfOpaque(origin));
243
244 // Serialization test.
245 //
246 // TODO(lukasza): Consider preserving the hostname when serializing file:
247 // URLs. Dropping the hostname seems incompatible with section 6 of
248 // rfc6454. Even though section 4 says that "the implementation MAY
249 // return an implementation-defined value", it seems that Chromium
250 // implementation *does* include the hostname in the origin SchemeHostPort
251 // tuple.
252 if (expected_tuple.scheme() != kFileScheme || expected_tuple.host() == "") {
253 EXPECT_SAME_ORIGIN(origin,
254 this->CreateOriginFromString(this->Serialize(origin)));
255 }
256
257 // Invariants that should hold for any origin.
258 VerifyOriginInvariants(origin);
259 }
260
261 private:
262 ScopedSchemeRegistryForTests scoped_scheme_registry_;
263 };
264
265 TYPED_TEST_SUITE_P(AbstractOriginTest);
266
TYPED_TEST_P(AbstractOriginTest,NonStandardSchemeWithAndroidWebViewHack)267 TYPED_TEST_P(AbstractOriginTest, NonStandardSchemeWithAndroidWebViewHack) {
268 EnableNonStandardSchemesForAndroidWebView();
269
270 // Regression test for https://crbug.com/896059.
271 auto origin = this->CreateOriginFromString("unknown-scheme://");
272 EXPECT_FALSE(this->IsOpaque(origin));
273 EXPECT_EQ("unknown-scheme", this->GetScheme(origin));
274 EXPECT_EQ("", this->GetHost(origin));
275 EXPECT_EQ(0, this->GetPort(origin));
276
277 // about:blank translates into an opaque origin, even in presence of
278 // EnableNonStandardSchemesForAndroidWebView.
279 origin = this->CreateOriginFromString("about:blank");
280 EXPECT_TRUE(this->IsOpaque(origin));
281 }
282
TYPED_TEST_P(AbstractOriginTest,AndroidWebViewHackWithStandardCompliantNonSpecialSchemeURLParsing)283 TYPED_TEST_P(
284 AbstractOriginTest,
285 AndroidWebViewHackWithStandardCompliantNonSpecialSchemeURLParsing) {
286 EnableNonStandardSchemesForAndroidWebView();
287
288 // Manual flag-dependent tests to ensure that the behavior doesn't change
289 // whether the flag is enabled or not.
290 for (bool flag : {false, true}) {
291 base::test::ScopedFeatureList scoped_feature_list;
292 if (flag) {
293 scoped_feature_list.InitAndEnableFeature(
294 kStandardCompliantNonSpecialSchemeURLParsing);
295 } else {
296 scoped_feature_list.InitAndDisableFeature(
297 kStandardCompliantNonSpecialSchemeURLParsing);
298 }
299
300 // Non-Standard scheme cases.
301 {
302 auto origin_a = this->CreateOriginFromString("non-standard://a.com:80");
303 // Ensure that a host and a port are discarded.
304 EXPECT_EQ(this->GetHost(origin_a), "");
305 EXPECT_EQ(this->GetPort(origin_a), 0);
306 EXPECT_EQ(this->Serialize(origin_a), "non-standard://");
307 EXPECT_FALSE(this->IsOpaque(origin_a));
308
309 // URLs are considered same-origin if their schemes match, even if
310 // their host and port are different.
311 auto origin_b = this->CreateOriginFromString("non-standard://b.com:90");
312 EXPECT_TRUE(this->IsSameOrigin(origin_a, origin_b));
313
314 // URLs are not considered same-origin if their schemes don't match,
315 // even if their host and port are same.
316 auto another_origin_a =
317 this->CreateOriginFromString("another-non-standard://a.com:80");
318 EXPECT_FALSE(this->IsSameOrigin(origin_a, another_origin_a));
319 }
320
321 // Standard scheme cases.
322 {
323 // Ensure that the behavior of a standard URL is preserved.
324 auto origin_a = this->CreateOriginFromString("https://a.com:80");
325 EXPECT_EQ(this->GetHost(origin_a), "a.com");
326 EXPECT_EQ(this->GetPort(origin_a), 80);
327 EXPECT_EQ(this->Serialize(origin_a), "https://a.com:80");
328 EXPECT_FALSE(this->IsOpaque(origin_a));
329
330 auto origin_b = this->CreateOriginFromString("https://b.com:80");
331 EXPECT_FALSE(this->IsSameOrigin(origin_a, origin_b));
332 }
333 }
334 }
335
TYPED_TEST_P(AbstractOriginTest,OpaqueOriginsFromValidUrls)336 TYPED_TEST_P(AbstractOriginTest, OpaqueOriginsFromValidUrls) {
337 const char* kTestCases[] = {
338 // Built-in noaccess schemes.
339 "data:text/html,Hello!",
340 "javascript:alert(1)",
341 "about:blank",
342
343 // Opaque blob URLs.
344 "blob:null/foo", // blob:null (actually a valid URL)
345 "blob:data:foo", // blob + data (which is nonstandard)
346 "blob:about://blank/", // blob + about (which is nonstandard)
347 "blob:about:blank/", // blob + about (which is nonstandard)
348 "blob:blob:http://www.example.com/guid-goes-here",
349 "blob:filesystem:ws:b/.",
350 "blob:filesystem:ftp://a/b",
351 "blob:blob:file://localhost/foo/bar",
352 };
353
354 for (const char* test_input : kTestCases) {
355 SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
356
357 // Verify that `origin` is opaque not just because `test_input` results is
358 // an invalid URL (because of a typo in the scheme name, or because of a
359 // technicality like having no host in a noaccess-std-with-host: scheme).
360 EXPECT_TRUE(this->IsValidUrl(test_input));
361
362 this->TestUniqueOpaqueOrigin(test_input);
363 }
364 }
365
TYPED_TEST_P(AbstractOriginTest,OpaqueOriginsFromInvalidUrls)366 TYPED_TEST_P(AbstractOriginTest, OpaqueOriginsFromInvalidUrls) {
367 // TODO(lukasza): Consider moving those to GURL/KURL tests that verify what
368 // inputs are parsed as an invalid URL.
369
370 const char* kTestCases[] = {
371 // Invalid file: URLs.
372 "file://example.com:443/etc/passwd", // No port expected.
373
374 // Invalid HTTP URLs.
375 "http",
376 "http:",
377 "http:/",
378 "http://",
379 "http://:",
380 "http://:1",
381 "http::///invalid.example.com/",
382 "http://example.com:65536/", // Port out of range.
383 "http://example.com:-1/", // Port out of range.
384 "http://example.com:18446744073709551616/", // Port = 2^64.
385 "http://example.com:18446744073709551616999/", // Lots of port digits.
386
387 // Invalid filesystem URLs.
388 "filesystem:http://example.com/", // Missing /type/.
389 "filesystem:local:baz./type/",
390 "filesystem:local://hostname/type/",
391 "filesystem:unknown-scheme://hostname/type/",
392 "filesystem:filesystem:http://example.org:88/foo/bar",
393
394 // Invalid IP addresses
395 "http://[]/",
396 "http://[2001:0db8:0000:0000:0000:0000:0000:0000:0001]/", // 9 groups.
397
398 // Unknown scheme without a colon character (":") gives an invalid URL.
399 "unknown-scheme",
400
401 // Standard schemes require a hostname (and result in an opaque origin if
402 // the hostname is missing).
403 "local-std-with-host:",
404 "noaccess-std-with-host:",
405 };
406
407 for (const char* test_input : kTestCases) {
408 SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
409
410 // All testcases here are expected to represent invalid URLs.
411 // an invalid URL (because of a type in scheme name, or because of a
412 // technicality like having no host in a noaccess-std-with-host: scheme).
413 EXPECT_FALSE(this->IsValidUrl(test_input));
414
415 // Invalid URLs should always result in an opaque origin.
416 this->TestUniqueOpaqueOrigin(test_input);
417 }
418 }
419
TYPED_TEST_P(AbstractOriginTest,TupleOrigins)420 TYPED_TEST_P(AbstractOriginTest, TupleOrigins) {
421 struct TestCase {
422 const char* input;
423 SchemeHostPort expected_tuple;
424 } kTestCases[] = {
425 // file: URLs
426 {"file:///etc/passwd", {"file", "", 0}},
427 {"file://example.com/etc/passwd", {"file", "example.com", 0}},
428 {"file:///", {"file", "", 0}},
429 {"file://hostname/C:/dir/file.txt", {"file", "hostname", 0}},
430
431 // HTTP URLs
432 {"http://example.com/", {"http", "example.com", 80}},
433 {"http://example.com:80/", {"http", "example.com", 80}},
434 {"http://example.com:123/", {"http", "example.com", 123}},
435 {"http://example.com:0/", {"http", "example.com", 0}},
436 {"http://example.com:65535/", {"http", "example.com", 65535}},
437 {"https://example.com/", {"https", "example.com", 443}},
438 {"https://example.com:443/", {"https", "example.com", 443}},
439 {"https://example.com:123/", {"https", "example.com", 123}},
440 {"https://example.com:0/", {"https", "example.com", 0}},
441 {"https://example.com:65535/", {"https", "example.com", 65535}},
442 {"http://user:[email protected]/", {"http", "example.com", 80}},
443 {"http://example.com:123/?query", {"http", "example.com", 123}},
444 {"https://example.com/#1234", {"https", "example.com", 443}},
445 {"https://u:[email protected]:123/?query#1234",
446 {"https", "example.com", 123}},
447 {"http://example/", {"http", "example", 80}},
448
449 // Blob URLs.
450 {"blob:http://example.com/guid-goes-here", {"http", "example.com", 80}},
451 {"blob:http://example.com:123/guid-goes-here",
452 {"http", "example.com", 123}},
453 {"blob:https://example.com/guid-goes-here",
454 {"https", "example.com", 443}},
455 {"blob:http://u:[email protected]/guid-goes-here",
456 {"http", "example.com", 80}},
457
458 // Filesystem URLs.
459 {"filesystem:http://example.com/type/", {"http", "example.com", 80}},
460 {"filesystem:http://example.com:123/type/", {"http", "example.com", 123}},
461 {"filesystem:https://example.com/type/", {"https", "example.com", 443}},
462 {"filesystem:https://example.com:123/type/",
463 {"https", "example.com", 123}},
464 {"filesystem:local-std-with-host:baz./type/",
465 {"local-std-with-host", "baz.", 0}},
466
467 // IP Addresses
468 {"http://192.168.9.1/", {"http", "192.168.9.1", 80}},
469 {"http://[2001:db8::1]/", {"http", "[2001:db8::1]", 80}},
470 {"http://[2001:0db8:0000:0000:0000:0000:0000:0001]/",
471 {"http", "[2001:db8::1]", 80}},
472 {"http://1/", {"http", "0.0.0.1", 80}},
473 {"http://1:1/", {"http", "0.0.0.1", 1}},
474 {"http://3232237825/", {"http", "192.168.9.1", 80}},
475
476 // Punycode
477 {"http://☃.net/", {"http", "xn--n3h.net", 80}},
478 {"blob:http://☃.net/", {"http", "xn--n3h.net", 80}},
479 {"local-std-with-host:↑↑↓↓←→←→ba.↑↑↓↓←→←→ba.0.bg",
480 {"local-std-with-host", "xn--ba-rzuadaibfa.xn--ba-rzuadaibfa.0.bg", 0}},
481
482 // Registered URLs
483 {"ftp://example.com/", {"ftp", "example.com", 21}},
484 {"ws://example.com/", {"ws", "example.com", 80}},
485 {"wss://example.com/", {"wss", "example.com", 443}},
486 {"wss://user:[email protected]/", {"wss", "example.com", 443}},
487 };
488
489 for (const TestCase& test : kTestCases) {
490 SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
491
492 // Only valid URLs should translate into valid, non-opaque origins.
493 EXPECT_TRUE(this->IsValidUrl(test.input));
494
495 auto origin = this->CreateOriginFromString(test.input);
496 this->VerifyTupleOriginInvariants(origin, test.expected_tuple);
497 }
498 }
499
TYPED_TEST_P(AbstractOriginTest,CustomSchemes_OpaqueOrigins)500 TYPED_TEST_P(AbstractOriginTest, CustomSchemes_OpaqueOrigins) {
501 const char* kTestCases[] = {
502 // Unknown scheme
503 "unknown-scheme:foo",
504 "unknown-scheme://bar",
505
506 // Unknown scheme that is a prefix or suffix of a registered scheme.
507 "loca:foo",
508 "ocal:foo",
509 "local-suffix:foo",
510 "prefix-local:foo",
511
512 // Custom no-access schemes translate into an opaque origin (just like the
513 // built-in no-access schemes such as about:blank or data:).
514 "noaccess-std-with-host:foo",
515 "noaccess-std-with-host://bar",
516 "noaccess://host",
517 "local-noaccess://host",
518 "local-noaccess-std-with-host://host",
519 };
520
521 for (const char* test_input : kTestCases) {
522 SCOPED_TRACE(testing::Message() << "Test input: " << test_input);
523
524 // Verify that `origin` is opaque not just because `test_input` results is
525 // an invalid URL (because of a typo in the scheme name, or because of a
526 // technicality like having no host in a noaccess-std-with-host: scheme).
527 EXPECT_TRUE(this->IsValidUrl(test_input));
528
529 this->TestUniqueOpaqueOrigin(test_input);
530 }
531 }
532
TYPED_TEST_P(AbstractOriginTest,CustomSchemes_TupleOrigins)533 TYPED_TEST_P(AbstractOriginTest, CustomSchemes_TupleOrigins) {
534 struct TestCase {
535 const char* input;
536 SchemeHostPort expected_tuple;
537 } kTestCases[] = {
538 // Scheme (registered in SetUp()) that's both local and standard.
539 // TODO: Is it really appropriate to do network-host canonicalization of
540 // schemes without ports?
541 {"local-std-with-host:20", {"local-std-with-host", "0.0.0.20", 0}},
542 {"local-std-with-host:20.", {"local-std-with-host", "0.0.0.20", 0}},
543 {"local-std-with-host:foo", {"local-std-with-host", "foo", 0}},
544 {"local-std-with-host://bar:20", {"local-std-with-host", "bar", 0}},
545 {"local-std-with-host:baz.", {"local-std-with-host", "baz.", 0}},
546 {"local-std-with-host:baz..", {"local-std-with-host", "baz..", 0}},
547 {"local-std-with-host:baz..bar", {"local-std-with-host", "baz..bar", 0}},
548 {"local-std-with-host:baz...", {"local-std-with-host", "baz...", 0}},
549
550 // Scheme (registered in SetUp()) that's local but nonstandard. These
551 // always have empty hostnames, but are allowed to be url::Origins.
552 {"local:", {"local", "", 0}},
553 {"local:foo", {"local", "", 0}},
554
555 {"std-with-host://host", {"std-with-host", "host", 0}},
556 {"local-std-with-host://host", {"local-std-with-host", "host", 0}},
557 };
558
559 for (const TestCase& test : kTestCases) {
560 SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
561
562 // Only valid URLs should translate into valid, non-opaque origins.
563 EXPECT_TRUE(this->IsValidUrl(test.input));
564
565 auto origin = this->CreateOriginFromString(test.input);
566 this->VerifyTupleOriginInvariants(origin, test.expected_tuple);
567 }
568 }
569
TYPED_TEST_P(AbstractOriginTest,CustomSchemes_TupleOrigins_StandardCompliantNonSpecialSchemeFlag)570 TYPED_TEST_P(AbstractOriginTest,
571 CustomSchemes_TupleOrigins_StandardCompliantNonSpecialSchemeFlag) {
572 // Manual flag-dependent tests.
573 //
574 // See AbstractOriginTest/CustomSchemes_TupleOrigins, which covers common
575 // test cases.
576 for (bool flag : {false, true}) {
577 // Note: The feature must be set before we construct test cases because
578 // SchemeHostPort's constructor changes its behavior.
579 base::test::ScopedFeatureList scoped_feature_list;
580 if (flag) {
581 scoped_feature_list.InitAndEnableFeature(
582 kStandardCompliantNonSpecialSchemeURLParsing);
583 } else {
584 scoped_feature_list.InitAndDisableFeature(
585 kStandardCompliantNonSpecialSchemeURLParsing);
586 }
587
588 struct TestCase {
589 std::string_view input;
590 SchemeHostPort expected_tuple_when_standard_compliant_flag_off;
591 SchemeHostPort expected_tuple_when_standard_compliant_flag_on;
592 } test_cases[] = {
593 {"local://bar", {"local", "", 0}, {"local", "bar", 0}},
594 {"also-local://bar", {"also-local", "", 0}, {"also-local", "bar", 0}},
595 };
596 for (const TestCase& test : test_cases) {
597 SCOPED_TRACE(testing::Message() << "Test input: " << test.input);
598 EXPECT_TRUE(this->IsValidUrl(test.input));
599 auto origin = this->CreateOriginFromString(test.input);
600 this->VerifyTupleOriginInvariants(
601 origin, flag ? test.expected_tuple_when_standard_compliant_flag_on
602 : test.expected_tuple_when_standard_compliant_flag_off);
603 }
604 }
605 }
606
607 REGISTER_TYPED_TEST_SUITE_P(
608 AbstractOriginTest,
609 NonStandardSchemeWithAndroidWebViewHack,
610 AndroidWebViewHackWithStandardCompliantNonSpecialSchemeURLParsing,
611 OpaqueOriginsFromValidUrls,
612 OpaqueOriginsFromInvalidUrls,
613 TupleOrigins,
614 CustomSchemes_OpaqueOrigins,
615 CustomSchemes_TupleOrigins,
616 CustomSchemes_TupleOrigins_StandardCompliantNonSpecialSchemeFlag);
617
618 } // namespace url
619
620 #endif // URL_ORIGIN_ABSTRACT_TESTS_H_
621