1 // Copyright 2013-2014 The rust-url developers.
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8
9 //! Unit tests
10
11 use std::borrow::Cow;
12 use std::cell::{Cell, RefCell};
13 use std::net::{Ipv4Addr, Ipv6Addr};
14 use std::path::{Path, PathBuf};
15 use url::{form_urlencoded, Host, Origin, Url};
16
17 #[test]
size()18 fn size() {
19 use std::mem::size_of;
20 assert_eq!(size_of::<Url>(), size_of::<Option<Url>>());
21 }
22
23 #[test]
test_relative()24 fn test_relative() {
25 let base: Url = "sc://%C3%B1".parse().unwrap();
26 let url = base.join("/resources/testharness.js").unwrap();
27 assert_eq!(url.as_str(), "sc://%C3%B1/resources/testharness.js");
28 }
29
30 #[test]
test_relative_empty()31 fn test_relative_empty() {
32 let base: Url = "sc://%C3%B1".parse().unwrap();
33 let url = base.join("").unwrap();
34 assert_eq!(url.as_str(), "sc://%C3%B1");
35 }
36
37 #[test]
test_set_empty_host()38 fn test_set_empty_host() {
39 let mut base: Url = "moz://foo:bar@servo/baz".parse().unwrap();
40 base.set_username("").unwrap();
41 assert_eq!(base.as_str(), "moz://:bar@servo/baz");
42 base.set_host(None).unwrap();
43 assert_eq!(base.as_str(), "moz:/baz");
44 base.set_host(Some("servo")).unwrap();
45 assert_eq!(base.as_str(), "moz://servo/baz");
46
47 let mut base: Url = "file://server/share/foo/bar".parse().unwrap();
48 base.set_host(None).unwrap();
49 assert_eq!(base.as_str(), "file:///share/foo/bar");
50
51 let mut base: Url = "file://server/share/foo/bar".parse().unwrap();
52 base.set_host(Some("foo")).unwrap();
53 assert_eq!(base.as_str(), "file://foo/share/foo/bar");
54 }
55
56 #[test]
test_set_empty_hostname()57 fn test_set_empty_hostname() {
58 use url::quirks;
59 let mut base: Url = "moz://foo@servo/baz".parse().unwrap();
60 assert!(
61 quirks::set_hostname(&mut base, "").is_err(),
62 "setting an empty hostname to a url with a username should fail"
63 );
64 base = "moz://:pass@servo/baz".parse().unwrap();
65 assert!(
66 quirks::set_hostname(&mut base, "").is_err(),
67 "setting an empty hostname to a url with a password should fail"
68 );
69 base = "moz://servo/baz".parse().unwrap();
70 quirks::set_hostname(&mut base, "").unwrap();
71 assert_eq!(base.as_str(), "moz:///baz");
72 }
73
74 macro_rules! assert_from_file_path {
75 ($path: expr) => {
76 assert_from_file_path!($path, $path)
77 };
78 ($path: expr, $url_path: expr) => {{
79 let url = Url::from_file_path(Path::new($path)).unwrap();
80 assert_eq!(url.host(), None);
81 assert_eq!(url.path(), $url_path);
82 assert_eq!(url.to_file_path(), Ok(PathBuf::from($path)));
83 }};
84 }
85
86 #[test]
new_file_paths()87 fn new_file_paths() {
88 if cfg!(unix) {
89 assert_eq!(Url::from_file_path(Path::new("relative")), Err(()));
90 assert_eq!(Url::from_file_path(Path::new("../relative")), Err(()));
91 }
92 if cfg!(windows) {
93 assert_eq!(Url::from_file_path(Path::new("relative")), Err(()));
94 assert_eq!(Url::from_file_path(Path::new(r"..\relative")), Err(()));
95 assert_eq!(Url::from_file_path(Path::new(r"\drive-relative")), Err(()));
96 assert_eq!(Url::from_file_path(Path::new(r"\\ucn\")), Err(()));
97 }
98
99 if cfg!(unix) {
100 assert_from_file_path!("/foo/bar");
101 assert_from_file_path!("/foo/ba\0r", "/foo/ba%00r");
102 assert_from_file_path!("/foo/ba%00r", "/foo/ba%2500r");
103 }
104 }
105
106 #[test]
107 #[cfg(unix)]
new_path_bad_utf8()108 fn new_path_bad_utf8() {
109 use std::ffi::OsStr;
110 use std::os::unix::prelude::*;
111
112 let url = Url::from_file_path(Path::new(OsStr::from_bytes(b"/foo/ba\x80r"))).unwrap();
113 let os_str = OsStr::from_bytes(b"/foo/ba\x80r");
114 assert_eq!(url.to_file_path(), Ok(PathBuf::from(os_str)));
115 }
116
117 #[test]
new_path_windows_fun()118 fn new_path_windows_fun() {
119 if cfg!(windows) {
120 assert_from_file_path!(r"C:\foo\bar", "/C:/foo/bar");
121 assert_from_file_path!("C:\\foo\\ba\0r", "/C:/foo/ba%00r");
122
123 // Invalid UTF-8
124 assert!(Url::parse("file:///C:/foo/ba%80r")
125 .unwrap()
126 .to_file_path()
127 .is_err());
128
129 // test windows canonicalized path
130 let path = PathBuf::from(r"\\?\C:\foo\bar");
131 assert!(Url::from_file_path(path).is_ok());
132
133 // Percent-encoded drive letter
134 let url = Url::parse("file:///C%3A/foo/bar").unwrap();
135 assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
136 }
137 }
138
139 #[test]
new_directory_paths()140 fn new_directory_paths() {
141 if cfg!(unix) {
142 assert_eq!(Url::from_directory_path(Path::new("relative")), Err(()));
143 assert_eq!(Url::from_directory_path(Path::new("../relative")), Err(()));
144
145 let url = Url::from_directory_path(Path::new("/foo/bar")).unwrap();
146 assert_eq!(url.host(), None);
147 assert_eq!(url.path(), "/foo/bar/");
148 }
149 if cfg!(windows) {
150 assert_eq!(Url::from_directory_path(Path::new("relative")), Err(()));
151 assert_eq!(Url::from_directory_path(Path::new(r"..\relative")), Err(()));
152 assert_eq!(
153 Url::from_directory_path(Path::new(r"\drive-relative")),
154 Err(())
155 );
156 assert_eq!(Url::from_directory_path(Path::new(r"\\ucn\")), Err(()));
157
158 let url = Url::from_directory_path(Path::new(r"C:\foo\bar")).unwrap();
159 assert_eq!(url.host(), None);
160 assert_eq!(url.path(), "/C:/foo/bar/");
161 }
162 }
163
164 #[test]
path_backslash_fun()165 fn path_backslash_fun() {
166 let mut special_url = "http://foobar.com".parse::<Url>().unwrap();
167 special_url.path_segments_mut().unwrap().push("foo\\bar");
168 assert_eq!(special_url.as_str(), "http://foobar.com/foo%5Cbar");
169
170 let mut nonspecial_url = "thing://foobar.com".parse::<Url>().unwrap();
171 nonspecial_url.path_segments_mut().unwrap().push("foo\\bar");
172 assert_eq!(nonspecial_url.as_str(), "thing://foobar.com/foo\\bar");
173 }
174
175 #[test]
from_str()176 fn from_str() {
177 assert!("http://testing.com/this".parse::<Url>().is_ok());
178 }
179
180 #[test]
parse_with_params()181 fn parse_with_params() {
182 let url = Url::parse_with_params(
183 "http://testing.com/this?dont=clobberme",
184 &[("lang", "rust")],
185 )
186 .unwrap();
187
188 assert_eq!(
189 url.as_str(),
190 "http://testing.com/this?dont=clobberme&lang=rust"
191 );
192 }
193
194 #[test]
issue_124()195 fn issue_124() {
196 let url: Url = "file:a".parse().unwrap();
197 assert_eq!(url.path(), "/a");
198 let url: Url = "file:...".parse().unwrap();
199 assert_eq!(url.path(), "/...");
200 let url: Url = "file:..".parse().unwrap();
201 assert_eq!(url.path(), "/");
202 }
203
204 #[test]
test_equality()205 fn test_equality() {
206 use std::collections::hash_map::DefaultHasher;
207 use std::hash::{Hash, Hasher};
208
209 fn check_eq(a: &Url, b: &Url) {
210 assert_eq!(a, b);
211
212 let mut h1 = DefaultHasher::new();
213 a.hash(&mut h1);
214 let mut h2 = DefaultHasher::new();
215 b.hash(&mut h2);
216 assert_eq!(h1.finish(), h2.finish());
217 }
218
219 fn url(s: &str) -> Url {
220 let rv = s.parse().unwrap();
221 check_eq(&rv, &rv);
222 rv
223 }
224
225 // Doesn't care if default port is given.
226 let a: Url = url("https://example.com/");
227 let b: Url = url("https://example.com:443/");
228 check_eq(&a, &b);
229
230 // Different ports
231 let a: Url = url("http://example.com/");
232 let b: Url = url("http://example.com:8080/");
233 assert!(a != b, "{:?} != {:?}", a, b);
234
235 // Different scheme
236 let a: Url = url("http://example.com/");
237 let b: Url = url("https://example.com/");
238 assert_ne!(a, b);
239
240 // Different host
241 let a: Url = url("http://foo.com/");
242 let b: Url = url("http://bar.com/");
243 assert_ne!(a, b);
244
245 // Missing path, automatically substituted. Semantically the same.
246 let a: Url = url("http://foo.com");
247 let b: Url = url("http://foo.com/");
248 check_eq(&a, &b);
249 }
250
251 #[test]
host()252 fn host() {
253 fn assert_host(input: &str, host: Host<&str>) {
254 assert_eq!(Url::parse(input).unwrap().host(), Some(host));
255 }
256 assert_host("http://www.mozilla.org", Host::Domain("www.mozilla.org"));
257 assert_host(
258 "http://1.35.33.49",
259 Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)),
260 );
261 assert_host(
262 "http://[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]",
263 Host::Ipv6(Ipv6Addr::new(
264 0x2001, 0x0db8, 0x85a3, 0x08d3, 0x1319, 0x8a2e, 0x0370, 0x7344,
265 )),
266 );
267 assert_host(
268 "http://[::]",
269 Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)),
270 );
271 assert_host(
272 "http://[::1]",
273 Host::Ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
274 );
275 assert_host(
276 "http://0x1.0X23.0x21.061",
277 Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)),
278 );
279 assert_host("http://0x1232131", Host::Ipv4(Ipv4Addr::new(1, 35, 33, 49)));
280 assert_host("http://111", Host::Ipv4(Ipv4Addr::new(0, 0, 0, 111)));
281 assert!(Url::parse("http://1.35.+33.49").is_err());
282 assert!(Url::parse("http://2..2.3").is_err());
283 assert!(Url::parse("http://42.0x1232131").is_err());
284 assert!(Url::parse("http://192.168.0.257").is_err());
285
286 assert_eq!(Host::Domain("foo"), Host::Domain("foo").to_owned());
287 assert_ne!(Host::Domain("foo"), Host::Domain("bar").to_owned());
288 }
289
290 #[test]
host_serialization()291 fn host_serialization() {
292 // libstd’s `Display for Ipv6Addr` serializes 0:0:0:0:0:0:_:_ and 0:0:0:0:0:ffff:_:_
293 // using IPv4-like syntax, as suggested in https://tools.ietf.org/html/rfc5952#section-4
294 // but https://url.spec.whatwg.org/#concept-ipv6-serializer specifies not to.
295
296 // Not [::0.0.0.2] / [::ffff:0.0.0.2]
297 assert_eq!(
298 Url::parse("http://[0::2]").unwrap().host_str(),
299 Some("[::2]")
300 );
301 assert_eq!(
302 Url::parse("http://[0::ffff:0:2]").unwrap().host_str(),
303 Some("[::ffff:0:2]")
304 );
305 }
306
307 #[test]
test_idna()308 fn test_idna() {
309 assert!("http://goșu.ro".parse::<Url>().is_ok());
310 assert_eq!(
311 Url::parse("http://☃.net/").unwrap().host(),
312 Some(Host::Domain("xn--n3h.net"))
313 );
314 assert!("https://r2---sn-huoa-cvhl.googlevideo.com/crossdomain.xml"
315 .parse::<Url>()
316 .is_ok());
317 }
318
319 #[test]
test_serialization()320 fn test_serialization() {
321 let data = [
322 ("http://example.com/", "http://example.com/"),
323 ("http://addslash.com", "http://addslash.com/"),
324 ("http://@emptyuser.com/", "http://emptyuser.com/"),
325 ("http://:@emptypass.com/", "http://emptypass.com/"),
326 ("http://user@user.com/", "http://user@user.com/"),
327 (
328 "http://user:pass@userpass.com/",
329 "http://user:pass@userpass.com/",
330 ),
331 (
332 "http://slashquery.com/path/?q=something",
333 "http://slashquery.com/path/?q=something",
334 ),
335 (
336 "http://noslashquery.com/path?q=something",
337 "http://noslashquery.com/path?q=something",
338 ),
339 ];
340 for &(input, result) in &data {
341 let url = Url::parse(input).unwrap();
342 assert_eq!(url.as_str(), result);
343 }
344 }
345
346 #[test]
test_form_urlencoded()347 fn test_form_urlencoded() {
348 let pairs: &[(Cow<'_, str>, Cow<'_, str>)] = &[
349 ("foo".into(), "é&".into()),
350 ("bar".into(), "".into()),
351 ("foo".into(), "#".into()),
352 ];
353 let encoded = form_urlencoded::Serializer::new(String::new())
354 .extend_pairs(pairs)
355 .finish();
356 assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23");
357 assert_eq!(
358 form_urlencoded::parse(encoded.as_bytes()).collect::<Vec<_>>(),
359 pairs.to_vec()
360 );
361 }
362
363 #[test]
test_form_serialize()364 fn test_form_serialize() {
365 let encoded = form_urlencoded::Serializer::new(String::new())
366 .append_pair("foo", "é&")
367 .append_pair("bar", "")
368 .append_pair("foo", "#")
369 .append_key_only("json")
370 .finish();
371 assert_eq!(encoded, "foo=%C3%A9%26&bar=&foo=%23&json");
372 }
373
374 #[test]
form_urlencoded_encoding_override()375 fn form_urlencoded_encoding_override() {
376 let encoded = form_urlencoded::Serializer::new(String::new())
377 .encoding_override(Some(&|s| s.as_bytes().to_ascii_uppercase().into()))
378 .append_pair("foo", "bar")
379 .append_key_only("xml")
380 .finish();
381 assert_eq!(encoded, "FOO=BAR&XML");
382 }
383
384 #[test]
385 /// https://github.com/servo/rust-url/issues/61
issue_61()386 fn issue_61() {
387 let mut url = Url::parse("http://mozilla.org").unwrap();
388 url.set_scheme("https").unwrap();
389 assert_eq!(url.port(), None);
390 assert_eq!(url.port_or_known_default(), Some(443));
391 url.check_invariants().unwrap();
392 }
393
394 #[test]
395 #[cfg(not(windows))]
396 /// https://github.com/servo/rust-url/issues/197
issue_197()397 fn issue_197() {
398 let mut url = Url::from_file_path("/").expect("Failed to parse path");
399 url.check_invariants().unwrap();
400 assert_eq!(
401 url,
402 Url::parse("file:///").expect("Failed to parse path + protocol")
403 );
404 url.path_segments_mut()
405 .expect("path_segments_mut")
406 .pop_if_empty();
407 }
408
409 #[test]
issue_241()410 fn issue_241() {
411 Url::parse("mailto:").unwrap().cannot_be_a_base();
412 }
413
414 #[test]
415 /// https://github.com/servo/rust-url/issues/222
append_trailing_slash()416 fn append_trailing_slash() {
417 let mut url: Url = "http://localhost:6767/foo/bar?a=b".parse().unwrap();
418 url.check_invariants().unwrap();
419 url.path_segments_mut().unwrap().push("");
420 url.check_invariants().unwrap();
421 assert_eq!(url.to_string(), "http://localhost:6767/foo/bar/?a=b");
422 }
423
424 #[test]
425 /// https://github.com/servo/rust-url/issues/227
extend_query_pairs_then_mutate()426 fn extend_query_pairs_then_mutate() {
427 let mut url: Url = "http://localhost:6767/foo/bar".parse().unwrap();
428 url.query_pairs_mut()
429 .extend_pairs(vec![("auth", "my-token")].into_iter());
430 url.check_invariants().unwrap();
431 assert_eq!(
432 url.to_string(),
433 "http://localhost:6767/foo/bar?auth=my-token"
434 );
435 url.path_segments_mut().unwrap().push("some_other_path");
436 url.check_invariants().unwrap();
437 assert_eq!(
438 url.to_string(),
439 "http://localhost:6767/foo/bar/some_other_path?auth=my-token"
440 );
441 }
442
443 #[test]
444 /// https://github.com/servo/rust-url/issues/222
append_empty_segment_then_mutate()445 fn append_empty_segment_then_mutate() {
446 let mut url: Url = "http://localhost:6767/foo/bar?a=b".parse().unwrap();
447 url.check_invariants().unwrap();
448 url.path_segments_mut().unwrap().push("").pop();
449 url.check_invariants().unwrap();
450 assert_eq!(url.to_string(), "http://localhost:6767/foo/bar?a=b");
451 }
452
453 #[test]
454 /// https://github.com/servo/rust-url/issues/243
test_set_host()455 fn test_set_host() {
456 let mut url = Url::parse("https://example.net/hello").unwrap();
457 url.set_host(Some("foo.com")).unwrap();
458 assert_eq!(url.as_str(), "https://foo.com/hello");
459 assert!(url.set_host(None).is_err());
460 assert_eq!(url.as_str(), "https://foo.com/hello");
461 assert!(url.set_host(Some("")).is_err());
462 assert_eq!(url.as_str(), "https://foo.com/hello");
463
464 let mut url = Url::parse("foobar://example.net/hello").unwrap();
465 url.set_host(None).unwrap();
466 assert_eq!(url.as_str(), "foobar:/hello");
467
468 let mut url = Url::parse("foo://ș").unwrap();
469 assert_eq!(url.as_str(), "foo://%C8%99");
470 url.set_host(Some("goșu.ro")).unwrap();
471 assert_eq!(url.as_str(), "foo://go%C8%99u.ro");
472 }
473
474 #[test]
475 // https://github.com/servo/rust-url/issues/166
test_leading_dots()476 fn test_leading_dots() {
477 assert_eq!(
478 Host::parse(".org").unwrap(),
479 Host::Domain(".org".to_owned())
480 );
481 assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some("."));
482 }
483
484 #[test]
485 /// https://github.com/servo/rust-url/issues/302
test_origin_hash()486 fn test_origin_hash() {
487 use std::collections::hash_map::DefaultHasher;
488 use std::hash::{Hash, Hasher};
489
490 fn hash<T: Hash>(value: &T) -> u64 {
491 let mut hasher = DefaultHasher::new();
492 value.hash(&mut hasher);
493 hasher.finish()
494 }
495
496 let origin = &Url::parse("http://example.net/").unwrap().origin();
497
498 let origins_to_compare = [
499 Url::parse("http://example.net:80/").unwrap().origin(),
500 Url::parse("http://example.net:81/").unwrap().origin(),
501 Url::parse("http://example.net").unwrap().origin(),
502 Url::parse("http://example.net/hello").unwrap().origin(),
503 Url::parse("https://example.net").unwrap().origin(),
504 Url::parse("ftp://example.net").unwrap().origin(),
505 Url::parse("file://example.net").unwrap().origin(),
506 Url::parse("http://user@example.net/").unwrap().origin(),
507 Url::parse("http://user:pass@example.net/")
508 .unwrap()
509 .origin(),
510 ];
511
512 for origin_to_compare in &origins_to_compare {
513 if origin == origin_to_compare {
514 assert_eq!(hash(origin), hash(origin_to_compare));
515 } else {
516 assert_ne!(hash(origin), hash(origin_to_compare));
517 }
518 }
519
520 let opaque_origin = Url::parse("file://example.net").unwrap().origin();
521 let same_opaque_origin = Url::parse("file://example.net").unwrap().origin();
522 let other_opaque_origin = Url::parse("file://other").unwrap().origin();
523
524 assert_ne!(hash(&opaque_origin), hash(&same_opaque_origin));
525 assert_ne!(hash(&opaque_origin), hash(&other_opaque_origin));
526 }
527
528 #[test]
test_origin_blob_equality()529 fn test_origin_blob_equality() {
530 let origin = &Url::parse("http://example.net/").unwrap().origin();
531 let blob_origin = &Url::parse("blob:http://example.net/").unwrap().origin();
532
533 assert_eq!(origin, blob_origin);
534 }
535
536 #[test]
test_origin_opaque()537 fn test_origin_opaque() {
538 assert!(!Origin::new_opaque().is_tuple());
539 assert!(!&Url::parse("blob:malformed//").unwrap().origin().is_tuple())
540 }
541
542 #[test]
test_origin_unicode_serialization()543 fn test_origin_unicode_serialization() {
544 let data = [
545 ("http://.com", "http://.com"),
546 ("ftp://:@.com", "ftp://.com"),
547 ("https://user@.com", "https://.com"),
548 ("http://.:40", "http://.:40"),
549 ];
550 for &(unicode_url, expected_serialization) in &data {
551 let origin = Url::parse(unicode_url).unwrap().origin();
552 assert_eq!(origin.unicode_serialization(), *expected_serialization);
553 }
554
555 let ascii_origins = [
556 Url::parse("http://example.net/").unwrap().origin(),
557 Url::parse("http://example.net:80/").unwrap().origin(),
558 Url::parse("http://example.net:81/").unwrap().origin(),
559 Url::parse("http://example.net").unwrap().origin(),
560 Url::parse("http://example.net/hello").unwrap().origin(),
561 Url::parse("https://example.net").unwrap().origin(),
562 Url::parse("ftp://example.net").unwrap().origin(),
563 Url::parse("file://example.net").unwrap().origin(),
564 Url::parse("http://user@example.net/").unwrap().origin(),
565 Url::parse("http://user:pass@example.net/")
566 .unwrap()
567 .origin(),
568 Url::parse("http://127.0.0.1").unwrap().origin(),
569 ];
570 for ascii_origin in &ascii_origins {
571 assert_eq!(
572 ascii_origin.ascii_serialization(),
573 ascii_origin.unicode_serialization()
574 );
575 }
576 }
577
578 #[test]
test_socket_addrs()579 fn test_socket_addrs() {
580 use std::net::ToSocketAddrs;
581
582 let data = [
583 ("https://127.0.0.1/", "127.0.0.1", 443),
584 ("https://127.0.0.1:9742/", "127.0.0.1", 9742),
585 ("custom-protocol://127.0.0.1:9742/", "127.0.0.1", 9742),
586 ("custom-protocol://127.0.0.1/", "127.0.0.1", 9743),
587 ("https://[::1]/", "::1", 443),
588 ("https://[::1]:9742/", "::1", 9742),
589 ("custom-protocol://[::1]:9742/", "::1", 9742),
590 ("custom-protocol://[::1]/", "::1", 9743),
591 ("https://localhost/", "localhost", 443),
592 ("https://localhost:9742/", "localhost", 9742),
593 ("custom-protocol://localhost:9742/", "localhost", 9742),
594 ("custom-protocol://localhost/", "localhost", 9743),
595 ];
596
597 for (url_string, host, port) in &data {
598 let url = url::Url::parse(url_string).unwrap();
599 let addrs = url
600 .socket_addrs(|| match url.scheme() {
601 "custom-protocol" => Some(9743),
602 _ => None,
603 })
604 .unwrap();
605 assert_eq!(
606 Some(addrs[0]),
607 (*host, *port).to_socket_addrs().unwrap().next()
608 );
609 }
610 }
611
612 #[test]
test_no_base_url()613 fn test_no_base_url() {
614 let mut no_base_url = Url::parse("mailto:test@example.net").unwrap();
615
616 assert!(no_base_url.cannot_be_a_base());
617 assert!(no_base_url.path_segments().is_none());
618 assert!(no_base_url.path_segments_mut().is_err());
619 assert!(no_base_url.set_host(Some("foo")).is_err());
620 assert!(no_base_url
621 .set_ip_host("127.0.0.1".parse().unwrap())
622 .is_err());
623
624 no_base_url.set_path("/foo");
625 assert_eq!(no_base_url.path(), "%2Ffoo");
626 }
627
628 #[test]
test_domain()629 fn test_domain() {
630 let url = Url::parse("https://127.0.0.1/").unwrap();
631 assert_eq!(url.domain(), None);
632
633 let url = Url::parse("mailto:test@example.net").unwrap();
634 assert_eq!(url.domain(), None);
635
636 let url = Url::parse("https://example.com/").unwrap();
637 assert_eq!(url.domain(), Some("example.com"));
638 }
639
640 #[test]
test_query()641 fn test_query() {
642 let url = Url::parse("https://example.com/products?page=2#fragment").unwrap();
643 assert_eq!(url.query(), Some("page=2"));
644 assert_eq!(
645 url.query_pairs().next(),
646 Some((Cow::Borrowed("page"), Cow::Borrowed("2")))
647 );
648
649 let url = Url::parse("https://example.com/products").unwrap();
650 assert!(url.query().is_none());
651 assert_eq!(url.query_pairs().count(), 0);
652
653 let url = Url::parse("https://example.com/?country=español").unwrap();
654 assert_eq!(url.query(), Some("country=espa%C3%B1ol"));
655 assert_eq!(
656 url.query_pairs().next(),
657 Some((Cow::Borrowed("country"), Cow::Borrowed("español")))
658 );
659
660 let url = Url::parse("https://example.com/products?page=2&sort=desc").unwrap();
661 assert_eq!(url.query(), Some("page=2&sort=desc"));
662 let mut pairs = url.query_pairs();
663 assert_eq!(pairs.count(), 2);
664 assert_eq!(
665 pairs.next(),
666 Some((Cow::Borrowed("page"), Cow::Borrowed("2")))
667 );
668 assert_eq!(
669 pairs.next(),
670 Some((Cow::Borrowed("sort"), Cow::Borrowed("desc")))
671 );
672 }
673
674 #[test]
test_fragment()675 fn test_fragment() {
676 let url = Url::parse("https://example.com/#fragment").unwrap();
677 assert_eq!(url.fragment(), Some("fragment"));
678
679 let url = Url::parse("https://example.com/").unwrap();
680 assert_eq!(url.fragment(), None);
681 }
682
683 #[test]
test_set_ip_host()684 fn test_set_ip_host() {
685 let mut url = Url::parse("http://example.com").unwrap();
686
687 url.set_ip_host("127.0.0.1".parse().unwrap()).unwrap();
688 assert_eq!(url.host_str(), Some("127.0.0.1"));
689
690 url.set_ip_host("::1".parse().unwrap()).unwrap();
691 assert_eq!(url.host_str(), Some("[::1]"));
692 }
693
694 #[test]
test_set_href()695 fn test_set_href() {
696 use url::quirks::set_href;
697
698 let mut url = Url::parse("https://existing.url").unwrap();
699
700 assert!(set_href(&mut url, "mal//formed").is_err());
701
702 assert!(set_href(
703 &mut url,
704 "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment"
705 )
706 .is_ok());
707 assert_eq!(
708 url,
709 Url::parse("https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment")
710 .unwrap()
711 );
712 }
713
714 #[test]
test_domain_encoding_quirks()715 fn test_domain_encoding_quirks() {
716 use url::quirks::{domain_to_ascii, domain_to_unicode};
717
718 let data = [
719 ("http://example.com", "", ""),
720 (".", "xn--j28h.xn--938h", "."),
721 ("example.com", "example.com", "example.com"),
722 ("mailto:test@example.net", "", ""),
723 ];
724
725 for url in &data {
726 assert_eq!(domain_to_ascii(url.0), url.1);
727 assert_eq!(domain_to_unicode(url.0), url.2);
728 }
729 }
730
731 #[cfg(feature = "expose_internals")]
732 #[test]
test_expose_internals()733 fn test_expose_internals() {
734 use url::quirks::internal_components;
735 use url::quirks::InternalComponents;
736
737 let url = Url::parse("https://example.com/path/file.ext?key=val&key2=val2#fragment").unwrap();
738 let InternalComponents {
739 scheme_end,
740 username_end,
741 host_start,
742 host_end,
743 port,
744 path_start,
745 query_start,
746 fragment_start,
747 } = internal_components(&url);
748
749 assert_eq!(scheme_end, 5);
750 assert_eq!(username_end, 8);
751 assert_eq!(host_start, 8);
752 assert_eq!(host_end, 19);
753 assert_eq!(port, None);
754 assert_eq!(path_start, 19);
755 assert_eq!(query_start, Some(33));
756 assert_eq!(fragment_start, Some(51));
757 }
758
759 #[test]
test_windows_unc_path()760 fn test_windows_unc_path() {
761 if !cfg!(windows) {
762 return;
763 }
764
765 let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap();
766 assert_eq!(url.as_str(), "file://host/share/path/file.txt");
767
768 let url = Url::from_file_path(Path::new(r"\\höst\share\path\file.txt")).unwrap();
769 assert_eq!(url.as_str(), "file://xn--hst-sna/share/path/file.txt");
770
771 let url = Url::from_file_path(Path::new(r"\\192.168.0.1\share\path\file.txt")).unwrap();
772 assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(192, 168, 0, 1))));
773
774 let path = url.to_file_path().unwrap();
775 assert_eq!(path.to_str(), Some(r"\\192.168.0.1\share\path\file.txt"));
776
777 // Another way to write these:
778 let url = Url::from_file_path(Path::new(r"\\?\UNC\host\share\path\file.txt")).unwrap();
779 assert_eq!(url.as_str(), "file://host/share/path/file.txt");
780
781 // Paths starting with "\\.\" (Local Device Paths) are intentionally not supported.
782 let url = Url::from_file_path(Path::new(r"\\.\some\path\file.txt"));
783 assert!(url.is_err());
784 }
785
786 #[test]
test_syntax_violation_callback()787 fn test_syntax_violation_callback() {
788 use url::SyntaxViolation::*;
789 let violation = Cell::new(None);
790 let url = Url::options()
791 .syntax_violation_callback(Some(&|v| violation.set(Some(v))))
792 .parse("http:////mozilla.org:42")
793 .unwrap();
794 assert_eq!(url.port(), Some(42));
795
796 let v = violation.take().unwrap();
797 assert_eq!(v, ExpectedDoubleSlash);
798 assert_eq!(v.description(), "expected //");
799 assert_eq!(v.to_string(), "expected //");
800 }
801
802 #[test]
test_syntax_violation_callback_lifetimes()803 fn test_syntax_violation_callback_lifetimes() {
804 use url::SyntaxViolation::*;
805 let violation = Cell::new(None);
806 let vfn = |s| violation.set(Some(s));
807
808 let url = Url::options()
809 .syntax_violation_callback(Some(&vfn))
810 .parse("http:////mozilla.org:42")
811 .unwrap();
812 assert_eq!(url.port(), Some(42));
813 assert_eq!(violation.take(), Some(ExpectedDoubleSlash));
814
815 let url = Url::options()
816 .syntax_violation_callback(Some(&vfn))
817 .parse("http://mozilla.org\\path")
818 .unwrap();
819 assert_eq!(url.path(), "/path");
820 assert_eq!(violation.take(), Some(Backslash));
821 }
822
823 #[test]
test_syntax_violation_callback_types()824 fn test_syntax_violation_callback_types() {
825 use url::SyntaxViolation::*;
826
827 let data = [
828 ("http://mozilla.org/\\foo", Backslash, "backslash"),
829 (" http://mozilla.org", C0SpaceIgnored, "leading or trailing control or space character are ignored in URLs"),
830 ("http://user:pass@mozilla.org", EmbeddedCredentials, "embedding authentication information (username or password) in an URL is not recommended"),
831 ("http:///mozilla.org", ExpectedDoubleSlash, "expected //"),
832 ("file:/foo.txt", ExpectedFileDoubleSlash, "expected // after file:"),
833 ("file://mozilla.org/c:/file.txt", FileWithHostAndWindowsDrive, "file: with host and Windows drive letter"),
834 ("http://mozilla.org/^", NonUrlCodePoint, "non-URL code point"),
835 ("http://mozilla.org/#\x000", NullInFragment, "NULL characters are ignored in URL fragment identifiers"),
836 ("http://mozilla.org/%1", PercentDecode, "expected 2 hex digits after %"),
837 ("http://mozilla.org\t/foo", TabOrNewlineIgnored, "tabs or newlines are ignored in URLs"),
838 ("http://user@:pass@mozilla.org", UnencodedAtSign, "unencoded @ sign in username or password")
839 ];
840
841 for test_case in &data {
842 let violation = Cell::new(None);
843 Url::options()
844 .syntax_violation_callback(Some(&|v| violation.set(Some(v))))
845 .parse(test_case.0)
846 .unwrap();
847
848 let v = violation.take();
849 assert_eq!(v, Some(test_case.1));
850 assert_eq!(v.unwrap().description(), test_case.2);
851 assert_eq!(v.unwrap().to_string(), test_case.2);
852 }
853 }
854
855 #[test]
test_options_reuse()856 fn test_options_reuse() {
857 use url::SyntaxViolation::*;
858 let violations = RefCell::new(Vec::new());
859 let vfn = |v| violations.borrow_mut().push(v);
860
861 let options = Url::options().syntax_violation_callback(Some(&vfn));
862 let url = options.parse("http:////mozilla.org").unwrap();
863
864 let options = options.base_url(Some(&url));
865 let url = options.parse("/sub\\path").unwrap();
866 assert_eq!(url.as_str(), "http://mozilla.org/sub/path");
867 assert_eq!(*violations.borrow(), vec!(ExpectedDoubleSlash, Backslash));
868 }
869
870 /// https://github.com/servo/rust-url/issues/505
871 #[cfg(windows)]
872 #[test]
test_url_from_file_path()873 fn test_url_from_file_path() {
874 use std::path::PathBuf;
875 use url::Url;
876
877 let p = PathBuf::from("c:///");
878 let u = Url::from_file_path(p).unwrap();
879 let path = u.to_file_path().unwrap();
880 assert_eq!("C:\\", path.to_str().unwrap());
881 }
882
883 /// https://github.com/servo/rust-url/issues/505
884 #[cfg(not(windows))]
885 #[test]
886 fn test_url_from_file_path() {
887 use std::path::PathBuf;
888 use url::Url;
889
890 let p = PathBuf::from("/c:/");
891 let u = Url::from_file_path(p).unwrap();
892 let path = u.to_file_path().unwrap();
893 assert_eq!("/c:/", path.to_str().unwrap());
894 }
895
896 #[test]
897 fn test_non_special_path() {
898 let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap();
899 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/");
900 db_url.set_path("diesel_foo");
901 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/diesel_foo");
902 assert_eq!(db_url.path(), "/diesel_foo");
903 }
904
905 #[test]
906 fn test_non_special_path2() {
907 let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap();
908 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/");
909 db_url.set_path("");
910 assert_eq!(db_url.path(), "");
911 assert_eq!(db_url.as_str(), "postgres://postgres@localhost");
912 db_url.set_path("foo");
913 assert_eq!(db_url.path(), "/foo");
914 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/foo");
915 db_url.set_path("/bar");
916 assert_eq!(db_url.path(), "/bar");
917 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/bar");
918 }
919
920 #[test]
921 fn test_non_special_path3() {
922 let mut db_url = url::Url::parse("postgres://postgres@localhost/").unwrap();
923 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/");
924 db_url.set_path("/");
925 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/");
926 assert_eq!(db_url.path(), "/");
927 db_url.set_path("/foo");
928 assert_eq!(db_url.as_str(), "postgres://postgres@localhost/foo");
929 assert_eq!(db_url.path(), "/foo");
930 }
931
932 #[test]
933 fn test_set_scheme_to_file_with_host() {
934 let mut url: Url = "http://localhost:6767/foo/bar".parse().unwrap();
935 let result = url.set_scheme("file");
936 assert_eq!(url.to_string(), "http://localhost:6767/foo/bar");
937 assert_eq!(result, Err(()));
938 }
939
940 #[test]
941 fn no_panic() {
942 let mut url = Url::parse("arhttpsps:/.//eom/dae.com/\\\\t\\:").unwrap();
943 url::quirks::set_hostname(&mut url, "//eom/datcom/\\\\t\\://eom/data.cs").unwrap();
944 }
945
946 #[test]
947 fn pop_if_empty_in_bounds() {
948 let mut url = Url::parse("m://").unwrap();
949 let mut segments = url.path_segments_mut().unwrap();
950 segments.pop_if_empty();
951 segments.pop();
952 }
953
954 #[test]
955 fn test_slicing() {
956 use url::Position::*;
957
958 #[derive(Default)]
959 struct ExpectedSlices<'a> {
960 full: &'a str,
961 scheme: &'a str,
962 username: &'a str,
963 password: &'a str,
964 host: &'a str,
965 port: &'a str,
966 path: &'a str,
967 query: &'a str,
968 fragment: &'a str,
969 }
970
971 let data = [
972 ExpectedSlices {
973 full: "https://user:pass@domain.com:9742/path/file.ext?key=val&key2=val2#fragment",
974 scheme: "https",
975 username: "user",
976 password: "pass",
977 host: "domain.com",
978 port: "9742",
979 path: "/path/file.ext",
980 query: "key=val&key2=val2",
981 fragment: "fragment",
982 },
983 ExpectedSlices {
984 full: "https://domain.com:9742/path/file.ext#fragment",
985 scheme: "https",
986 host: "domain.com",
987 port: "9742",
988 path: "/path/file.ext",
989 fragment: "fragment",
990 ..Default::default()
991 },
992 ExpectedSlices {
993 full: "https://domain.com:9742/path/file.ext",
994 scheme: "https",
995 host: "domain.com",
996 port: "9742",
997 path: "/path/file.ext",
998 ..Default::default()
999 },
1000 ExpectedSlices {
1001 full: "blob:blob-info",
1002 scheme: "blob",
1003 path: "blob-info",
1004 ..Default::default()
1005 },
1006 ];
1007
1008 for expected_slices in &data {
1009 let url = Url::parse(expected_slices.full).unwrap();
1010 assert_eq!(&url[..], expected_slices.full);
1011 assert_eq!(&url[BeforeScheme..AfterScheme], expected_slices.scheme);
1012 assert_eq!(
1013 &url[BeforeUsername..AfterUsername],
1014 expected_slices.username
1015 );
1016 assert_eq!(
1017 &url[BeforePassword..AfterPassword],
1018 expected_slices.password
1019 );
1020 assert_eq!(&url[BeforeHost..AfterHost], expected_slices.host);
1021 assert_eq!(&url[BeforePort..AfterPort], expected_slices.port);
1022 assert_eq!(&url[BeforePath..AfterPath], expected_slices.path);
1023 assert_eq!(&url[BeforeQuery..AfterQuery], expected_slices.query);
1024 assert_eq!(
1025 &url[BeforeFragment..AfterFragment],
1026 expected_slices.fragment
1027 );
1028 assert_eq!(&url[..AfterFragment], expected_slices.full);
1029 }
1030 }
1031
1032 #[test]
1033 fn test_make_relative() {
1034 let tests = [
1035 (
1036 "http://127.0.0.1:8080/test",
1037 "http://127.0.0.1:8080/test",
1038 "",
1039 ),
1040 (
1041 "http://127.0.0.1:8080/test",
1042 "http://127.0.0.1:8080/test/",
1043 "test/",
1044 ),
1045 (
1046 "http://127.0.0.1:8080/test/",
1047 "http://127.0.0.1:8080/test",
1048 "../test",
1049 ),
1050 (
1051 "http://127.0.0.1:8080/",
1052 "http://127.0.0.1:8080/?foo=bar#123",
1053 "?foo=bar#123",
1054 ),
1055 (
1056 "http://127.0.0.1:8080/",
1057 "http://127.0.0.1:8080/test/video",
1058 "test/video",
1059 ),
1060 (
1061 "http://127.0.0.1:8080/test",
1062 "http://127.0.0.1:8080/test/video",
1063 "test/video",
1064 ),
1065 (
1066 "http://127.0.0.1:8080/test/",
1067 "http://127.0.0.1:8080/test/video",
1068 "video",
1069 ),
1070 (
1071 "http://127.0.0.1:8080/test",
1072 "http://127.0.0.1:8080/test2/video",
1073 "test2/video",
1074 ),
1075 (
1076 "http://127.0.0.1:8080/test/",
1077 "http://127.0.0.1:8080/test2/video",
1078 "../test2/video",
1079 ),
1080 (
1081 "http://127.0.0.1:8080/test/bla",
1082 "http://127.0.0.1:8080/test2/video",
1083 "../test2/video",
1084 ),
1085 (
1086 "http://127.0.0.1:8080/test/bla/",
1087 "http://127.0.0.1:8080/test2/video",
1088 "../../test2/video",
1089 ),
1090 (
1091 "http://127.0.0.1:8080/test/?foo=bar#123",
1092 "http://127.0.0.1:8080/test/video",
1093 "video",
1094 ),
1095 (
1096 "http://127.0.0.1:8080/test/",
1097 "http://127.0.0.1:8080/test/video?baz=meh#456",
1098 "video?baz=meh#456",
1099 ),
1100 (
1101 "http://127.0.0.1:8080/test",
1102 "http://127.0.0.1:8080/test?baz=meh#456",
1103 "?baz=meh#456",
1104 ),
1105 (
1106 "http://127.0.0.1:8080/test/",
1107 "http://127.0.0.1:8080/test?baz=meh#456",
1108 "../test?baz=meh#456",
1109 ),
1110 (
1111 "http://127.0.0.1:8080/test/",
1112 "http://127.0.0.1:8080/test/?baz=meh#456",
1113 "?baz=meh#456",
1114 ),
1115 (
1116 "http://127.0.0.1:8080/test/?foo=bar#123",
1117 "http://127.0.0.1:8080/test/video?baz=meh#456",
1118 "video?baz=meh#456",
1119 ),
1120 (
1121 "http://127.0.0.1:8080/file.txt",
1122 "http://127.0.0.1:8080/test/file.txt",
1123 "test/file.txt",
1124 ),
1125 (
1126 "http://127.0.0.1:8080/not_equal.txt",
1127 "http://127.0.0.1:8080/test/file.txt",
1128 "test/file.txt",
1129 ),
1130 ];
1131
1132 for (base, uri, relative) in &tests {
1133 let base_uri = url::Url::parse(base).unwrap();
1134 let relative_uri = url::Url::parse(uri).unwrap();
1135 let make_relative = base_uri.make_relative(&relative_uri).unwrap();
1136 assert_eq!(
1137 make_relative, *relative,
1138 "base: {}, uri: {}, relative: {}",
1139 base, uri, relative
1140 );
1141 assert_eq!(
1142 base_uri.join(relative).unwrap().as_str(),
1143 *uri,
1144 "base: {}, uri: {}, relative: {}",
1145 base,
1146 uri,
1147 relative
1148 );
1149 }
1150
1151 let error_tests = [
1152 ("http://127.0.0.1:8080/", "https://127.0.0.1:8080/test/"),
1153 ("http://127.0.0.1:8080/", "http://127.0.0.1:8081/test/"),
1154 ("http://127.0.0.1:8080/", "http://127.0.0.2:8080/test/"),
1155 ("mailto:a@example.com", "mailto:b@example.com"),
1156 ];
1157
1158 for (base, uri) in &error_tests {
1159 let base_uri = url::Url::parse(base).unwrap();
1160 let relative_uri = url::Url::parse(uri).unwrap();
1161 let make_relative = base_uri.make_relative(&relative_uri);
1162 assert_eq!(make_relative, None, "base: {}, uri: {}", base, uri);
1163 }
1164 }
1165