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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/350788890): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "url/scheme_host_port.h"
11
12 #include <stddef.h>
13 #include <stdint.h>
14
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "url/gurl.h"
17 #include "url/url_util.h"
18
19 namespace {
20
21 class SchemeHostPortTest : public testing::Test {
22 public:
23 SchemeHostPortTest() = default;
24
25 SchemeHostPortTest(const SchemeHostPortTest&) = delete;
26 SchemeHostPortTest& operator=(const SchemeHostPortTest&) = delete;
27
28 ~SchemeHostPortTest() override = default;
29
30 private:
31 url::ScopedSchemeRegistryForTests scoped_registry_;
32 };
33
ExpectParsedUrlsEqual(const GURL & a,const GURL & b)34 void ExpectParsedUrlsEqual(const GURL& a, const GURL& b) {
35 EXPECT_EQ(a, b);
36 const url::Parsed& a_parsed = a.parsed_for_possibly_invalid_spec();
37 const url::Parsed& b_parsed = b.parsed_for_possibly_invalid_spec();
38 EXPECT_EQ(a_parsed.scheme.begin, b_parsed.scheme.begin);
39 EXPECT_EQ(a_parsed.scheme.len, b_parsed.scheme.len);
40 EXPECT_EQ(a_parsed.username.begin, b_parsed.username.begin);
41 EXPECT_EQ(a_parsed.username.len, b_parsed.username.len);
42 EXPECT_EQ(a_parsed.password.begin, b_parsed.password.begin);
43 EXPECT_EQ(a_parsed.password.len, b_parsed.password.len);
44 EXPECT_EQ(a_parsed.host.begin, b_parsed.host.begin);
45 EXPECT_EQ(a_parsed.host.len, b_parsed.host.len);
46 EXPECT_EQ(a_parsed.port.begin, b_parsed.port.begin);
47 EXPECT_EQ(a_parsed.port.len, b_parsed.port.len);
48 EXPECT_EQ(a_parsed.path.begin, b_parsed.path.begin);
49 EXPECT_EQ(a_parsed.path.len, b_parsed.path.len);
50 EXPECT_EQ(a_parsed.query.begin, b_parsed.query.begin);
51 EXPECT_EQ(a_parsed.query.len, b_parsed.query.len);
52 EXPECT_EQ(a_parsed.ref.begin, b_parsed.ref.begin);
53 EXPECT_EQ(a_parsed.ref.len, b_parsed.ref.len);
54 }
55
TEST_F(SchemeHostPortTest,Invalid)56 TEST_F(SchemeHostPortTest, Invalid) {
57 url::SchemeHostPort invalid;
58 EXPECT_EQ("", invalid.scheme());
59 EXPECT_EQ("", invalid.host());
60 EXPECT_EQ(0, invalid.port());
61 EXPECT_FALSE(invalid.IsValid());
62 EXPECT_EQ(invalid, invalid);
63
64 const char* urls[] = {
65 // about:, data:, javascript: and other no-access schemes translate into
66 // an invalid SchemeHostPort
67 "about:blank", "about:blank#ref", "about:blank?query=123", "about:srcdoc",
68 "about:srcdoc#ref", "about:srcdoc?query=123", "data:text/html,Hello!",
69 "javascript:alert(1)",
70
71 // Non-special URLs which don't have an opaque path.
72 "git:/", "git://", "git:///", "git://host/", "git://host/path",
73
74 // GURLs where GURL::is_valid returns false translate into an invalid
75 // SchemeHostPort.
76 "file://example.com:443/etc/passwd", "#!^%!$!&*",
77
78 // These schemes do not follow the generic URL syntax, so make sure we
79 // treat them as invalid (scheme, host, port) tuples (even though such
80 // URLs' _Origin_ might have a (scheme, host, port) tuple, they themselves
81 // do not). This is only *implicitly* checked in the code, by means of
82 // blob schemes not being standard, and filesystem schemes having type
83 // SCHEME_WITHOUT_AUTHORITY. If conditions change such that the implicit
84 // checks no longer hold, this policy should be made explicit.
85 "blob:https://example.com/uuid-goes-here",
86 "filesystem:https://example.com/temporary/yay.png"};
87
88 for (auto* test : urls) {
89 SCOPED_TRACE(test);
90 GURL url(test);
91 url::SchemeHostPort tuple(url);
92 EXPECT_EQ("", tuple.scheme());
93 EXPECT_EQ("", tuple.host());
94 EXPECT_EQ(0, tuple.port());
95 EXPECT_FALSE(tuple.IsValid());
96 EXPECT_EQ(tuple, tuple);
97 EXPECT_EQ(tuple, invalid);
98 EXPECT_EQ(invalid, tuple);
99 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
100 }
101 }
102
TEST_F(SchemeHostPortTest,ExplicitConstruction)103 TEST_F(SchemeHostPortTest, ExplicitConstruction) {
104 struct TestCases {
105 const char* scheme;
106 const char* host;
107 uint16_t port;
108 } cases[] = {
109 {"http", "example.com", 80},
110 {"http", "example.com", 123},
111 {"http", "example.com", 0}, // 0 is a valid port for http.
112 {"https", "example.com", 443},
113 {"https", "example.com", 123},
114 {"file", "", 0}, // 0 indicates "no port" for file: scheme.
115 {"file", "example.com", 0},
116 };
117
118 for (const auto& test : cases) {
119 SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":"
120 << test.port);
121 url::SchemeHostPort tuple(test.scheme, test.host, test.port);
122 EXPECT_EQ(test.scheme, tuple.scheme());
123 EXPECT_EQ(test.host, tuple.host());
124 EXPECT_EQ(test.port, tuple.port());
125 EXPECT_TRUE(tuple.IsValid());
126 EXPECT_EQ(tuple, tuple);
127 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
128 }
129 }
130
TEST_F(SchemeHostPortTest,InvalidConstruction)131 TEST_F(SchemeHostPortTest, InvalidConstruction) {
132 struct TestCases {
133 const char* scheme;
134 const char* host;
135 uint16_t port;
136 } cases[] = {{"", "", 0},
137 {"data", "", 0},
138 {"blob", "", 0},
139 {"filesystem", "", 0},
140 {"http", "", 80},
141 {"data", "example.com", 80},
142 {"git", "", 0},
143 {"git", "example.com", 80},
144 {"http", "☃.net", 80},
145 {"http\nmore", "example.com", 80},
146 {"http\rmore", "example.com", 80},
147 {"http\n", "example.com", 80},
148 {"http\r", "example.com", 80},
149 {"http", "example.com\nnot-example.com", 80},
150 {"http", "example.com\rnot-example.com", 80},
151 {"http", "example.com\n", 80},
152 {"http", "example.com\r", 80},
153 {"file", "", 80}}; // Can''t have a port for file: scheme.
154
155 for (const auto& test : cases) {
156 SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":"
157 << test.port);
158 url::SchemeHostPort tuple(test.scheme, test.host, test.port);
159 EXPECT_EQ("", tuple.scheme());
160 EXPECT_EQ("", tuple.host());
161 EXPECT_EQ(0, tuple.port());
162 EXPECT_FALSE(tuple.IsValid());
163 EXPECT_EQ(tuple, tuple);
164 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
165 }
166 }
167
TEST_F(SchemeHostPortTest,InvalidConstructionWithEmbeddedNulls)168 TEST_F(SchemeHostPortTest, InvalidConstructionWithEmbeddedNulls) {
169 struct TestCases {
170 const char* scheme;
171 size_t scheme_length;
172 const char* host;
173 size_t host_length;
174 uint16_t port;
175 } cases[] = {{"http\0more", 9, "example.com", 11, 80},
176 {"http\0", 5, "example.com", 11, 80},
177 {"\0http", 5, "example.com", 11, 80},
178 {"http", 4, "example.com\0not-example.com", 27, 80},
179 {"http", 4, "example.com\0", 12, 80},
180 {"http", 4, "\0example.com", 12, 80}};
181
182 for (const auto& test : cases) {
183 SCOPED_TRACE(testing::Message() << test.scheme << "://" << test.host << ":"
184 << test.port);
185 url::SchemeHostPort tuple(std::string(test.scheme, test.scheme_length),
186 std::string(test.host, test.host_length),
187 test.port);
188 EXPECT_EQ("", tuple.scheme());
189 EXPECT_EQ("", tuple.host());
190 EXPECT_EQ(0, tuple.port());
191 EXPECT_FALSE(tuple.IsValid());
192 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
193 }
194 }
195
TEST_F(SchemeHostPortTest,GURLConstruction)196 TEST_F(SchemeHostPortTest, GURLConstruction) {
197 struct TestCases {
198 const char* url;
199 const char* scheme;
200 const char* host;
201 uint16_t port;
202 } cases[] = {
203 {"http://192.168.9.1/", "http", "192.168.9.1", 80},
204 {"http://[2001:db8::1]/", "http", "[2001:db8::1]", 80},
205 {"http://☃.net/", "http", "xn--n3h.net", 80},
206 {"http://example.com/", "http", "example.com", 80},
207 {"http://example.com:123/", "http", "example.com", 123},
208 {"https://example.com/", "https", "example.com", 443},
209 {"https://example.com:123/", "https", "example.com", 123},
210 {"file:///etc/passwd", "file", "", 0},
211 {"file://example.com/etc/passwd", "file", "example.com", 0},
212 {"http://u:p@example.com/", "http", "example.com", 80},
213 {"http://u:p@example.com/path", "http", "example.com", 80},
214 {"http://u:p@example.com/path?123", "http", "example.com", 80},
215 {"http://u:p@example.com/path?123#hash", "http", "example.com", 80},
216 };
217
218 for (const auto& test : cases) {
219 SCOPED_TRACE(test.url);
220 GURL url(test.url);
221 EXPECT_TRUE(url.is_valid());
222 url::SchemeHostPort tuple(url);
223 EXPECT_EQ(test.scheme, tuple.scheme());
224 EXPECT_EQ(test.host, tuple.host());
225 EXPECT_EQ(test.port, tuple.port());
226 EXPECT_TRUE(tuple.IsValid());
227 EXPECT_EQ(tuple, tuple);
228 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
229 }
230 }
231
TEST_F(SchemeHostPortTest,Serialization)232 TEST_F(SchemeHostPortTest, Serialization) {
233 struct TestCases {
234 const char* url;
235 const char* expected;
236 } cases[] = {
237 {"http://192.168.9.1/", "http://192.168.9.1"},
238 {"http://[2001:db8::1]/", "http://[2001:db8::1]"},
239 {"http://☃.net/", "http://xn--n3h.net"},
240 {"http://example.com/", "http://example.com"},
241 {"http://example.com:123/", "http://example.com:123"},
242 {"https://example.com/", "https://example.com"},
243 {"https://example.com:123/", "https://example.com:123"},
244 {"file:///etc/passwd", "file://"},
245 {"file://example.com/etc/passwd", "file://example.com"},
246 {"https://example.com:0/", "https://example.com:0"},
247 };
248
249 for (const auto& test : cases) {
250 SCOPED_TRACE(test.url);
251 GURL url(test.url);
252 url::SchemeHostPort tuple(url);
253 EXPECT_EQ(test.expected, tuple.Serialize());
254 ExpectParsedUrlsEqual(GURL(tuple.Serialize()), tuple.GetURL());
255 }
256 }
257
TEST_F(SchemeHostPortTest,Comparison)258 TEST_F(SchemeHostPortTest, Comparison) {
259 // These tuples are arranged in increasing order:
260 struct SchemeHostPorts {
261 const char* scheme;
262 const char* host;
263 uint16_t port;
264 } tuples[] = {
265 {"http", "a", 80},
266 {"http", "b", 80},
267 {"https", "a", 80},
268 {"https", "b", 80},
269 {"http", "a", 81},
270 {"http", "b", 81},
271 {"https", "a", 81},
272 {"https", "b", 81},
273 };
274
275 for (size_t i = 0; i < std::size(tuples); i++) {
276 url::SchemeHostPort current(tuples[i].scheme, tuples[i].host,
277 tuples[i].port);
278 for (size_t j = i; j < std::size(tuples); j++) {
279 url::SchemeHostPort to_compare(tuples[j].scheme, tuples[j].host,
280 tuples[j].port);
281 EXPECT_EQ(i < j, current < to_compare) << i << " < " << j;
282 EXPECT_EQ(j < i, to_compare < current) << j << " < " << i;
283 }
284 }
285 }
286
287 // Some schemes have optional authority. Make sure that GURL conversion from
288 // SchemeHostPort is not opinionated in that regard. For more info, See
289 // crbug.com/820194, where we considered all SchemeHostPorts with
290 // SCHEME_WITH_HOST (i.e., without ports) as valid with empty hosts, even though
291 // most are not (e.g. chrome URLs).
TEST_F(SchemeHostPortTest,EmptyHostGurlConversion)292 TEST_F(SchemeHostPortTest, EmptyHostGurlConversion) {
293 url::AddStandardScheme("chrome", url::SCHEME_WITH_HOST);
294
295 GURL chrome_url("chrome:");
296 EXPECT_FALSE(chrome_url.is_valid());
297
298 url::SchemeHostPort chrome_tuple("chrome", "", 0);
299 EXPECT_FALSE(chrome_tuple.GetURL().is_valid());
300 ExpectParsedUrlsEqual(GURL(chrome_tuple.Serialize()), chrome_tuple.GetURL());
301 ExpectParsedUrlsEqual(chrome_url, chrome_tuple.GetURL());
302 }
303
304 } // namespace url
305