1 // Copyright 2013 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.
7 #include "base/macros.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "url/third_party/mozilla/url_parse.h"
10 #include "url/url_canon.h"
11 #include "url/url_canon_stdstring.h"
12 #include "url/url_test_utils.h"
13 #include "url/url_util.h"
17 class URLUtilTest : public testing::Test {
19 URLUtilTest() = default;
20 ~URLUtilTest() override {
21 // Reset any added schemes.
26 DISALLOW_COPY_AND_ASSIGN(URLUtilTest);
29 TEST_F(URLUtilTest, FindAndCompareScheme) {
30 Component found_scheme;
32 // Simple case where the scheme is found and matches.
33 const char kStr1[] = "http://www.com/";
34 EXPECT_TRUE(FindAndCompareScheme(
35 kStr1, static_cast<int>(strlen(kStr1)), "http", NULL));
36 EXPECT_TRUE(FindAndCompareScheme(
37 kStr1, static_cast<int>(strlen(kStr1)), "http", &found_scheme));
38 EXPECT_TRUE(found_scheme == Component(0, 4));
40 // A case where the scheme is found and doesn't match.
41 EXPECT_FALSE(FindAndCompareScheme(
42 kStr1, static_cast<int>(strlen(kStr1)), "https", &found_scheme));
43 EXPECT_TRUE(found_scheme == Component(0, 4));
45 // A case where there is no scheme.
46 const char kStr2[] = "httpfoobar";
47 EXPECT_FALSE(FindAndCompareScheme(
48 kStr2, static_cast<int>(strlen(kStr2)), "http", &found_scheme));
49 EXPECT_TRUE(found_scheme == Component());
51 // When there is an empty scheme, it should match the empty scheme.
52 const char kStr3[] = ":foo.com/";
53 EXPECT_TRUE(FindAndCompareScheme(
54 kStr3, static_cast<int>(strlen(kStr3)), "", &found_scheme));
55 EXPECT_TRUE(found_scheme == Component(0, 0));
57 // But when there is no scheme, it should fail.
58 EXPECT_FALSE(FindAndCompareScheme("", 0, "", &found_scheme));
59 EXPECT_TRUE(found_scheme == Component());
61 // When there is a whitespace char in scheme, it should canonicalize the URL
63 const char whtspc_str[] = " \r\n\tjav\ra\nscri\tpt:alert(1)";
64 EXPECT_TRUE(FindAndCompareScheme(whtspc_str,
65 static_cast<int>(strlen(whtspc_str)),
66 "javascript", &found_scheme));
67 EXPECT_TRUE(found_scheme == Component(1, 10));
69 // Control characters should be stripped out on the ends, and kept in the
71 const char ctrl_str[] = "\02jav\02scr\03ipt:alert(1)";
72 EXPECT_FALSE(FindAndCompareScheme(ctrl_str,
73 static_cast<int>(strlen(ctrl_str)),
74 "javascript", &found_scheme));
75 EXPECT_TRUE(found_scheme == Component(1, 11));
78 TEST_F(URLUtilTest, IsStandard) {
79 const char kHTTPScheme[] = "http";
80 EXPECT_TRUE(IsStandard(kHTTPScheme, Component(0, strlen(kHTTPScheme))));
82 const char kFooScheme[] = "foo";
83 EXPECT_FALSE(IsStandard(kFooScheme, Component(0, strlen(kFooScheme))));
86 TEST_F(URLUtilTest, IsReferrerScheme) {
87 const char kHTTPScheme[] = "http";
88 EXPECT_TRUE(IsReferrerScheme(kHTTPScheme, Component(0, strlen(kHTTPScheme))));
90 const char kFooScheme[] = "foo";
91 EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
94 TEST_F(URLUtilTest, AddReferrerScheme) {
95 const char kFooScheme[] = "foo";
96 EXPECT_FALSE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
97 AddReferrerScheme(kFooScheme, url::SCHEME_WITH_HOST);
98 EXPECT_TRUE(IsReferrerScheme(kFooScheme, Component(0, strlen(kFooScheme))));
101 TEST_F(URLUtilTest, GetStandardSchemeType) {
102 url::SchemeType scheme_type;
104 const char kHTTPScheme[] = "http";
105 scheme_type = url::SCHEME_WITHOUT_AUTHORITY;
106 EXPECT_TRUE(GetStandardSchemeType(kHTTPScheme,
107 Component(0, strlen(kHTTPScheme)),
109 EXPECT_EQ(url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION, scheme_type);
111 const char kFilesystemScheme[] = "filesystem";
112 scheme_type = url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
113 EXPECT_TRUE(GetStandardSchemeType(kFilesystemScheme,
114 Component(0, strlen(kFilesystemScheme)),
116 EXPECT_EQ(url::SCHEME_WITHOUT_AUTHORITY, scheme_type);
118 const char kFooScheme[] = "foo";
119 scheme_type = url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION;
120 EXPECT_FALSE(GetStandardSchemeType(kFooScheme,
121 Component(0, strlen(kFooScheme)),
125 TEST_F(URLUtilTest, ReplaceComponents) {
127 RawCanonOutputT<char> output;
130 // Check that the following calls do not cause crash
131 Replacements<char> replacements;
132 replacements.SetRef("test", Component(0, 4));
133 ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
134 ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
135 replacements.ClearRef();
136 replacements.SetHost("test", Component(0, 4));
137 ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
138 ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
140 replacements.ClearHost();
141 ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
142 ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
143 ReplaceComponents(NULL, 0, parsed, replacements, NULL, &output, &new_parsed);
144 ReplaceComponents("", 0, parsed, replacements, NULL, &output, &new_parsed);
147 static std::string CheckReplaceScheme(const char* base_url,
148 const char* scheme) {
149 // Make sure the input is canonicalized.
150 RawCanonOutput<32> original;
151 Parsed original_parsed;
152 Canonicalize(base_url, strlen(base_url), true, NULL, &original,
155 Replacements<char> replacements;
156 replacements.SetScheme(scheme, Component(0, strlen(scheme)));
158 std::string output_string;
159 StdStringCanonOutput output(&output_string);
160 Parsed output_parsed;
161 ReplaceComponents(original.data(), original.length(), original_parsed,
162 replacements, NULL, &output, &output_parsed);
165 return output_string;
168 TEST_F(URLUtilTest, ReplaceScheme) {
169 EXPECT_EQ("https://google.com/",
170 CheckReplaceScheme("http://google.com/", "https"));
171 EXPECT_EQ("file://google.com/",
172 CheckReplaceScheme("http://google.com/", "file"));
173 EXPECT_EQ("http://home/Build",
174 CheckReplaceScheme("file:///Home/Build", "http"));
175 EXPECT_EQ("javascript:foo",
176 CheckReplaceScheme("about:foo", "javascript"));
177 EXPECT_EQ("://google.com/",
178 CheckReplaceScheme("http://google.com/", ""));
179 EXPECT_EQ("http://google.com/",
180 CheckReplaceScheme("about:google.com", "http"));
181 EXPECT_EQ("http:", CheckReplaceScheme("", "http"));
184 // Magic Windows drive letter behavior when converting to a file URL.
185 EXPECT_EQ("file:///E:/foo/",
186 CheckReplaceScheme("http://localhost/e:foo/", "file"));
189 // This will probably change to "about://google.com/" when we fix
190 // http://crbug.com/160 which should also be an acceptable result.
191 EXPECT_EQ("about://google.com/",
192 CheckReplaceScheme("http://google.com/", "about"));
194 EXPECT_EQ("http://example.com/%20hello%20#%20world",
195 CheckReplaceScheme("myscheme:example.com/ hello # world ", "http"));
198 TEST_F(URLUtilTest, DecodeURLEscapeSequences) {
202 DecodeURLResult result;
204 {"hello, world", "hello, world", DecodeURLResult::kAsciiOnly},
205 {"%01%02%03%04%05%06%07%08%09%0a%0B%0C%0D%0e%0f/",
206 "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0B\x0C\x0D\x0e\x0f/",
207 DecodeURLResult::kAsciiOnly},
208 {"%10%11%12%13%14%15%16%17%18%19%1a%1B%1C%1D%1e%1f/",
209 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1B\x1C\x1D\x1e\x1f/",
210 DecodeURLResult::kAsciiOnly},
211 {"%20%21%22%23%24%25%26%27%28%29%2a%2B%2C%2D%2e%2f/",
212 " !\"#$%&'()*+,-.//", DecodeURLResult::kAsciiOnly},
213 {"%30%31%32%33%34%35%36%37%38%39%3a%3B%3C%3D%3e%3f/", "0123456789:;<=>?/",
214 DecodeURLResult::kAsciiOnly},
215 {"%40%41%42%43%44%45%46%47%48%49%4a%4B%4C%4D%4e%4f/", "@ABCDEFGHIJKLMNO/",
216 DecodeURLResult::kAsciiOnly},
217 {"%50%51%52%53%54%55%56%57%58%59%5a%5B%5C%5D%5e%5f/",
218 "PQRSTUVWXYZ[\\]^_/", DecodeURLResult::kAsciiOnly},
219 {"%60%61%62%63%64%65%66%67%68%69%6a%6B%6C%6D%6e%6f/", "`abcdefghijklmno/",
220 DecodeURLResult::kAsciiOnly},
221 {"%70%71%72%73%74%75%76%77%78%79%7a%7B%7C%7D%7e%7f/",
222 "pqrstuvwxyz{|}~\x7f/", DecodeURLResult::kAsciiOnly},
223 // Test un-UTF-8-ization.
224 {"%e4%bd%a0%e5%a5%bd", "\xe4\xbd\xa0\xe5\xa5\xbd",
225 DecodeURLResult::kUTF8},
228 for (size_t i = 0; i < arraysize(decode_cases); i++) {
229 const char* input = decode_cases[i].input;
230 RawCanonOutputT<base::char16> output;
231 EXPECT_EQ(decode_cases[i].result,
232 DecodeURLEscapeSequences(input, strlen(input), &output));
233 EXPECT_EQ(decode_cases[i].output,
234 base::UTF16ToUTF8(base::string16(output.data(),
238 // Our decode should decode %00
239 const char zero_input[] = "%00";
240 RawCanonOutputT<base::char16> zero_output;
241 DecodeURLEscapeSequences(zero_input, strlen(zero_input), &zero_output);
242 EXPECT_NE("%00", base::UTF16ToUTF8(
243 base::string16(zero_output.data(), zero_output.length())));
245 // Test the error behavior for invalid UTF-8.
247 const char invalid_input[] = "%e4%a0%e5%a5%bd";
248 const base::char16 invalid_expected[4] = {0x00e4, 0x00a0, 0x597d, 0};
249 RawCanonOutputT<base::char16> invalid_output;
250 EXPECT_EQ(DecodeURLResult::kMixed,
251 DecodeURLEscapeSequences(invalid_input, strlen(invalid_input),
253 EXPECT_EQ(base::string16(invalid_expected),
254 base::string16(invalid_output.data(), invalid_output.length()));
257 const char invalid_input[] = "%e4%a0%e5%bd";
258 const base::char16 invalid_expected[5] = {0x00e4, 0x00a0, 0x00e5, 0x00bd,
260 RawCanonOutputT<base::char16> invalid_output;
261 EXPECT_EQ(DecodeURLResult::kIsomorphic,
262 DecodeURLEscapeSequences(invalid_input, strlen(invalid_input),
264 EXPECT_EQ(base::string16(invalid_expected),
265 base::string16(invalid_output.data(), invalid_output.length()));
269 TEST_F(URLUtilTest, TestEncodeURIComponent) {
274 {"hello, world", "hello%2C%20world"},
275 {"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F",
276 "%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F"},
277 {"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F",
278 "%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F"},
279 {" !\"#$%&'()*+,-./",
280 "%20!%22%23%24%25%26%27()*%2B%2C-.%2F"},
282 "0123456789%3A%3B%3C%3D%3E%3F"},
284 "%40ABCDEFGHIJKLMNO"},
285 {"PQRSTUVWXYZ[\\]^_",
286 "PQRSTUVWXYZ%5B%5C%5D%5E_"},
288 "%60abcdefghijklmno"},
289 {"pqrstuvwxyz{|}~\x7f",
290 "pqrstuvwxyz%7B%7C%7D~%7F"},
293 for (size_t i = 0; i < arraysize(encode_cases); i++) {
294 const char* input = encode_cases[i].input;
295 RawCanonOutputT<char> buffer;
296 EncodeURIComponent(input, strlen(input), &buffer);
297 std::string output(buffer.data(), buffer.length());
298 EXPECT_EQ(encode_cases[i].output, output);
302 TEST_F(URLUtilTest, TestResolveRelativeWithNonStandardBase) {
303 // This tests non-standard (in the sense that IsStandard() == false)
304 // hierarchical schemes.
305 struct ResolveRelativeCase {
310 } resolve_non_standard_cases[] = {
311 // Resolving a relative path against a non-hierarchical URL should fail.
312 {"scheme:opaque_data", "/path", false, ""},
313 // Resolving a relative path against a non-standard authority-based base
314 // URL doesn't alter the authority section.
315 {"scheme://Authority/", "../path", true, "scheme://Authority/path"},
316 // A non-standard hierarchical base is resolved with path URL
317 // canonicalization rules.
318 {"data:/Blah:Blah/", "file.html", true, "data:/Blah:Blah/file.html"},
319 {"data:/Path/../part/part2", "file.html", true,
320 "data:/Path/../part/file.html"},
321 {"data://text/html,payload", "//user:pass@host:33////payload22", true,
322 "data://user:pass@host:33////payload22"},
323 // Path URL canonicalization rules also apply to non-standard authority-
325 {"custom://Authority/", "file.html", true,
326 "custom://Authority/file.html"},
327 {"custom://Authority/", "other://Auth/", true, "other://Auth/"},
328 {"custom://Authority/", "../../file.html", true,
329 "custom://Authority/file.html"},
330 {"custom://Authority/path/", "file.html", true,
331 "custom://Authority/path/file.html"},
332 {"custom://Authority:NoCanon/path/", "file.html", true,
333 "custom://Authority:NoCanon/path/file.html"},
334 // It's still possible to get an invalid path URL.
335 {"custom://Invalid:!#Auth/", "file.html", false, ""},
336 // A path with an authority section gets canonicalized under standard URL
337 // rules, even though the base was non-standard.
338 {"content://content.Provider/", "//other.Provider", true,
339 "content://other.provider/"},
341 // Resolving an absolute URL doesn't cause canonicalization of the
343 {"about:blank", "custom://Authority", true, "custom://Authority"},
344 // Fragment URLs can be resolved against a non-standard base.
345 {"scheme://Authority/path", "#fragment", true,
346 "scheme://Authority/path#fragment"},
347 {"scheme://Authority/", "#fragment", true,
348 "scheme://Authority/#fragment"},
349 // Resolving should fail if the base URL is authority-based but is
350 // missing a path component (the '/' at the end).
351 {"scheme://Authority", "path", false, ""},
352 // Test resolving a fragment (only) against any kind of base-URL.
353 {"about:blank", "#id42", true, "about:blank#id42"},
354 {"about:blank", " #id42", true, "about:blank#id42"},
355 {"about:blank#oldfrag", "#newfrag", true, "about:blank#newfrag"},
356 // A surprising side effect of allowing fragments to resolve against
357 // any URL scheme is we might break javascript: URLs by doing so...
358 {"javascript:alert('foo#bar')", "#badfrag", true,
359 "javascript:alert('foo#badfrag"},
360 // In this case, the backslashes will not be canonicalized because it's a
361 // non-standard URL, but they will be treated as a path separators,
362 // giving the base URL here a path of "\".
364 // The result here is somewhat arbitrary. One could argue it should be
365 // either "aaa://a\" or "aaa://a/" since the path is being replaced with
366 // the "current directory". But in the context of resolving on data URLs,
367 // adding the requested dot doesn't seem wrong either.
368 {"aaa://a\\", "aaa:.", true, "aaa://a\\."}};
370 for (size_t i = 0; i < arraysize(resolve_non_standard_cases); i++) {
371 const ResolveRelativeCase& test_data = resolve_non_standard_cases[i];
373 ParsePathURL(test_data.base, strlen(test_data.base), false, &base_parsed);
375 std::string resolved;
376 StdStringCanonOutput output(&resolved);
377 Parsed resolved_parsed;
378 bool valid = ResolveRelative(test_data.base, strlen(test_data.base),
379 base_parsed, test_data.rel,
380 strlen(test_data.rel), NULL, &output,
384 EXPECT_EQ(test_data.is_valid, valid) << i;
385 if (test_data.is_valid && valid)
386 EXPECT_EQ(test_data.out, resolved) << i;
390 TEST_F(URLUtilTest, TestNoRefComponent) {
391 // The hash-mark must be ignored when mailto: scheme is parsed,
392 // even if the URL has a base and relative part.
393 const char* base = "mailto://to/";
394 const char* rel = "any#body";
397 ParsePathURL(base, strlen(base), false, &base_parsed);
399 std::string resolved;
400 StdStringCanonOutput output(&resolved);
401 Parsed resolved_parsed;
403 bool valid = ResolveRelative(base, strlen(base),
405 strlen(rel), NULL, &output,
408 EXPECT_FALSE(resolved_parsed.ref.is_valid());
411 TEST_F(URLUtilTest, PotentiallyDanglingMarkup) {
412 struct ResolveRelativeCase {
415 bool potentially_dangling_markup;
418 {"https://example.com/", "/path<", false, "https://example.com/path%3C"},
419 {"https://example.com/", "\n/path<", true, "https://example.com/path%3C"},
420 {"https://example.com/", "\r/path<", true, "https://example.com/path%3C"},
421 {"https://example.com/", "\t/path<", true, "https://example.com/path%3C"},
422 {"https://example.com/", "/pa\nth<", true, "https://example.com/path%3C"},
423 {"https://example.com/", "/pa\rth<", true, "https://example.com/path%3C"},
424 {"https://example.com/", "/pa\tth<", true, "https://example.com/path%3C"},
425 {"https://example.com/", "/path\n<", true, "https://example.com/path%3C"},
426 {"https://example.com/", "/path\r<", true, "https://example.com/path%3C"},
427 {"https://example.com/", "/path\r<", true, "https://example.com/path%3C"},
428 {"https://example.com/", "\n/<path", true, "https://example.com/%3Cpath"},
429 {"https://example.com/", "\r/<path", true, "https://example.com/%3Cpath"},
430 {"https://example.com/", "\t/<path", true, "https://example.com/%3Cpath"},
431 {"https://example.com/", "/<pa\nth", true, "https://example.com/%3Cpath"},
432 {"https://example.com/", "/<pa\rth", true, "https://example.com/%3Cpath"},
433 {"https://example.com/", "/<pa\tth", true, "https://example.com/%3Cpath"},
434 {"https://example.com/", "/<path\n", true, "https://example.com/%3Cpath"},
435 {"https://example.com/", "/<path\r", true, "https://example.com/%3Cpath"},
436 {"https://example.com/", "/<path\r", true, "https://example.com/%3Cpath"},
439 for (const auto& test : cases) {
440 SCOPED_TRACE(::testing::Message() << test.base << ", " << test.rel);
442 ParseStandardURL(test.base, strlen(test.base), &base_parsed);
444 std::string resolved;
445 StdStringCanonOutput output(&resolved);
446 Parsed resolved_parsed;
448 ResolveRelative(test.base, strlen(test.base), base_parsed, test.rel,
449 strlen(test.rel), NULL, &output, &resolved_parsed);
453 EXPECT_EQ(test.potentially_dangling_markup,
454 resolved_parsed.potentially_dangling_markup);
455 EXPECT_EQ(test.out, resolved);
459 TEST_F(URLUtilTest, TestDomainIs) {
461 const char* canonicalized_host;
462 const char* lower_ascii_domain;
463 bool expected_domain_is;
465 {"google.com", "google.com", true},
466 {"www.google.com", "google.com", true}, // Subdomain is ignored.
467 {"www.google.com.cn", "google.com", false}, // Different TLD.
468 {"www.google.comm", "google.com", false},
469 {"www.iamnotgoogle.com", "google.com", false}, // Different hostname.
470 {"www.google.com", "Google.com", false}, // The input is not lower-cased.
472 // If the host ends with a dot, it matches domains with or without a dot.
473 {"www.google.com.", "google.com", true},
474 {"www.google.com.", "google.com.", true},
475 {"www.google.com.", ".com", true},
476 {"www.google.com.", ".com.", true},
478 // But, if the host doesn't end with a dot and the input domain does, then
479 // it's considered to not match.
480 {"www.google.com", "google.com.", false},
482 // If the host ends with two dots, it doesn't match.
483 {"www.google.com..", "google.com", false},
486 {"www.google.com", "", false},
487 {"", "www.google.com", false},
491 for (const auto& test_case : kTestCases) {
492 SCOPED_TRACE(testing::Message() << "(host, domain): ("
493 << test_case.canonicalized_host << ", "
494 << test_case.lower_ascii_domain << ")");
497 test_case.expected_domain_is,
498 DomainIs(test_case.canonicalized_host, test_case.lower_ascii_domain));