xref: /aosp_15_r20/external/cronet/url/origin_abstract_tests.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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