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.10-pre"];
14 #[crate_type = "rlib"];
15 #[crate_type = "dylib"];
16 #[license = "MIT/ASL2"];
17 #[doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
18 html_favicon_url = "http://www.rust-lang.org/favicon.ico",
19 html_root_url = "http://static.rust-lang.org/doc/master")];
20 #[feature(default_type_params)];
22 extern crate collections;
27 use std::io::BufReader;
28 use std::from_str::FromStr;
31 use collections::HashMap;
33 /// A Uniform Resource Locator (URL). A URL is a form of URI (Uniform Resource
34 /// Identifier) that includes network location information, such as hostname or
40 /// use url::{Url, UserInfo};
42 /// let url = Url { scheme: ~"https",
43 /// user: Some(UserInfo { user: ~"username", pass: None }),
44 /// host: ~"example.com",
45 /// port: Some(~"8080"),
46 /// path: ~"/foo/bar",
47 /// query: vec!((~"baz", ~"qux")),
48 /// fragment: Some(~"quz") };
49 /// // https://username@example.com:8080/foo/bar?baz=qux#quz
51 #[deriving(Clone, Eq)]
53 /// The scheme part of a URL, such as `https` in the above example.
55 /// A URL subcomponent for user authentication. `username` in the above example.
56 user: Option<UserInfo>,
57 /// A domain name or IP address. For example, `example.com`.
59 /// A TCP port number, for example `8080`.
61 /// The path component of a URL, for example `/foo/bar`.
63 /// The query component of a URL. `vec!((~"baz", ~"qux"))` represents the
64 /// fragment `baz=qux` in the above example.
66 /// The fragment component, such as `quz`. Doesn't include the leading `#` character.
67 fragment: Option<~str>
70 #[deriving(Clone, Eq)]
72 /// The path component of a URL, for example `/foo/bar`.
74 /// The query component of a URL. `vec!((~"baz", ~"qux"))` represents the
75 /// fragment `baz=qux` in the above example.
77 /// The fragment component, such as `quz`. Doesn't include the leading `#` character.
78 fragment: Option<~str>
81 /// An optional subcomponent of a URI authority component.
82 #[deriving(Clone, Eq)]
86 /// Password or other scheme-specific authentication information.
90 /// Represents the query component of a URI.
91 pub type Query = Vec<(~str, ~str)>;
94 pub fn new(scheme: ~str,
95 user: Option<UserInfo>,
100 fragment: Option<~str>)
115 pub fn new(path: ~str,
117 fragment: Option<~str>)
129 pub fn new(user: ~str, pass: Option<~str>) -> UserInfo {
130 UserInfo { user: user, pass: pass }
134 fn encode_inner(s: &str, full_url: bool) -> ~str {
135 let mut rdr = BufReader::new(s.as_bytes());
140 let ch = match rdr.read(buf) {
142 Ok(..) => buf[0] as char,
150 '-' | '.' | '_' | '~' => {
157 ':' | '/' | '?' | '#' | '[' | ']' | '@' |
160 '!' | '$' | '&' | '"' | '(' | ')' | '*' |
161 '+' | ',' | ';' | '=' => {
165 _ => out.push_str(format!("%{:X}", ch as uint))
168 out.push_str(format!("%{:X}", ch as uint));
178 * Encodes a URI by replacing reserved characters with percent-encoded
179 * character sequences.
181 * This function is compliant with RFC 3986.
188 * let url = encode(&"https://example.com/Rust (programming language)");
189 * println!("{}", url); // https://example.com/Rust%20(programming%20language)
192 pub fn encode(s: &str) -> ~str {
193 encode_inner(s, true)
197 * Encodes a URI component by replacing reserved characters with percent
198 * encoded character sequences.
200 * This function is compliant with RFC 3986.
203 pub fn encode_component(s: &str) -> ~str {
204 encode_inner(s, false)
207 fn decode_inner(s: &str, full_url: bool) -> ~str {
208 let mut rdr = BufReader::new(s.as_bytes());
213 let ch = match rdr.read(buf) {
215 Ok(..) => buf[0] as char
219 let mut bytes = [0, 0];
220 match rdr.read(bytes) {
222 _ => fail!() // FIXME: malformed url?
224 let ch = uint::parse_bytes(bytes, 16u).unwrap() as u8 as char;
227 // Only decode some characters:
230 ':' | '/' | '?' | '#' | '[' | ']' | '@' |
233 '!' | '$' | '&' | '"' | '(' | ')' | '*' |
234 '+' | ',' | ';' | '=' => {
236 out.push_char(bytes[0u] as char);
237 out.push_char(bytes[1u] as char);
240 ch => out.push_char(ch)
246 ch => out.push_char(ch)
254 * Decodes a percent-encoded string representing a URI.
256 * This will only decode escape sequences generated by `encode`.
263 * let url = decode(&"https://example.com/Rust%20(programming%20language)");
264 * println!("{}", url); // https://example.com/Rust (programming language)
267 pub fn decode(s: &str) -> ~str {
268 decode_inner(s, true)
272 * Decode a string encoded with percent encoding.
274 pub fn decode_component(s: &str) -> ~str {
275 decode_inner(s, false)
278 fn encode_plus(s: &str) -> ~str {
279 let mut rdr = BufReader::new(s.as_bytes());
284 let ch = match rdr.read(buf) {
285 Ok(..) => buf[0] as char,
289 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => {
292 ' ' => out.push_char('+'),
293 _ => out.push_str(format!("%{:X}", ch as uint))
301 * Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
303 pub fn encode_form_urlencoded(m: &HashMap<~str, Vec<~str>>) -> ~str {
305 let mut first = true;
307 for (key, values) in m.iter() {
308 let key = encode_plus(*key);
310 for value in values.iter() {
318 out.push_str(format!("{}={}", key, encode_plus(*value)));
326 * Decode a string encoded with the 'application/x-www-form-urlencoded' media
327 * type into a hashmap.
329 pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<~str, Vec<~str>> {
330 let mut rdr = BufReader::new(s);
331 let mut m = HashMap::new();
334 let mut parsing_key = true;
338 let ch = match rdr.read(buf) {
339 Ok(..) => buf[0] as char,
344 if key != ~"" && value != ~"" {
345 let mut values = match m.pop(&key) {
346 Some(values) => values,
351 m.insert(key, values);
358 '=' => parsing_key = false,
362 let mut bytes = [0, 0];
363 match rdr.read(bytes) {
365 _ => fail!() // FIXME: malformed?
367 uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
382 if key != ~"" && value != ~"" {
383 let mut values = match m.pop(&key) {
384 Some(values) => values,
389 m.insert(key, values);
396 fn split_char_first(s: &str, c: char) -> (~str, ~str) {
400 let mut rdr = BufReader::new(s.as_bytes());
403 let ch = match rdr.read(buf) {
404 Ok(..) => buf[0] as char,
408 // found a match, adjust markers
409 index = (rdr.tell().unwrap() as uint) - 1;
414 if index+mat == len {
415 return (s.slice(0, index).to_owned(), ~"");
417 return (s.slice(0, index).to_owned(),
418 s.slice(index + mat, s.len()).to_owned());
422 impl fmt::Show for UserInfo {
423 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425 Some(ref pass) => write!(f.buf, "{}:{}@", self.user, *pass),
426 None => write!(f.buf, "{}@", self.user),
431 fn query_from_str(rawquery: &str) -> Query {
432 let mut query: Query = vec!();
433 if !rawquery.is_empty() {
434 for p in rawquery.split('&') {
435 let (k, v) = split_char_first(p, '=');
436 query.push((decode_component(k), decode_component(v)));
443 * Converts an instance of a URI `Query` type to a string.
448 * let query = vec!((~"title", ~"The Village"), (~"north", ~"52.91"), (~"west", ~"4.10"));
449 * println!("{}", url::query_to_str(&query)); // title=The%20Village&north=52.91&west=4.10
452 #[allow(unused_must_use)]
453 pub fn query_to_str(query: &Query) -> ~str {
454 use std::io::MemWriter;
457 let mut writer = MemWriter::new();
458 for (i, &(ref k, ref v)) in query.iter().enumerate() {
459 if i != 0 { write!(&mut writer, "&"); }
460 write!(&mut writer, "{}={}", encode_component(*k),
461 encode_component(*v));
463 str::from_utf8_lossy(writer.unwrap()).into_owned()
467 * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
469 * Does not include the separating `:` character.
474 * use url::get_scheme;
476 * let scheme = match get_scheme("https://example.com/") {
477 * Ok((sch, _)) => sch,
478 * Err(_) => ~"(None)",
480 * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
483 pub fn get_scheme(rawurl: &str) -> Result<(~str, ~str), ~str> {
484 for (i,c) in rawurl.chars().enumerate() {
486 'A' .. 'Z' | 'a' .. 'z' => continue,
487 '0' .. '9' | '+' | '-' | '.' => {
489 return Err(~"url: Scheme must begin with a letter.");
495 return Err(~"url: Scheme cannot be empty.");
497 return Ok((rawurl.slice(0,i).to_owned(),
498 rawurl.slice(i+1,rawurl.len()).to_owned()));
502 return Err(~"url: Invalid character in scheme.");
506 return Err(~"url: Scheme must be terminated with a colon.");
509 #[deriving(Clone, Eq)]
512 Hex, // digits and letters a-f
513 Unreserved // all other legal characters
516 // returns userinfo, host, port, and unparsed part, or an error
517 fn get_authority(rawurl: &str) ->
518 Result<(Option<UserInfo>, ~str, Option<~str>, ~str), ~str> {
519 if !rawurl.starts_with("//") {
520 // there is no authority.
521 return Ok((None, ~"", None, rawurl.to_str()));
525 Start, // starting state
526 PassHostPort, // could be in user or port
527 Ip6Port, // either in ipv6 host or port
528 Ip6Host, // are in an ipv6 host
529 InHost, // are in a host - may be ipv6, but don't know yet
530 InPort // are in port
533 let len = rawurl.len();
535 let mut input = Digit; // most restricted, start here.
537 let mut userinfo = None;
541 let mut colon_count = 0;
546 for (i,c) in rawurl.chars().enumerate() {
547 if i < 2 { continue; } // ignore the leading //
549 // deal with input class first
552 'A' .. 'F' | 'a' .. 'f' => {
557 'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
558 '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
561 ':' | '@' | '?' | '#' | '/' => {
562 // separators, don't change anything
565 return Err(~"Illegal character in authority");
569 // now process states
579 // multiple colons means ipv6 address.
580 if input == Unreserved {
582 ~"Illegal characters in IPv6 address.");
588 if input == Unreserved {
590 host = rawurl.slice(begin, i).to_owned();
593 // can't be sure whether this is an ipv6 address or a port
598 if input == Unreserved {
599 return Err(~"Illegal characters in authority.");
605 host = rawurl.slice(begin, i).to_owned();
611 return Err(~"Invalid ':' in authority.");
614 input = Digit; // reset input class
618 input = Digit; // reset input class
619 colon_count = 0; // reset count
622 let user = rawurl.slice(begin, i).to_owned();
623 userinfo = Some(UserInfo::new(user, None));
627 let user = rawurl.slice(begin, pos).to_owned();
628 let pass = rawurl.slice(pos+1, i).to_owned();
629 userinfo = Some(UserInfo::new(user, Some(pass)));
633 return Err(~"Invalid '@' in authority.");
650 host = rawurl.slice(begin, end).to_owned();
652 PassHostPort | Ip6Port => {
654 return Err(~"Non-digit characters in port.");
656 host = rawurl.slice(begin, pos).to_owned();
657 port = Some(rawurl.slice(pos+1, end).to_owned());
659 Ip6Host | InHost => {
660 host = rawurl.slice(begin, end).to_owned();
664 return Err(~"Non-digit characters in port.");
666 port = Some(rawurl.slice(pos+1, end).to_owned());
670 let rest = rawurl.slice(end, len).to_owned();
671 return Ok((userinfo, host, port, rest));
675 // returns the path and unparsed part of url, or an error
676 fn get_path(rawurl: &str, authority: bool) ->
677 Result<(~str, ~str), ~str> {
678 let len = rawurl.len();
680 for (i,c) in rawurl.chars().enumerate() {
682 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
683 | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
684 | '_' | '-' | '~' => {
691 _ => return Err(~"Invalid character in path.")
696 if end != 0 && !rawurl.starts_with("/") {
697 return Err(~"Non-empty path must begin with\
698 '/' in presence of authority.");
702 return Ok((decode_component(rawurl.slice(0, end)),
703 rawurl.slice(end, len).to_owned()));
706 // returns the parsed query and the fragment, if present
707 fn get_query_fragment(rawurl: &str) ->
708 Result<(Query, Option<~str>), ~str> {
709 if !rawurl.starts_with("?") {
710 if rawurl.starts_with("#") {
711 let f = decode_component(rawurl.slice(
714 return Ok((vec!(), Some(f)));
716 return Ok((vec!(), None));
719 let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#');
720 let f = if r.len() != 0 {
721 Some(decode_component(r)) } else { None };
722 return Ok((query_from_str(q), f));
726 * Parses a URL, converting it from a string to `Url` representation.
730 * `rawurl` - a string representing the full URL, including scheme.
734 * A `Url` struct type representing the URL.
736 pub fn from_str(rawurl: &str) -> Result<Url, ~str> {
738 let (scheme, rest) = match get_scheme(rawurl) {
740 Err(e) => return Err(e),
744 let (userinfo, host, port, rest) = match get_authority(rest) {
746 Err(e) => return Err(e),
750 let has_authority = if host == ~"" { false } else { true };
751 let (path, rest) = match get_path(rest, has_authority) {
753 Err(e) => return Err(e),
756 // query and fragment
757 let (query, fragment) = match get_query_fragment(rest) {
759 Err(e) => return Err(e),
762 Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
765 pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
766 let (path, rest) = match get_path(rawpath, false) {
768 Err(e) => return Err(e)
771 // query and fragment
772 let (query, fragment) = match get_query_fragment(rest) {
774 Err(e) => return Err(e),
777 Ok(Path{ path: path, query: query, fragment: fragment })
780 impl FromStr for Url {
781 fn from_str(s: &str) -> Option<Url> {
783 Ok(url) => Some(url),
789 impl FromStr for Path {
790 fn from_str(s: &str) -> Option<Path> {
791 match path_from_str(s) {
792 Ok(path) => Some(path),
798 impl fmt::Show for Url {
800 * Converts a URL from `Url` to string representation.
808 * A string that contains the formatted URL. Note that this will usually
809 * be an inverse of `from_str` but might strip out unneeded separators;
810 * for example, "http://somehost.com?", when parsed and formatted, will
811 * result in just "http://somehost.com".
813 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
814 try!(write!(f.buf, "{}:", self.scheme));
816 if !self.host.is_empty() {
817 try!(write!(f.buf, "//"));
819 Some(ref user) => try!(write!(f.buf, "{}", *user)),
823 Some(ref port) => try!(write!(f.buf, "{}:{}", self.host,
825 None => try!(write!(f.buf, "{}", self.host)),
829 try!(write!(f.buf, "{}", self.path));
831 if !self.query.is_empty() {
832 try!(write!(f.buf, "?{}", query_to_str(&self.query)));
835 match self.fragment {
836 Some(ref fragment) => write!(f.buf, "\\#{}",
837 encode_component(*fragment)),
843 impl fmt::Show for Path {
844 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
845 try!(write!(f.buf, "{}", self.path));
846 if !self.query.is_empty() {
847 try!(write!(f.buf, "?{}", self.query))
850 match self.fragment {
851 Some(ref fragment) => {
852 write!(f.buf, "\\#{}", encode_component(*fragment))
859 impl<S: Writer> Hash<S> for Url {
860 fn hash(&self, state: &mut S) {
861 self.to_str().hash(state)
865 impl<S: Writer> Hash<S> for Path {
866 fn hash(&self, state: &mut S) {
867 self.to_str().hash(state)
871 // Put a few tests outside of the 'test' module so they can test the internal
872 // functions and those functions don't need 'pub'
875 fn test_split_char_first() {
876 let (u,v) = split_char_first("hello, sweet world", ',');
877 assert_eq!(u, ~"hello");
878 assert_eq!(v, ~" sweet world");
880 let (u,v) = split_char_first("hello sweet world", ',');
881 assert_eq!(u, ~"hello sweet world");
886 fn test_get_authority() {
887 let (u, h, p, r) = get_authority(
888 "//user:pass@rust-lang.org/something").unwrap();
889 assert_eq!(u, Some(UserInfo::new(~"user", Some(~"pass"))));
890 assert_eq!(h, ~"rust-lang.org");
891 assert!(p.is_none());
892 assert_eq!(r, ~"/something");
894 let (u, h, p, r) = get_authority(
895 "//rust-lang.org:8000?something").unwrap();
896 assert!(u.is_none());
897 assert_eq!(h, ~"rust-lang.org");
898 assert_eq!(p, Some(~"8000"));
899 assert_eq!(r, ~"?something");
901 let (u, h, p, r) = get_authority(
902 "//rust-lang.org#blah").unwrap();
903 assert!(u.is_none());
904 assert_eq!(h, ~"rust-lang.org");
905 assert!(p.is_none());
906 assert_eq!(r, ~"#blah");
909 let (_, h, _, _) = get_authority(
910 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap();
911 assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334");
913 let (_, h, p, _) = get_authority(
914 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap();
915 assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334");
916 assert_eq!(p, Some(~"8000"));
918 let (u, h, p, _) = get_authority(
919 "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
921 assert_eq!(u, Some(UserInfo::new(~"us", Some(~"p"))));
922 assert_eq!(h, ~"2001:0db8:85a3:0042:0000:8a2e:0370:7334");
923 assert_eq!(p, Some(~"8000"));
925 // invalid authorities;
926 assert!(get_authority("//user:pass@rust-lang:something").is_err());
927 assert!(get_authority("//user@rust-lang:something:/path").is_err());
928 assert!(get_authority(
929 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err());
930 assert!(get_authority(
931 "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err());
933 // these parse as empty, because they don't start with '//'
934 let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
936 let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
942 let (p, r) = get_path("/something+%20orother", true).unwrap();
943 assert_eq!(p, ~"/something+ orother");
945 let (p, r) = get_path("test@email.com#fragment", false).unwrap();
946 assert_eq!(p, ~"test@email.com");
947 assert_eq!(r, ~"#fragment");
948 let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap();
949 assert_eq!(p, ~"/gen/:addr=");
950 assert_eq!(r, ~"?q=v");
953 assert!(get_path("something?q", true).is_err());
958 use {encode_form_urlencoded, decode_form_urlencoded,
959 decode, encode, from_str, encode_component, decode_component,
960 path_from_str, UserInfo, get_scheme};
962 use collections::HashMap;
965 fn test_url_parse() {
966 let url = ~"http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
968 let up = from_str(url);
970 assert_eq!(&u.scheme, &~"http");
971 assert_eq!(&u.user, &Some(UserInfo::new(~"user", Some(~"pass"))));
972 assert_eq!(&u.host, &~"rust-lang.org");
973 assert_eq!(&u.port, &Some(~"8080"));
974 assert_eq!(&u.path, &~"/doc/~u");
975 assert_eq!(&u.query, &vec!((~"s", ~"v")));
976 assert_eq!(&u.fragment, &Some(~"something"));
980 fn test_path_parse() {
981 let path = ~"/doc/~u?s=v#something";
983 let up = path_from_str(path);
985 assert_eq!(&u.path, &~"/doc/~u");
986 assert_eq!(&u.query, &vec!((~"s", ~"v")));
987 assert_eq!(&u.fragment, &Some(~"something"));
991 fn test_url_parse_host_slash() {
992 let urlstr = ~"http://0.42.42.42/";
993 let url = from_str(urlstr).unwrap();
994 assert!(url.host == ~"0.42.42.42");
995 assert!(url.path == ~"/");
999 fn test_path_parse_host_slash() {
1001 let path = path_from_str(pathstr).unwrap();
1002 assert!(path.path == ~"/");
1006 fn test_url_host_with_port() {
1007 let urlstr = ~"scheme://host:1234";
1008 let url = from_str(urlstr).unwrap();
1009 assert_eq!(&url.scheme, &~"scheme");
1010 assert_eq!(&url.host, &~"host");
1011 assert_eq!(&url.port, &Some(~"1234"));
1012 assert_eq!(&url.path, &~""); // is empty path really correct? Other tests think so
1013 let urlstr = ~"scheme://host:1234/";
1014 let url = from_str(urlstr).unwrap();
1015 assert_eq!(&url.scheme, &~"scheme");
1016 assert_eq!(&url.host, &~"host");
1017 assert_eq!(&url.port, &Some(~"1234"));
1018 assert_eq!(&url.path, &~"/");
1022 fn test_url_with_underscores() {
1023 let urlstr = ~"http://dotcom.com/file_name.html";
1024 let url = from_str(urlstr).unwrap();
1025 assert!(url.path == ~"/file_name.html");
1029 fn test_path_with_underscores() {
1030 let pathstr = ~"/file_name.html";
1031 let path = path_from_str(pathstr).unwrap();
1032 assert!(path.path == ~"/file_name.html");
1036 fn test_url_with_dashes() {
1037 let urlstr = ~"http://dotcom.com/file-name.html";
1038 let url = from_str(urlstr).unwrap();
1039 assert!(url.path == ~"/file-name.html");
1043 fn test_path_with_dashes() {
1044 let pathstr = ~"/file-name.html";
1045 let path = path_from_str(pathstr).unwrap();
1046 assert!(path.path == ~"/file-name.html");
1050 fn test_no_scheme() {
1051 assert!(get_scheme("noschemehere.html").is_err());
1055 fn test_invalid_scheme_errors() {
1056 assert!(from_str("99://something").is_err());
1057 assert!(from_str("://something").is_err());
1061 fn test_full_url_parse_and_format() {
1062 let url = ~"http://user:pass@rust-lang.org/doc?s=v#something";
1063 assert_eq!(from_str(url).unwrap().to_str(), url);
1067 fn test_userless_url_parse_and_format() {
1068 let url = ~"http://rust-lang.org/doc?s=v#something";
1069 assert_eq!(from_str(url).unwrap().to_str(), url);
1073 fn test_queryless_url_parse_and_format() {
1074 let url = ~"http://user:pass@rust-lang.org/doc#something";
1075 assert_eq!(from_str(url).unwrap().to_str(), url);
1079 fn test_empty_query_url_parse_and_format() {
1080 let url = ~"http://user:pass@rust-lang.org/doc?#something";
1081 let should_be = ~"http://user:pass@rust-lang.org/doc#something";
1082 assert_eq!(from_str(url).unwrap().to_str(), should_be);
1086 fn test_fragmentless_url_parse_and_format() {
1087 let url = ~"http://user:pass@rust-lang.org/doc?q=v";
1088 assert_eq!(from_str(url).unwrap().to_str(), url);
1092 fn test_minimal_url_parse_and_format() {
1093 let url = ~"http://rust-lang.org/doc";
1094 assert_eq!(from_str(url).unwrap().to_str(), url);
1098 fn test_url_with_port_parse_and_format() {
1099 let url = ~"http://rust-lang.org:80/doc";
1100 assert_eq!(from_str(url).unwrap().to_str(), url);
1104 fn test_scheme_host_only_url_parse_and_format() {
1105 let url = ~"http://rust-lang.org";
1106 assert_eq!(from_str(url).unwrap().to_str(), url);
1110 fn test_pathless_url_parse_and_format() {
1111 let url = ~"http://user:pass@rust-lang.org?q=v#something";
1112 assert_eq!(from_str(url).unwrap().to_str(), url);
1116 fn test_scheme_host_fragment_only_url_parse_and_format() {
1117 let url = ~"http://rust-lang.org#something";
1118 assert_eq!(from_str(url).unwrap().to_str(), url);
1122 fn test_url_component_encoding() {
1123 let url = ~"http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B";
1124 let u = from_str(url).unwrap();
1125 assert!(u.path == ~"/doc uments");
1126 assert!(u.query == vec!((~"ba%d ", ~"#&+")));
1130 fn test_path_component_encoding() {
1131 let path = ~"/doc%20uments?ba%25d%20=%23%26%2B";
1132 let p = path_from_str(path).unwrap();
1133 assert!(p.path == ~"/doc uments");
1134 assert!(p.query == vec!((~"ba%d ", ~"#&+")));
1138 fn test_url_without_authority() {
1139 let url = ~"mailto:test@email.com";
1140 assert_eq!(from_str(url).unwrap().to_str(), url);
1145 assert_eq!(encode(""), ~"");
1146 assert_eq!(encode("http://example.com"), ~"http://example.com");
1147 assert_eq!(encode("foo bar% baz"), ~"foo%20bar%25%20baz");
1148 assert_eq!(encode(" "), ~"%20");
1149 assert_eq!(encode("!"), ~"!");
1150 assert_eq!(encode("\""), ~"\"");
1151 assert_eq!(encode("#"), ~"#");
1152 assert_eq!(encode("$"), ~"$");
1153 assert_eq!(encode("%"), ~"%25");
1154 assert_eq!(encode("&"), ~"&");
1155 assert_eq!(encode("'"), ~"%27");
1156 assert_eq!(encode("("), ~"(");
1157 assert_eq!(encode(")"), ~")");
1158 assert_eq!(encode("*"), ~"*");
1159 assert_eq!(encode("+"), ~"+");
1160 assert_eq!(encode(","), ~",");
1161 assert_eq!(encode("/"), ~"/");
1162 assert_eq!(encode(":"), ~":");
1163 assert_eq!(encode(";"), ~";");
1164 assert_eq!(encode("="), ~"=");
1165 assert_eq!(encode("?"), ~"?");
1166 assert_eq!(encode("@"), ~"@");
1167 assert_eq!(encode("["), ~"[");
1168 assert_eq!(encode("]"), ~"]");
1172 fn test_encode_component() {
1173 assert_eq!(encode_component(""), ~"");
1174 assert!(encode_component("http://example.com") ==
1175 ~"http%3A%2F%2Fexample.com");
1176 assert!(encode_component("foo bar% baz") ==
1177 ~"foo%20bar%25%20baz");
1178 assert_eq!(encode_component(" "), ~"%20");
1179 assert_eq!(encode_component("!"), ~"%21");
1180 assert_eq!(encode_component("#"), ~"%23");
1181 assert_eq!(encode_component("$"), ~"%24");
1182 assert_eq!(encode_component("%"), ~"%25");
1183 assert_eq!(encode_component("&"), ~"%26");
1184 assert_eq!(encode_component("'"), ~"%27");
1185 assert_eq!(encode_component("("), ~"%28");
1186 assert_eq!(encode_component(")"), ~"%29");
1187 assert_eq!(encode_component("*"), ~"%2A");
1188 assert_eq!(encode_component("+"), ~"%2B");
1189 assert_eq!(encode_component(","), ~"%2C");
1190 assert_eq!(encode_component("/"), ~"%2F");
1191 assert_eq!(encode_component(":"), ~"%3A");
1192 assert_eq!(encode_component(";"), ~"%3B");
1193 assert_eq!(encode_component("="), ~"%3D");
1194 assert_eq!(encode_component("?"), ~"%3F");
1195 assert_eq!(encode_component("@"), ~"%40");
1196 assert_eq!(encode_component("["), ~"%5B");
1197 assert_eq!(encode_component("]"), ~"%5D");
1202 assert_eq!(decode(""), ~"");
1203 assert_eq!(decode("abc/def 123"), ~"abc/def 123");
1204 assert_eq!(decode("abc%2Fdef%20123"), ~"abc%2Fdef 123");
1205 assert_eq!(decode("%20"), ~" ");
1206 assert_eq!(decode("%21"), ~"%21");
1207 assert_eq!(decode("%22"), ~"%22");
1208 assert_eq!(decode("%23"), ~"%23");
1209 assert_eq!(decode("%24"), ~"%24");
1210 assert_eq!(decode("%25"), ~"%");
1211 assert_eq!(decode("%26"), ~"%26");
1212 assert_eq!(decode("%27"), ~"'");
1213 assert_eq!(decode("%28"), ~"%28");
1214 assert_eq!(decode("%29"), ~"%29");
1215 assert_eq!(decode("%2A"), ~"%2A");
1216 assert_eq!(decode("%2B"), ~"%2B");
1217 assert_eq!(decode("%2C"), ~"%2C");
1218 assert_eq!(decode("%2F"), ~"%2F");
1219 assert_eq!(decode("%3A"), ~"%3A");
1220 assert_eq!(decode("%3B"), ~"%3B");
1221 assert_eq!(decode("%3D"), ~"%3D");
1222 assert_eq!(decode("%3F"), ~"%3F");
1223 assert_eq!(decode("%40"), ~"%40");
1224 assert_eq!(decode("%5B"), ~"%5B");
1225 assert_eq!(decode("%5D"), ~"%5D");
1229 fn test_decode_component() {
1230 assert_eq!(decode_component(""), ~"");
1231 assert_eq!(decode_component("abc/def 123"), ~"abc/def 123");
1232 assert_eq!(decode_component("abc%2Fdef%20123"), ~"abc/def 123");
1233 assert_eq!(decode_component("%20"), ~" ");
1234 assert_eq!(decode_component("%21"), ~"!");
1235 assert_eq!(decode_component("%22"), ~"\"");
1236 assert_eq!(decode_component("%23"), ~"#");
1237 assert_eq!(decode_component("%24"), ~"$");
1238 assert_eq!(decode_component("%25"), ~"%");
1239 assert_eq!(decode_component("%26"), ~"&");
1240 assert_eq!(decode_component("%27"), ~"'");
1241 assert_eq!(decode_component("%28"), ~"(");
1242 assert_eq!(decode_component("%29"), ~")");
1243 assert_eq!(decode_component("%2A"), ~"*");
1244 assert_eq!(decode_component("%2B"), ~"+");
1245 assert_eq!(decode_component("%2C"), ~",");
1246 assert_eq!(decode_component("%2F"), ~"/");
1247 assert_eq!(decode_component("%3A"), ~":");
1248 assert_eq!(decode_component("%3B"), ~";");
1249 assert_eq!(decode_component("%3D"), ~"=");
1250 assert_eq!(decode_component("%3F"), ~"?");
1251 assert_eq!(decode_component("%40"), ~"@");
1252 assert_eq!(decode_component("%5B"), ~"[");
1253 assert_eq!(decode_component("%5D"), ~"]");
1257 fn test_encode_form_urlencoded() {
1258 let mut m = HashMap::new();
1259 assert_eq!(encode_form_urlencoded(&m), ~"");
1261 m.insert(~"", vec!());
1262 m.insert(~"foo", vec!());
1263 assert_eq!(encode_form_urlencoded(&m), ~"");
1265 let mut m = HashMap::new();
1266 m.insert(~"foo", vec!(~"bar", ~"123"));
1267 assert_eq!(encode_form_urlencoded(&m), ~"foo=bar&foo=123");
1269 let mut m = HashMap::new();
1270 m.insert(~"foo bar", vec!(~"abc", ~"12 = 34"));
1271 assert!(encode_form_urlencoded(&m) ==
1272 ~"foo+bar=abc&foo+bar=12+%3D+34");
1276 fn test_decode_form_urlencoded() {
1277 assert_eq!(decode_form_urlencoded([]).len(), 0);
1279 let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes();
1280 let form = decode_form_urlencoded(s);
1281 assert_eq!(form.len(), 2);
1282 assert_eq!(form.get(&~"a"), &vec!(~"1"));
1283 assert_eq!(form.get(&~"foo bar"), &vec!(~"abc", ~"12 = 34"));