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