1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Types/fns concerning URLs (see RFC 3986)
13 #![crate_id = "url#0.11.0-pre"]
15 #![crate_type = "rlib"]
16 #![crate_type = "dylib"]
17 #![license = "MIT/ASL2"]
18 #![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
19 html_favicon_url = "http://www.rust-lang.org/favicon.ico",
20 html_root_url = "http://doc.rust-lang.org/",
21 html_playground_url = "http://play.rust-lang.org/")]
22 #![feature(default_type_params)]
24 use std::collections::HashMap;
26 use std::from_str::FromStr;
28 use std::io::BufReader;
29 use std::string::String;
32 /// A Uniform Resource Locator (URL). A URL is a form of URI (Uniform Resource
33 /// Identifier) that includes network location information, such as hostname or
39 /// use url::{Url, UserInfo};
41 /// let url = Url { scheme: "https".to_string(),
42 /// user: Some(UserInfo { user: "username".to_string(), pass: None }),
43 /// host: "example.com".to_string(),
44 /// port: Some("8080".to_string()),
45 /// path: "/foo/bar".to_string(),
46 /// query: vec!(("baz".to_string(), "qux".to_string())),
47 /// fragment: Some("quz".to_string()) };
48 /// // https://username@example.com:8080/foo/bar?baz=qux#quz
50 #[deriving(Clone, PartialEq, Eq)]
52 /// The scheme part of a URL, such as `https` in the above example.
54 /// A URL subcomponent for user authentication. `username` in the above example.
55 pub user: Option<UserInfo>,
56 /// A domain name or IP address. For example, `example.com`.
58 /// A TCP port number, for example `8080`.
59 pub port: Option<String>,
60 /// The path component of a URL, for example `/foo/bar`.
62 /// The query component of a URL.
63 /// `vec!(("baz".to_string(), "qux".to_string()))` represents the fragment
64 /// `baz=qux` in the above example.
66 /// The fragment component, such as `quz`. Doesn't include the leading `#` character.
67 pub fragment: Option<String>
70 #[deriving(Clone, PartialEq)]
72 /// The path component of a URL, for example `/foo/bar`.
74 /// The query component of a URL.
75 /// `vec!(("baz".to_string(), "qux".to_string()))` represents the fragment
76 /// `baz=qux` in the above example.
78 /// The fragment component, such as `quz`. Doesn't include the leading `#` character.
79 pub fragment: Option<String>
82 /// An optional subcomponent of a URI authority component.
83 #[deriving(Clone, PartialEq, Eq)]
87 /// Password or other scheme-specific authentication information.
88 pub pass: Option<String>
91 /// Represents the query component of a URI.
92 pub type Query = Vec<(String, String)>;
95 pub fn new(scheme: String,
96 user: Option<UserInfo>,
101 fragment: Option<String>)
116 pub fn new(path: String,
118 fragment: Option<String>)
130 pub fn new(user: String, pass: Option<String>) -> UserInfo {
131 UserInfo { user: user, pass: pass }
135 fn encode_inner(s: &str, full_url: bool) -> String {
136 let mut rdr = BufReader::new(s.as_bytes());
137 let mut out = String::new();
141 let ch = match rdr.read(buf) {
143 Ok(..) => buf[0] as char,
151 '-' | '.' | '_' | '~' => {
158 ':' | '/' | '?' | '#' | '[' | ']' | '@' |
161 '!' | '$' | '&' | '"' | '(' | ')' | '*' |
162 '+' | ',' | ';' | '=' => {
166 _ => out.push_str(format!("%{:02X}", ch as uint).as_slice())
169 out.push_str(format!("%{:02X}", ch as uint).as_slice());
179 * Encodes a URI by replacing reserved characters with percent-encoded
180 * character sequences.
182 * This function is compliant with RFC 3986.
189 * let url = encode("https://example.com/Rust (programming language)");
190 * println!("{}", url); // https://example.com/Rust%20(programming%20language)
193 pub fn encode(s: &str) -> String {
194 encode_inner(s, true)
198 * Encodes a URI component by replacing reserved characters with percent-
199 * encoded character sequences.
201 * This function is compliant with RFC 3986.
204 pub fn encode_component(s: &str) -> String {
205 encode_inner(s, false)
208 fn decode_inner(s: &str, full_url: bool) -> String {
209 let mut rdr = BufReader::new(s.as_bytes());
210 let mut out = String::new();
214 let ch = match rdr.read(buf) {
216 Ok(..) => buf[0] as char
220 let mut bytes = [0, 0];
221 match rdr.read(bytes) {
223 _ => fail!() // FIXME: malformed url?
225 let ch = uint::parse_bytes(bytes, 16u).unwrap() as u8 as char;
228 // Only decode some characters:
231 ':' | '/' | '?' | '#' | '[' | ']' | '@' |
234 '!' | '$' | '&' | '"' | '(' | ')' | '*' |
235 '+' | ',' | ';' | '=' => {
237 out.push_char(bytes[0u] as char);
238 out.push_char(bytes[1u] as char);
241 ch => out.push_char(ch)
247 ch => out.push_char(ch)
255 * Decodes a percent-encoded string representing a URI.
257 * This will only decode escape sequences generated by `encode`.
264 * let url = decode("https://example.com/Rust%20(programming%20language)");
265 * println!("{}", url); // https://example.com/Rust (programming language)
268 pub fn decode(s: &str) -> String {
269 decode_inner(s, true)
273 * Decode a string encoded with percent encoding.
275 pub fn decode_component(s: &str) -> String {
276 decode_inner(s, false)
279 fn encode_plus(s: &str) -> String {
280 let mut rdr = BufReader::new(s.as_bytes());
281 let mut out = String::new();
285 let ch = match rdr.read(buf) {
286 Ok(..) => buf[0] as char,
290 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => {
293 ' ' => out.push_char('+'),
294 _ => out.push_str(format!("%{:X}", ch as uint).as_slice())
302 * Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
304 pub fn encode_form_urlencoded(m: &HashMap<String, Vec<String>>) -> String {
305 let mut out = String::new();
306 let mut first = true;
308 for (key, values) in m.iter() {
309 let key = encode_plus(key.as_slice());
311 for value in values.iter() {
319 out.push_str(format!("{}={}",
321 encode_plus(value.as_slice())).as_slice());
329 * Decode a string encoded with the 'application/x-www-form-urlencoded' media
330 * type into a hashmap.
332 #[allow(experimental)]
333 pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<String, Vec<String>> {
334 let mut rdr = BufReader::new(s);
335 let mut m: HashMap<String,Vec<String>> = HashMap::new();
336 let mut key = String::new();
337 let mut value = String::new();
338 let mut parsing_key = true;
342 let ch = match rdr.read(buf) {
343 Ok(..) => buf[0] as char,
348 if key.len() > 0 && value.len() > 0 {
349 let mut values = match m.pop_equiv(&key.as_slice()) {
350 Some(values) => values,
355 m.insert(key, values);
360 value = String::new();
362 '=' => parsing_key = false,
366 let mut bytes = [0, 0];
367 match rdr.read(bytes) {
369 _ => fail!() // FIXME: malformed?
371 uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
386 if key.len() > 0 && value.len() > 0 {
387 let mut values = match m.pop_equiv(&key.as_slice()) {
388 Some(values) => values,
393 m.insert(key, values);
400 fn split_char_first(s: &str, c: char) -> (String, String) {
404 let mut rdr = BufReader::new(s.as_bytes());
407 let ch = match rdr.read(buf) {
408 Ok(..) => buf[0] as char,
412 // found a match, adjust markers
413 index = (rdr.tell().unwrap() as uint) - 1;
418 if index+mat == len {
419 return (s.slice(0, index).to_string(), "".to_string());
421 return (s.slice(0, index).to_string(),
422 s.slice(index + mat, s.len()).to_string());
426 impl fmt::Show for UserInfo {
427 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
429 Some(ref pass) => write!(f, "{}:{}@", self.user, *pass),
430 None => write!(f, "{}@", self.user),
435 fn query_from_str(rawquery: &str) -> Query {
436 let mut query: Query = vec!();
437 if !rawquery.is_empty() {
438 for p in rawquery.split('&') {
439 let (k, v) = split_char_first(p, '=');
440 query.push((decode_component(k.as_slice()),
441 decode_component(v.as_slice())));
448 * Converts an instance of a URI `Query` type to a string.
453 * let query = vec!(("title".to_string(), "The Village".to_string()),
454 * ("north".to_string(), "52.91".to_string()),
455 * ("west".to_string(), "4.10".to_string()));
456 * println!("{}", url::query_to_str(&query)); // title=The%20Village&north=52.91&west=4.10
459 #[allow(unused_must_use)]
460 pub fn query_to_str(query: &Query) -> String {
461 use std::io::MemWriter;
464 let mut writer = MemWriter::new();
465 for (i, &(ref k, ref v)) in query.iter().enumerate() {
466 if i != 0 { write!(&mut writer, "&"); }
467 write!(&mut writer, "{}={}", encode_component(k.as_slice()),
468 encode_component(v.as_slice()));
470 str::from_utf8_lossy(writer.unwrap().as_slice()).to_string()
474 * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
476 * Does not include the separating `:` character.
481 * use url::get_scheme;
483 * let scheme = match get_scheme("https://example.com/") {
484 * Ok((sch, _)) => sch,
485 * Err(_) => "(None)".to_string(),
487 * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
490 pub fn get_scheme(rawurl: &str) -> Result<(String, String), String> {
491 for (i,c) in rawurl.chars().enumerate() {
493 'A' .. 'Z' | 'a' .. 'z' => continue,
494 '0' .. '9' | '+' | '-' | '.' => {
496 return Err("url: Scheme must begin with a \
497 letter.".to_string());
503 return Err("url: Scheme cannot be empty.".to_string());
505 return Ok((rawurl.slice(0,i).to_string(),
506 rawurl.slice(i+1,rawurl.len()).to_string()));
510 return Err("url: Invalid character in scheme.".to_string());
514 return Err("url: Scheme must be terminated with a colon.".to_string());
517 #[deriving(Clone, PartialEq)]
520 Hex, // digits and letters a-f
521 Unreserved // all other legal characters
524 // returns userinfo, host, port, and unparsed part, or an error
525 fn get_authority(rawurl: &str) ->
526 Result<(Option<UserInfo>, String, Option<String>, String), String> {
527 if !rawurl.starts_with("//") {
528 // there is no authority.
529 return Ok((None, "".to_string(), None, rawurl.to_str()));
533 Start, // starting state
534 PassHostPort, // could be in user or port
535 Ip6Port, // either in ipv6 host or port
536 Ip6Host, // are in an ipv6 host
537 InHost, // are in a host - may be ipv6, but don't know yet
538 InPort // are in port
541 let len = rawurl.len();
543 let mut input = Digit; // most restricted, start here.
545 let mut userinfo = None;
546 let mut host = "".to_string();
549 let mut colon_count = 0;
554 for (i,c) in rawurl.chars().enumerate() {
555 if i < 2 { continue; } // ignore the leading //
557 // deal with input class first
560 'A' .. 'F' | 'a' .. 'f' => {
565 'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
566 '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
569 ':' | '@' | '?' | '#' | '/' => {
570 // separators, don't change anything
573 return Err("Illegal character in authority".to_string());
577 // now process states
587 // multiple colons means ipv6 address.
588 if input == Unreserved {
590 "Illegal characters in IPv6 address.".to_string());
596 if input == Unreserved {
598 host = rawurl.slice(begin, i).to_string();
601 // can't be sure whether this is an ipv6 address or a port
606 if input == Unreserved {
607 return Err("Illegal characters in \
608 authority.".to_string());
614 host = rawurl.slice(begin, i).to_string();
620 return Err("Invalid ':' in authority.".to_string());
623 input = Digit; // reset input class
627 input = Digit; // reset input class
628 colon_count = 0; // reset count
631 let user = rawurl.slice(begin, i).to_string();
632 userinfo = Some(UserInfo::new(user, None));
636 let user = rawurl.slice(begin, pos).to_string();
637 let pass = rawurl.slice(pos+1, i).to_string();
638 userinfo = Some(UserInfo::new(user, Some(pass)));
642 return Err("Invalid '@' in authority.".to_string());
659 host = rawurl.slice(begin, end).to_string();
661 PassHostPort | Ip6Port => {
663 return Err("Non-digit characters in port.".to_string());
665 host = rawurl.slice(begin, pos).to_string();
666 port = Some(rawurl.slice(pos+1, end).to_string());
668 Ip6Host | InHost => {
669 host = rawurl.slice(begin, end).to_string();
673 return Err("Non-digit characters in port.".to_string());
675 port = Some(rawurl.slice(pos+1, end).to_string());
679 let rest = rawurl.slice(end, len).to_string();
680 return Ok((userinfo, host, port, rest));
684 // returns the path and unparsed part of url, or an error
685 fn get_path(rawurl: &str, authority: bool) ->
686 Result<(String, String), String> {
687 let len = rawurl.len();
689 for (i,c) in rawurl.chars().enumerate() {
691 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
692 | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
693 | '_' | '-' | '~' => {
700 _ => return Err("Invalid character in path.".to_string())
705 if end != 0 && !rawurl.starts_with("/") {
706 return Err("Non-empty path must begin with\
707 '/' in presence of authority.".to_string());
711 return Ok((decode_component(rawurl.slice(0, end)),
712 rawurl.slice(end, len).to_string()));
715 // returns the parsed query and the fragment, if present
716 fn get_query_fragment(rawurl: &str) ->
717 Result<(Query, Option<String>), String> {
718 if !rawurl.starts_with("?") {
719 if rawurl.starts_with("#") {
720 let f = decode_component(rawurl.slice(
723 return Ok((vec!(), Some(f)));
725 return Ok((vec!(), None));
728 let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#');
729 let f = if r.len() != 0 {
730 Some(decode_component(r.as_slice()))
734 return Ok((query_from_str(q.as_slice()), f));
738 * Parses a URL, converting it from a string to `Url` representation.
742 * `rawurl` - a string representing the full URL, including scheme.
746 * A `Url` struct type representing the URL.
748 pub fn from_str(rawurl: &str) -> Result<Url, String> {
750 let (scheme, rest) = match get_scheme(rawurl) {
752 Err(e) => return Err(e),
756 let (userinfo, host, port, rest) = match get_authority(rest.as_slice()) {
758 Err(e) => return Err(e),
762 let has_authority = host.len() > 0;
763 let (path, rest) = match get_path(rest.as_slice(), has_authority) {
765 Err(e) => return Err(e),
768 // query and fragment
769 let (query, fragment) = match get_query_fragment(rest.as_slice()) {
771 Err(e) => return Err(e),
774 Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
777 pub fn path_from_str(rawpath: &str) -> Result<Path, String> {
778 let (path, rest) = match get_path(rawpath, false) {
780 Err(e) => return Err(e)
783 // query and fragment
784 let (query, fragment) = match get_query_fragment(rest.as_slice()) {
786 Err(e) => return Err(e),
789 Ok(Path{ path: path, query: query, fragment: fragment })
792 impl FromStr for Url {
793 fn from_str(s: &str) -> Option<Url> {
795 Ok(url) => Some(url),
801 impl FromStr for Path {
802 fn from_str(s: &str) -> Option<Path> {
803 match path_from_str(s) {
804 Ok(path) => Some(path),
810 impl fmt::Show for Url {
812 * Converts a URL from `Url` to string representation.
820 * A string that contains the formatted URL. Note that this will usually
821 * be an inverse of `from_str` but might strip out unneeded separators;
822 * for example, "http://somehost.com?", when parsed and formatted, will
823 * result in just "http://somehost.com".
825 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
826 try!(write!(f, "{}:", self.scheme));
828 if !self.host.is_empty() {
829 try!(write!(f, "//"));
831 Some(ref user) => try!(write!(f, "{}", *user)),
835 Some(ref port) => try!(write!(f, "{}:{}", self.host,
837 None => try!(write!(f, "{}", self.host)),
841 try!(write!(f, "{}", self.path));
843 if !self.query.is_empty() {
844 try!(write!(f, "?{}", query_to_str(&self.query)));
847 match self.fragment {
848 Some(ref fragment) => {
849 write!(f, "#{}", encode_component(fragment.as_slice()))
856 impl fmt::Show for Path {
857 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
858 try!(write!(f, "{}", self.path));
859 if !self.query.is_empty() {
860 try!(write!(f, "?{}", self.query))
863 match self.fragment {
864 Some(ref fragment) => {
865 write!(f, "#{}", encode_component(fragment.as_slice()))
872 impl<S: hash::Writer> hash::Hash<S> for Url {
873 fn hash(&self, state: &mut S) {
874 self.to_str().hash(state)
878 impl<S: hash::Writer> hash::Hash<S> for Path {
879 fn hash(&self, state: &mut S) {
880 self.to_str().hash(state)
884 // Put a few tests outside of the 'test' module so they can test the internal
885 // functions and those functions don't need 'pub'
888 fn test_split_char_first() {
889 let (u,v) = split_char_first("hello, sweet world", ',');
890 assert_eq!(u, "hello".to_string());
891 assert_eq!(v, " sweet world".to_string());
893 let (u,v) = split_char_first("hello sweet world", ',');
894 assert_eq!(u, "hello sweet world".to_string());
895 assert_eq!(v, "".to_string());
899 fn test_get_authority() {
900 let (u, h, p, r) = get_authority(
901 "//user:pass@rust-lang.org/something").unwrap();
902 assert_eq!(u, Some(UserInfo::new("user".to_string(), Some("pass".to_string()))));
903 assert_eq!(h, "rust-lang.org".to_string());
904 assert!(p.is_none());
905 assert_eq!(r, "/something".to_string());
907 let (u, h, p, r) = get_authority(
908 "//rust-lang.org:8000?something").unwrap();
909 assert!(u.is_none());
910 assert_eq!(h, "rust-lang.org".to_string());
911 assert_eq!(p, Some("8000".to_string()));
912 assert_eq!(r, "?something".to_string());
914 let (u, h, p, r) = get_authority(
915 "//rust-lang.org#blah").unwrap();
916 assert!(u.is_none());
917 assert_eq!(h, "rust-lang.org".to_string());
918 assert!(p.is_none());
919 assert_eq!(r, "#blah".to_string());
922 let (_, h, _, _) = get_authority(
923 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap();
924 assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
926 let (_, h, p, _) = get_authority(
927 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap();
928 assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
929 assert_eq!(p, Some("8000".to_string()));
931 let (u, h, p, _) = get_authority(
932 "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
934 assert_eq!(u, Some(UserInfo::new("us".to_string(), Some("p".to_string()))));
935 assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
936 assert_eq!(p, Some("8000".to_string()));
938 // invalid authorities;
939 assert!(get_authority("//user:pass@rust-lang:something").is_err());
940 assert!(get_authority("//user@rust-lang:something:/path").is_err());
941 assert!(get_authority(
942 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err());
943 assert!(get_authority(
944 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err());
946 // these parse as empty, because they don't start with '//'
947 let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
948 assert_eq!(h, "".to_string());
949 let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
950 assert_eq!(h, "".to_string());
955 let (p, r) = get_path("/something+%20orother", true).unwrap();
956 assert_eq!(p, "/something+ orother".to_string());
957 assert_eq!(r, "".to_string());
958 let (p, r) = get_path("test@email.com#fragment", false).unwrap();
959 assert_eq!(p, "test@email.com".to_string());
960 assert_eq!(r, "#fragment".to_string());
961 let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap();
962 assert_eq!(p, "/gen/:addr=".to_string());
963 assert_eq!(r, "?q=v".to_string());
966 assert!(get_path("something?q", true).is_err());
971 use {encode_form_urlencoded, decode_form_urlencoded,
972 decode, encode, from_str, encode_component, decode_component,
973 path_from_str, UserInfo, get_scheme};
975 use std::collections::HashMap;
978 fn test_url_parse() {
979 let url = "http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
981 let up = from_str(url);
983 assert_eq!(&u.scheme, &"http".to_string());
984 assert_eq!(&u.user, &Some(UserInfo::new("user".to_string(), Some("pass".to_string()))));
985 assert_eq!(&u.host, &"rust-lang.org".to_string());
986 assert_eq!(&u.port, &Some("8080".to_string()));
987 assert_eq!(&u.path, &"/doc/~u".to_string());
988 assert_eq!(&u.query, &vec!(("s".to_string(), "v".to_string())));
989 assert_eq!(&u.fragment, &Some("something".to_string()));
993 fn test_path_parse() {
994 let path = "/doc/~u?s=v#something";
996 let up = path_from_str(path);
998 assert_eq!(&u.path, &"/doc/~u".to_string());
999 assert_eq!(&u.query, &vec!(("s".to_string(), "v".to_string())));
1000 assert_eq!(&u.fragment, &Some("something".to_string()));
1004 fn test_url_parse_host_slash() {
1005 let urlstr = "http://0.42.42.42/";
1006 let url = from_str(urlstr).unwrap();
1007 assert!(url.host == "0.42.42.42".to_string());
1008 assert!(url.path == "/".to_string());
1012 fn test_path_parse_host_slash() {
1014 let path = path_from_str(pathstr).unwrap();
1015 assert!(path.path == "/".to_string());
1019 fn test_url_host_with_port() {
1020 let urlstr = "scheme://host:1234";
1021 let url = from_str(urlstr).unwrap();
1022 assert_eq!(&url.scheme, &"scheme".to_string());
1023 assert_eq!(&url.host, &"host".to_string());
1024 assert_eq!(&url.port, &Some("1234".to_string()));
1025 // is empty path really correct? Other tests think so
1026 assert_eq!(&url.path, &"".to_string());
1027 let urlstr = "scheme://host:1234/";
1028 let url = from_str(urlstr).unwrap();
1029 assert_eq!(&url.scheme, &"scheme".to_string());
1030 assert_eq!(&url.host, &"host".to_string());
1031 assert_eq!(&url.port, &Some("1234".to_string()));
1032 assert_eq!(&url.path, &"/".to_string());
1036 fn test_url_with_underscores() {
1037 let urlstr = "http://dotcom.com/file_name.html";
1038 let url = from_str(urlstr).unwrap();
1039 assert!(url.path == "/file_name.html".to_string());
1043 fn test_path_with_underscores() {
1044 let pathstr = "/file_name.html";
1045 let path = path_from_str(pathstr).unwrap();
1046 assert!(path.path == "/file_name.html".to_string());
1050 fn test_url_with_dashes() {
1051 let urlstr = "http://dotcom.com/file-name.html";
1052 let url = from_str(urlstr).unwrap();
1053 assert!(url.path == "/file-name.html".to_string());
1057 fn test_path_with_dashes() {
1058 let pathstr = "/file-name.html";
1059 let path = path_from_str(pathstr).unwrap();
1060 assert!(path.path == "/file-name.html".to_string());
1064 fn test_no_scheme() {
1065 assert!(get_scheme("noschemehere.html").is_err());
1069 fn test_invalid_scheme_errors() {
1070 assert!(from_str("99://something").is_err());
1071 assert!(from_str("://something").is_err());
1075 fn test_full_url_parse_and_format() {
1076 let url = "http://user:pass@rust-lang.org/doc?s=v#something";
1077 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1081 fn test_userless_url_parse_and_format() {
1082 let url = "http://rust-lang.org/doc?s=v#something";
1083 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1087 fn test_queryless_url_parse_and_format() {
1088 let url = "http://user:pass@rust-lang.org/doc#something";
1089 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1093 fn test_empty_query_url_parse_and_format() {
1094 let url = "http://user:pass@rust-lang.org/doc?#something";
1095 let should_be = "http://user:pass@rust-lang.org/doc#something";
1096 assert_eq!(from_str(url).unwrap().to_str().as_slice(), should_be);
1100 fn test_fragmentless_url_parse_and_format() {
1101 let url = "http://user:pass@rust-lang.org/doc?q=v";
1102 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1106 fn test_minimal_url_parse_and_format() {
1107 let url = "http://rust-lang.org/doc";
1108 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1112 fn test_url_with_port_parse_and_format() {
1113 let url = "http://rust-lang.org:80/doc";
1114 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1118 fn test_scheme_host_only_url_parse_and_format() {
1119 let url = "http://rust-lang.org";
1120 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1124 fn test_pathless_url_parse_and_format() {
1125 let url = "http://user:pass@rust-lang.org?q=v#something";
1126 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1130 fn test_scheme_host_fragment_only_url_parse_and_format() {
1131 let url = "http://rust-lang.org#something";
1132 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1136 fn test_url_component_encoding() {
1137 let url = "http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B";
1138 let u = from_str(url).unwrap();
1139 assert!(u.path == "/doc uments".to_string());
1140 assert!(u.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
1144 fn test_path_component_encoding() {
1145 let path = "/doc%20uments?ba%25d%20=%23%26%2B";
1146 let p = path_from_str(path).unwrap();
1147 assert!(p.path == "/doc uments".to_string());
1148 assert!(p.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
1152 fn test_url_without_authority() {
1153 let url = "mailto:test@email.com";
1154 assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1159 assert_eq!(encode(""), "".to_string());
1160 assert_eq!(encode("http://example.com"), "http://example.com".to_string());
1161 assert_eq!(encode("foo bar% baz"), "foo%20bar%25%20baz".to_string());
1162 assert_eq!(encode(" "), "%20".to_string());
1163 assert_eq!(encode("!"), "!".to_string());
1164 assert_eq!(encode("\""), "\"".to_string());
1165 assert_eq!(encode("#"), "#".to_string());
1166 assert_eq!(encode("$"), "$".to_string());
1167 assert_eq!(encode("%"), "%25".to_string());
1168 assert_eq!(encode("&"), "&".to_string());
1169 assert_eq!(encode("'"), "%27".to_string());
1170 assert_eq!(encode("("), "(".to_string());
1171 assert_eq!(encode(")"), ")".to_string());
1172 assert_eq!(encode("*"), "*".to_string());
1173 assert_eq!(encode("+"), "+".to_string());
1174 assert_eq!(encode(","), ",".to_string());
1175 assert_eq!(encode("/"), "/".to_string());
1176 assert_eq!(encode(":"), ":".to_string());
1177 assert_eq!(encode(";"), ";".to_string());
1178 assert_eq!(encode("="), "=".to_string());
1179 assert_eq!(encode("?"), "?".to_string());
1180 assert_eq!(encode("@"), "@".to_string());
1181 assert_eq!(encode("["), "[".to_string());
1182 assert_eq!(encode("]"), "]".to_string());
1183 assert_eq!(encode("\0"), "%00".to_string());
1184 assert_eq!(encode("\n"), "%0A".to_string());
1188 fn test_encode_component() {
1189 assert_eq!(encode_component(""), "".to_string());
1190 assert!(encode_component("http://example.com") ==
1191 "http%3A%2F%2Fexample.com".to_string());
1192 assert!(encode_component("foo bar% baz") ==
1193 "foo%20bar%25%20baz".to_string());
1194 assert_eq!(encode_component(" "), "%20".to_string());
1195 assert_eq!(encode_component("!"), "%21".to_string());
1196 assert_eq!(encode_component("#"), "%23".to_string());
1197 assert_eq!(encode_component("$"), "%24".to_string());
1198 assert_eq!(encode_component("%"), "%25".to_string());
1199 assert_eq!(encode_component("&"), "%26".to_string());
1200 assert_eq!(encode_component("'"), "%27".to_string());
1201 assert_eq!(encode_component("("), "%28".to_string());
1202 assert_eq!(encode_component(")"), "%29".to_string());
1203 assert_eq!(encode_component("*"), "%2A".to_string());
1204 assert_eq!(encode_component("+"), "%2B".to_string());
1205 assert_eq!(encode_component(","), "%2C".to_string());
1206 assert_eq!(encode_component("/"), "%2F".to_string());
1207 assert_eq!(encode_component(":"), "%3A".to_string());
1208 assert_eq!(encode_component(";"), "%3B".to_string());
1209 assert_eq!(encode_component("="), "%3D".to_string());
1210 assert_eq!(encode_component("?"), "%3F".to_string());
1211 assert_eq!(encode_component("@"), "%40".to_string());
1212 assert_eq!(encode_component("["), "%5B".to_string());
1213 assert_eq!(encode_component("]"), "%5D".to_string());
1214 assert_eq!(encode_component("\0"), "%00".to_string());
1215 assert_eq!(encode_component("\n"), "%0A".to_string());
1220 assert_eq!(decode(""), "".to_string());
1221 assert_eq!(decode("abc/def 123"), "abc/def 123".to_string());
1222 assert_eq!(decode("abc%2Fdef%20123"), "abc%2Fdef 123".to_string());
1223 assert_eq!(decode("%20"), " ".to_string());
1224 assert_eq!(decode("%21"), "%21".to_string());
1225 assert_eq!(decode("%22"), "%22".to_string());
1226 assert_eq!(decode("%23"), "%23".to_string());
1227 assert_eq!(decode("%24"), "%24".to_string());
1228 assert_eq!(decode("%25"), "%".to_string());
1229 assert_eq!(decode("%26"), "%26".to_string());
1230 assert_eq!(decode("%27"), "'".to_string());
1231 assert_eq!(decode("%28"), "%28".to_string());
1232 assert_eq!(decode("%29"), "%29".to_string());
1233 assert_eq!(decode("%2A"), "%2A".to_string());
1234 assert_eq!(decode("%2B"), "%2B".to_string());
1235 assert_eq!(decode("%2C"), "%2C".to_string());
1236 assert_eq!(decode("%2F"), "%2F".to_string());
1237 assert_eq!(decode("%3A"), "%3A".to_string());
1238 assert_eq!(decode("%3B"), "%3B".to_string());
1239 assert_eq!(decode("%3D"), "%3D".to_string());
1240 assert_eq!(decode("%3F"), "%3F".to_string());
1241 assert_eq!(decode("%40"), "%40".to_string());
1242 assert_eq!(decode("%5B"), "%5B".to_string());
1243 assert_eq!(decode("%5D"), "%5D".to_string());
1247 fn test_decode_component() {
1248 assert_eq!(decode_component(""), "".to_string());
1249 assert_eq!(decode_component("abc/def 123"), "abc/def 123".to_string());
1250 assert_eq!(decode_component("abc%2Fdef%20123"), "abc/def 123".to_string());
1251 assert_eq!(decode_component("%20"), " ".to_string());
1252 assert_eq!(decode_component("%21"), "!".to_string());
1253 assert_eq!(decode_component("%22"), "\"".to_string());
1254 assert_eq!(decode_component("%23"), "#".to_string());
1255 assert_eq!(decode_component("%24"), "$".to_string());
1256 assert_eq!(decode_component("%25"), "%".to_string());
1257 assert_eq!(decode_component("%26"), "&".to_string());
1258 assert_eq!(decode_component("%27"), "'".to_string());
1259 assert_eq!(decode_component("%28"), "(".to_string());
1260 assert_eq!(decode_component("%29"), ")".to_string());
1261 assert_eq!(decode_component("%2A"), "*".to_string());
1262 assert_eq!(decode_component("%2B"), "+".to_string());
1263 assert_eq!(decode_component("%2C"), ",".to_string());
1264 assert_eq!(decode_component("%2F"), "/".to_string());
1265 assert_eq!(decode_component("%3A"), ":".to_string());
1266 assert_eq!(decode_component("%3B"), ";".to_string());
1267 assert_eq!(decode_component("%3D"), "=".to_string());
1268 assert_eq!(decode_component("%3F"), "?".to_string());
1269 assert_eq!(decode_component("%40"), "@".to_string());
1270 assert_eq!(decode_component("%5B"), "[".to_string());
1271 assert_eq!(decode_component("%5D"), "]".to_string());
1275 fn test_encode_form_urlencoded() {
1276 let mut m = HashMap::new();
1277 assert_eq!(encode_form_urlencoded(&m), "".to_string());
1279 m.insert("".to_string(), vec!());
1280 m.insert("foo".to_string(), vec!());
1281 assert_eq!(encode_form_urlencoded(&m), "".to_string());
1283 let mut m = HashMap::new();
1284 m.insert("foo".to_string(), vec!("bar".to_string(), "123".to_string()));
1285 assert_eq!(encode_form_urlencoded(&m), "foo=bar&foo=123".to_string());
1287 let mut m = HashMap::new();
1288 m.insert("foo bar".to_string(), vec!("abc".to_string(), "12 = 34".to_string()));
1289 assert!(encode_form_urlencoded(&m) ==
1290 "foo+bar=abc&foo+bar=12+%3D+34".to_string());
1294 fn test_decode_form_urlencoded() {
1295 assert_eq!(decode_form_urlencoded([]).len(), 0);
1297 let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes();
1298 let form = decode_form_urlencoded(s);
1299 assert_eq!(form.len(), 2);
1300 assert_eq!(form.get(&"a".to_string()), &vec!("1".to_string()));
1301 assert_eq!(form.get(&"foo bar".to_string()),
1302 &vec!("abc".to_string(), "12 = 34".to_string()));