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