]> git.lizzy.rs Git - rust.git/blob - src/liburl/lib.rs
librustc: Don't try to perform the magical
[rust.git] / src / liburl / lib.rs
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.
4 //
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.
10
11 //! Types/fns concerning URLs (see RFC 3986)
12
13 #![crate_id = "url#0.11.0-pre"]
14 #![experimental]
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)]
23
24 use std::collections::HashMap;
25 use std::fmt;
26 use std::from_str::FromStr;
27 use std::hash;
28 use std::io::BufReader;
29 use std::string::String;
30 use std::uint;
31
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
34 /// port number.
35 ///
36 /// # Example
37 ///
38 /// ```rust
39 /// use url::{Url, UserInfo};
40 ///
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
49 /// ```
50 #[deriving(Clone, PartialEq, Eq)]
51 pub struct Url {
52     /// The scheme part of a URL, such as `https` in the above example.
53     pub scheme: String,
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`.
57     pub host: String,
58     /// A TCP port number, for example `8080`.
59     pub port: Option<String>,
60     /// The path component of a URL, for example `/foo/bar`.
61     pub path: String,
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.
65     pub query: Query,
66     /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
67     pub fragment: Option<String>
68 }
69
70 #[deriving(Clone, PartialEq)]
71 pub struct Path {
72     /// The path component of a URL, for example `/foo/bar`.
73     pub path: String,
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.
77     pub query: Query,
78     /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
79     pub fragment: Option<String>
80 }
81
82 /// An optional subcomponent of a URI authority component.
83 #[deriving(Clone, PartialEq, Eq)]
84 pub struct UserInfo {
85     /// The user name.
86     pub user: String,
87     /// Password or other scheme-specific authentication information.
88     pub pass: Option<String>
89 }
90
91 /// Represents the query component of a URI.
92 pub type Query = Vec<(String, String)>;
93
94 impl Url {
95     pub fn new(scheme: String,
96                user: Option<UserInfo>,
97                host: String,
98                port: Option<String>,
99                path: String,
100                query: Query,
101                fragment: Option<String>)
102                -> Url {
103         Url {
104             scheme: scheme,
105             user: user,
106             host: host,
107             port: port,
108             path: path,
109             query: query,
110             fragment: fragment,
111         }
112     }
113 }
114
115 impl Path {
116     pub fn new(path: String,
117                query: Query,
118                fragment: Option<String>)
119                -> Path {
120         Path {
121             path: path,
122             query: query,
123             fragment: fragment,
124         }
125     }
126 }
127
128 impl UserInfo {
129     #[inline]
130     pub fn new(user: String, pass: Option<String>) -> UserInfo {
131         UserInfo { user: user, pass: pass }
132     }
133 }
134
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();
138
139     loop {
140         let mut buf = [0];
141         let ch = match rdr.read(buf) {
142             Err(..) => break,
143             Ok(..) => buf[0] as char,
144         };
145
146         match ch {
147           // unreserved:
148           'A' .. 'Z' |
149           'a' .. 'z' |
150           '0' .. '9' |
151           '-' | '.' | '_' | '~' => {
152             out.push_char(ch);
153           }
154           _ => {
155               if full_url {
156                 match ch {
157                   // gen-delims:
158                   ':' | '/' | '?' | '#' | '[' | ']' | '@' |
159
160                   // sub-delims:
161                   '!' | '$' | '&' | '"' | '(' | ')' | '*' |
162                   '+' | ',' | ';' | '=' => {
163                     out.push_char(ch);
164                   }
165
166                   _ => out.push_str(format!("%{:02X}", ch as uint).as_slice())
167                 }
168             } else {
169                 out.push_str(format!("%{:02X}", ch as uint).as_slice());
170             }
171           }
172         }
173     }
174
175     out
176 }
177
178 /**
179  * Encodes a URI by replacing reserved characters with percent-encoded
180  * character sequences.
181  *
182  * This function is compliant with RFC 3986.
183  *
184  * # Example
185  *
186  * ```rust
187  * use url::encode;
188  *
189  * let url = encode("https://example.com/Rust (programming language)");
190  * println!("{}", url); // https://example.com/Rust%20(programming%20language)
191  * ```
192  */
193 pub fn encode(s: &str) -> String {
194     encode_inner(s, true)
195 }
196
197 /**
198  * Encodes a URI component by replacing reserved characters with percent-
199  * encoded character sequences.
200  *
201  * This function is compliant with RFC 3986.
202  */
203
204 pub fn encode_component(s: &str) -> String {
205     encode_inner(s, false)
206 }
207
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();
211
212     loop {
213         let mut buf = [0];
214         let ch = match rdr.read(buf) {
215             Err(..) => break,
216             Ok(..) => buf[0] as char
217         };
218         match ch {
219           '%' => {
220             let mut bytes = [0, 0];
221             match rdr.read(bytes) {
222                 Ok(2) => {}
223                 _ => fail!() // FIXME: malformed url?
224             }
225             let ch = uint::parse_bytes(bytes, 16u).unwrap() as u8 as char;
226
227             if full_url {
228                 // Only decode some characters:
229                 match ch {
230                   // gen-delims:
231                   ':' | '/' | '?' | '#' | '[' | ']' | '@' |
232
233                   // sub-delims:
234                   '!' | '$' | '&' | '"' | '(' | ')' | '*' |
235                   '+' | ',' | ';' | '=' => {
236                     out.push_char('%');
237                     out.push_char(bytes[0u] as char);
238                     out.push_char(bytes[1u] as char);
239                   }
240
241                   ch => out.push_char(ch)
242                 }
243             } else {
244                   out.push_char(ch);
245             }
246           }
247           ch => out.push_char(ch)
248         }
249     }
250
251     out
252 }
253
254 /**
255  * Decodes a percent-encoded string representing a URI.
256  *
257  * This will only decode escape sequences generated by `encode`.
258  *
259  * # Example
260  *
261  * ```rust
262  * use url::decode;
263  *
264  * let url = decode("https://example.com/Rust%20(programming%20language)");
265  * println!("{}", url); // https://example.com/Rust (programming language)
266  * ```
267  */
268 pub fn decode(s: &str) -> String {
269     decode_inner(s, true)
270 }
271
272 /**
273  * Decode a string encoded with percent encoding.
274  */
275 pub fn decode_component(s: &str) -> String {
276     decode_inner(s, false)
277 }
278
279 fn encode_plus(s: &str) -> String {
280     let mut rdr = BufReader::new(s.as_bytes());
281     let mut out = String::new();
282
283     loop {
284         let mut buf = [0];
285         let ch = match rdr.read(buf) {
286             Ok(..) => buf[0] as char,
287             Err(..) => break,
288         };
289         match ch {
290           'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => {
291             out.push_char(ch);
292           }
293           ' ' => out.push_char('+'),
294           _ => out.push_str(format!("%{:X}", ch as uint).as_slice())
295         }
296     }
297
298     out
299 }
300
301 /**
302  * Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
303  */
304 pub fn encode_form_urlencoded(m: &HashMap<String, Vec<String>>) -> String {
305     let mut out = String::new();
306     let mut first = true;
307
308     for (key, values) in m.iter() {
309         let key = encode_plus(key.as_slice());
310
311         for value in values.iter() {
312             if first {
313                 first = false;
314             } else {
315                 out.push_char('&');
316                 first = false;
317             }
318
319             out.push_str(format!("{}={}",
320                                  key,
321                                  encode_plus(value.as_slice())).as_slice());
322         }
323     }
324
325     out
326 }
327
328 /**
329  * Decode a string encoded with the 'application/x-www-form-urlencoded' media
330  * type into a hashmap.
331  */
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;
339
340     loop {
341         let mut buf = [0];
342         let ch = match rdr.read(buf) {
343             Ok(..) => buf[0] as char,
344             Err(..) => break,
345         };
346         match ch {
347             '&' | ';' => {
348                 if key.len() > 0 && value.len() > 0 {
349                     let mut values = match m.pop_equiv(&key.as_slice()) {
350                         Some(values) => values,
351                         None => vec!(),
352                     };
353
354                     values.push(value);
355                     m.insert(key, values);
356                 }
357
358                 parsing_key = true;
359                 key = String::new();
360                 value = String::new();
361             }
362             '=' => parsing_key = false,
363             ch => {
364                 let ch = match ch {
365                     '%' => {
366                         let mut bytes = [0, 0];
367                         match rdr.read(bytes) {
368                             Ok(2) => {}
369                             _ => fail!() // FIXME: malformed?
370                         }
371                         uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
372                     }
373                     '+' => ' ',
374                     ch => ch
375                 };
376
377                 if parsing_key {
378                     key.push_char(ch)
379                 } else {
380                     value.push_char(ch)
381                 }
382             }
383         }
384     }
385
386     if key.len() > 0 && value.len() > 0 {
387         let mut values = match m.pop_equiv(&key.as_slice()) {
388             Some(values) => values,
389             None => vec!(),
390         };
391
392         values.push(value);
393         m.insert(key, values);
394     }
395
396     m
397 }
398
399
400 fn split_char_first(s: &str, c: char) -> (String, String) {
401     let len = s.len();
402     let mut index = len;
403     let mut mat = 0;
404     let mut rdr = BufReader::new(s.as_bytes());
405     loop {
406         let mut buf = [0];
407         let ch = match rdr.read(buf) {
408             Ok(..) => buf[0] as char,
409             Err(..) => break,
410         };
411         if ch == c {
412             // found a match, adjust markers
413             index = (rdr.tell().unwrap() as uint) - 1;
414             mat = 1;
415             break;
416         }
417     }
418     if index+mat == len {
419         return (s.slice(0, index).to_string(), "".to_string());
420     } else {
421         return (s.slice(0, index).to_string(),
422                 s.slice(index + mat, s.len()).to_string());
423     }
424 }
425
426 impl fmt::Show for UserInfo {
427     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
428         match self.pass {
429             Some(ref pass) => write!(f, "{}:{}@", self.user, *pass),
430             None => write!(f, "{}@", self.user),
431         }
432     }
433 }
434
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())));
442         };
443     }
444     return query;
445 }
446
447 /**
448  * Converts an instance of a URI `Query` type to a string.
449  *
450  * # Example
451  *
452  * ```rust
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
457  * ```
458  */
459 #[allow(unused_must_use)]
460 pub fn query_to_str(query: &Query) -> String {
461     use std::io::MemWriter;
462     use std::str;
463
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()));
469     }
470     str::from_utf8_lossy(writer.unwrap().as_slice()).to_string()
471 }
472
473 /**
474  * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
475  *
476  * Does not include the separating `:` character.
477  *
478  * # Example
479  *
480  * ```rust
481  * use url::get_scheme;
482  *
483  * let scheme = match get_scheme("https://example.com/") {
484  *     Ok((sch, _)) => sch,
485  *     Err(_) => "(None)".to_string(),
486  * };
487  * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
488  * ```
489  */
490 pub fn get_scheme(rawurl: &str) -> Result<(String, String), String> {
491     for (i,c) in rawurl.chars().enumerate() {
492         match c {
493           'A' .. 'Z' | 'a' .. 'z' => continue,
494           '0' .. '9' | '+' | '-' | '.' => {
495             if i == 0 {
496                 return Err("url: Scheme must begin with a \
497                             letter.".to_string());
498             }
499             continue;
500           }
501           ':' => {
502             if i == 0 {
503                 return Err("url: Scheme cannot be empty.".to_string());
504             } else {
505                 return Ok((rawurl.slice(0,i).to_string(),
506                            rawurl.slice(i+1,rawurl.len()).to_string()));
507             }
508           }
509           _ => {
510             return Err("url: Invalid character in scheme.".to_string());
511           }
512         }
513     };
514     return Err("url: Scheme must be terminated with a colon.".to_string());
515 }
516
517 #[deriving(Clone, PartialEq)]
518 enum Input {
519     Digit, // all digits
520     Hex, // digits and letters a-f
521     Unreserved // all other legal characters
522 }
523
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()));
530     }
531
532     enum State {
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
539     }
540
541     let len = rawurl.len();
542     let mut st = Start;
543     let mut input = Digit; // most restricted, start here.
544
545     let mut userinfo = None;
546     let mut host = "".to_string();
547     let mut port = None;
548
549     let mut colon_count = 0;
550     let mut pos = 0;
551     let mut begin = 2;
552     let mut end = len;
553
554     for (i,c) in rawurl.chars().enumerate() {
555         if i < 2 { continue; } // ignore the leading //
556
557         // deal with input class first
558         match c {
559           '0' .. '9' => (),
560           'A' .. 'F' | 'a' .. 'f' => {
561             if input == Digit {
562                 input = Hex;
563             }
564           }
565           'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
566           '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
567             input = Unreserved;
568           }
569           ':' | '@' | '?' | '#' | '/' => {
570             // separators, don't change anything
571           }
572           _ => {
573             return Err("Illegal character in authority".to_string());
574           }
575         }
576
577         // now process states
578         match c {
579           ':' => {
580             colon_count += 1;
581             match st {
582               Start => {
583                 pos = i;
584                 st = PassHostPort;
585               }
586               PassHostPort => {
587                 // multiple colons means ipv6 address.
588                 if input == Unreserved {
589                     return Err(
590                         "Illegal characters in IPv6 address.".to_string());
591                 }
592                 st = Ip6Host;
593               }
594               InHost => {
595                 pos = i;
596                 if input == Unreserved {
597                     // must be port
598                     host = rawurl.slice(begin, i).to_string();
599                     st = InPort;
600                 } else {
601                     // can't be sure whether this is an ipv6 address or a port
602                     st = Ip6Port;
603                 }
604               }
605               Ip6Port => {
606                 if input == Unreserved {
607                     return Err("Illegal characters in \
608                                 authority.".to_string());
609                 }
610                 st = Ip6Host;
611               }
612               Ip6Host => {
613                 if colon_count > 7 {
614                     host = rawurl.slice(begin, i).to_string();
615                     pos = i;
616                     st = InPort;
617                 }
618               }
619               _ => {
620                 return Err("Invalid ':' in authority.".to_string());
621               }
622             }
623             input = Digit; // reset input class
624           }
625
626           '@' => {
627             input = Digit; // reset input class
628             colon_count = 0; // reset count
629             match st {
630               Start => {
631                 let user = rawurl.slice(begin, i).to_string();
632                 userinfo = Some(UserInfo::new(user, None));
633                 st = InHost;
634               }
635               PassHostPort => {
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)));
639                 st = InHost;
640               }
641               _ => {
642                 return Err("Invalid '@' in authority.".to_string());
643               }
644             }
645             begin = i+1;
646           }
647
648           '?' | '#' | '/' => {
649             end = i;
650             break;
651           }
652           _ => ()
653         }
654     }
655
656     // finish up
657     match st {
658       Start => {
659         host = rawurl.slice(begin, end).to_string();
660       }
661       PassHostPort | Ip6Port => {
662         if input != Digit {
663             return Err("Non-digit characters in port.".to_string());
664         }
665         host = rawurl.slice(begin, pos).to_string();
666         port = Some(rawurl.slice(pos+1, end).to_string());
667       }
668       Ip6Host | InHost => {
669         host = rawurl.slice(begin, end).to_string();
670       }
671       InPort => {
672         if input != Digit {
673             return Err("Non-digit characters in port.".to_string());
674         }
675         port = Some(rawurl.slice(pos+1, end).to_string());
676       }
677     }
678
679     let rest = rawurl.slice(end, len).to_string();
680     return Ok((userinfo, host, port, rest));
681 }
682
683
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();
688     let mut end = len;
689     for (i,c) in rawurl.chars().enumerate() {
690         match c {
691           'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
692           | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
693           | '_' | '-' | '~' => {
694             continue;
695           }
696           '?' | '#' => {
697             end = i;
698             break;
699           }
700           _ => return Err("Invalid character in path.".to_string())
701         }
702     }
703
704     if authority {
705         if end != 0 && !rawurl.starts_with("/") {
706             return Err("Non-empty path must begin with\
707                               '/' in presence of authority.".to_string());
708         }
709     }
710
711     return Ok((decode_component(rawurl.slice(0, end)),
712                     rawurl.slice(end, len).to_string()));
713 }
714
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(
721                                                 1,
722                                                 rawurl.len()));
723             return Ok((vec!(), Some(f)));
724         } else {
725             return Ok((vec!(), None));
726         }
727     }
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()))
731     } else {
732         None
733     };
734     return Ok((query_from_str(q.as_slice()), f));
735 }
736
737 /**
738  * Parses a URL, converting it from a string to `Url` representation.
739  *
740  * # Arguments
741  *
742  * `rawurl` - a string representing the full URL, including scheme.
743  *
744  * # Returns
745  *
746  * A `Url` struct type representing the URL.
747  */
748 pub fn from_str(rawurl: &str) -> Result<Url, String> {
749     // scheme
750     let (scheme, rest) = match get_scheme(rawurl) {
751         Ok(val) => val,
752         Err(e) => return Err(e),
753     };
754
755     // authority
756     let (userinfo, host, port, rest) = match get_authority(rest.as_slice()) {
757         Ok(val) => val,
758         Err(e) => return Err(e),
759     };
760
761     // path
762     let has_authority = host.len() > 0;
763     let (path, rest) = match get_path(rest.as_slice(), has_authority) {
764         Ok(val) => val,
765         Err(e) => return Err(e),
766     };
767
768     // query and fragment
769     let (query, fragment) = match get_query_fragment(rest.as_slice()) {
770         Ok(val) => val,
771         Err(e) => return Err(e),
772     };
773
774     Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
775 }
776
777 pub fn path_from_str(rawpath: &str) -> Result<Path, String> {
778     let (path, rest) = match get_path(rawpath, false) {
779         Ok(val) => val,
780         Err(e) => return Err(e)
781     };
782
783     // query and fragment
784     let (query, fragment) = match get_query_fragment(rest.as_slice()) {
785         Ok(val) => val,
786         Err(e) => return Err(e),
787     };
788
789     Ok(Path{ path: path, query: query, fragment: fragment })
790 }
791
792 impl FromStr for Url {
793     fn from_str(s: &str) -> Option<Url> {
794         match from_str(s) {
795             Ok(url) => Some(url),
796             Err(_) => None
797         }
798     }
799 }
800
801 impl FromStr for Path {
802     fn from_str(s: &str) -> Option<Path> {
803         match path_from_str(s) {
804             Ok(path) => Some(path),
805             Err(_) => None
806         }
807     }
808 }
809
810 impl fmt::Show for Url {
811     /**
812      * Converts a URL from `Url` to string representation.
813      *
814      * # Arguments
815      *
816      * `url` - a URL.
817      *
818      * # Returns
819      *
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".
824      */
825     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
826         try!(write!(f, "{}:", self.scheme));
827
828         if !self.host.is_empty() {
829             try!(write!(f, "//"));
830             match self.user {
831                 Some(ref user) => try!(write!(f, "{}", *user)),
832                 None => {}
833             }
834             match self.port {
835                 Some(ref port) => try!(write!(f, "{}:{}", self.host,
836                                                 *port)),
837                 None => try!(write!(f, "{}", self.host)),
838             }
839         }
840
841         try!(write!(f, "{}", self.path));
842
843         if !self.query.is_empty() {
844             try!(write!(f, "?{}", query_to_str(&self.query)));
845         }
846
847         match self.fragment {
848             Some(ref fragment) => {
849                 write!(f, "#{}", encode_component(fragment.as_slice()))
850             }
851             None => Ok(()),
852         }
853     }
854 }
855
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))
861         }
862
863         match self.fragment {
864             Some(ref fragment) => {
865                 write!(f, "#{}", encode_component(fragment.as_slice()))
866             }
867             None => Ok(())
868         }
869     }
870 }
871
872 impl<S: hash::Writer> hash::Hash<S> for Url {
873     fn hash(&self, state: &mut S) {
874         self.to_str().hash(state)
875     }
876 }
877
878 impl<S: hash::Writer> hash::Hash<S> for Path {
879     fn hash(&self, state: &mut S) {
880         self.to_str().hash(state)
881     }
882 }
883
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'
886
887 #[test]
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());
892
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());
896 }
897
898 #[test]
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());
906
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());
913
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());
920
921     // ipv6 tests
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());
925
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()));
930
931     let (u, h, p, _) = get_authority(
932         "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
933     ).unwrap();
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()));
937
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());
945
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());
951 }
952
953 #[test]
954 fn test_get_path() {
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());
964
965     //failure cases
966     assert!(get_path("something?q", true).is_err());
967 }
968
969 #[cfg(test)]
970 mod tests {
971     use {encode_form_urlencoded, decode_form_urlencoded,
972          decode, encode, from_str, encode_component, decode_component,
973          path_from_str, UserInfo, get_scheme};
974
975     use std::collections::HashMap;
976
977     #[test]
978     fn test_url_parse() {
979         let url = "http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
980
981         let up = from_str(url);
982         let u = up.unwrap();
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()));
990     }
991
992     #[test]
993     fn test_path_parse() {
994         let path = "/doc/~u?s=v#something";
995
996         let up = path_from_str(path);
997         let u = up.unwrap();
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()));
1001     }
1002
1003     #[test]
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());
1009     }
1010
1011     #[test]
1012     fn test_path_parse_host_slash() {
1013         let pathstr = "/";
1014         let path = path_from_str(pathstr).unwrap();
1015         assert!(path.path == "/".to_string());
1016     }
1017
1018     #[test]
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());
1033     }
1034
1035     #[test]
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());
1040     }
1041
1042     #[test]
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());
1047     }
1048
1049     #[test]
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());
1054     }
1055
1056     #[test]
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());
1061     }
1062
1063     #[test]
1064     fn test_no_scheme() {
1065         assert!(get_scheme("noschemehere.html").is_err());
1066     }
1067
1068     #[test]
1069     fn test_invalid_scheme_errors() {
1070         assert!(from_str("99://something").is_err());
1071         assert!(from_str("://something").is_err());
1072     }
1073
1074     #[test]
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);
1078     }
1079
1080     #[test]
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);
1084     }
1085
1086     #[test]
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);
1090     }
1091
1092     #[test]
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);
1097     }
1098
1099     #[test]
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);
1103     }
1104
1105     #[test]
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);
1109     }
1110
1111     #[test]
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);
1115     }
1116
1117     #[test]
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);
1121     }
1122
1123     #[test]
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);
1127     }
1128
1129     #[test]
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);
1133     }
1134
1135     #[test]
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())));
1141     }
1142
1143     #[test]
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())));
1149     }
1150
1151     #[test]
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);
1155     }
1156
1157     #[test]
1158     fn test_encode() {
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());
1185     }
1186
1187     #[test]
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());
1216     }
1217
1218     #[test]
1219     fn test_decode() {
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());
1244     }
1245
1246     #[test]
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());
1272     }
1273
1274     #[test]
1275     fn test_encode_form_urlencoded() {
1276         let mut m = HashMap::new();
1277         assert_eq!(encode_form_urlencoded(&m), "".to_string());
1278
1279         m.insert("".to_string(), vec!());
1280         m.insert("foo".to_string(), vec!());
1281         assert_eq!(encode_form_urlencoded(&m), "".to_string());
1282
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());
1286
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());
1291     }
1292
1293     #[test]
1294     fn test_decode_form_urlencoded() {
1295         assert_eq!(decode_form_urlencoded([]).len(), 0);
1296
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()));
1303     }
1304 }