]> git.lizzy.rs Git - rust.git/blob - src/liburl/lib.rs
Convert most code to new inner attribute syntax.
[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.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)]
21
22 extern crate collections;
23
24 use std::cmp::Eq;
25 use std::fmt;
26 use std::hash::Hash;
27 use std::io::BufReader;
28 use std::from_str::FromStr;
29 use std::uint;
30
31 use collections::HashMap;
32
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
35 /// port number.
36 ///
37 /// # Example
38 ///
39 /// ```rust
40 /// use url::{Url, UserInfo};
41 ///
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
50 /// ```
51 #[deriving(Clone, Eq, TotalEq)]
52 pub struct Url {
53     /// The scheme part of a URL, such as `https` in the above example.
54     scheme: ~str,
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`.
58     host: ~str,
59     /// A TCP port number, for example `8080`.
60     port: Option<~str>,
61     /// The path component of a URL, for example `/foo/bar`.
62     path: ~str,
63     /// The query component of a URL.  `vec!((~"baz", ~"qux"))` represents the
64     /// fragment `baz=qux` in the above example.
65     query: Query,
66     /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
67     fragment: Option<~str>
68 }
69
70 #[deriving(Clone, Eq)]
71 pub struct Path {
72     /// The path component of a URL, for example `/foo/bar`.
73     path: ~str,
74     /// The query component of a URL.  `vec!((~"baz", ~"qux"))` represents the
75     /// fragment `baz=qux` in the above example.
76     query: Query,
77     /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
78     fragment: Option<~str>
79 }
80
81 /// An optional subcomponent of a URI authority component.
82 #[deriving(Clone, Eq, TotalEq)]
83 pub struct UserInfo {
84     /// The user name.
85     user: ~str,
86     /// Password or other scheme-specific authentication information.
87     pass: Option<~str>
88 }
89
90 /// Represents the query component of a URI.
91 pub type Query = Vec<(~str, ~str)>;
92
93 impl Url {
94     pub fn new(scheme: ~str,
95                user: Option<UserInfo>,
96                host: ~str,
97                port: Option<~str>,
98                path: ~str,
99                query: Query,
100                fragment: Option<~str>)
101                -> Url {
102         Url {
103             scheme: scheme,
104             user: user,
105             host: host,
106             port: port,
107             path: path,
108             query: query,
109             fragment: fragment,
110         }
111     }
112 }
113
114 impl Path {
115     pub fn new(path: ~str,
116                query: Query,
117                fragment: Option<~str>)
118                -> Path {
119         Path {
120             path: path,
121             query: query,
122             fragment: fragment,
123         }
124     }
125 }
126
127 impl UserInfo {
128     #[inline]
129     pub fn new(user: ~str, pass: Option<~str>) -> UserInfo {
130         UserInfo { user: user, pass: pass }
131     }
132 }
133
134 fn encode_inner(s: &str, full_url: bool) -> ~str {
135     let mut rdr = BufReader::new(s.as_bytes());
136     let mut out = ~"";
137
138     loop {
139         let mut buf = [0];
140         let ch = match rdr.read(buf) {
141             Err(..) => break,
142             Ok(..) => buf[0] as char,
143         };
144
145         match ch {
146           // unreserved:
147           'A' .. 'Z' |
148           'a' .. 'z' |
149           '0' .. '9' |
150           '-' | '.' | '_' | '~' => {
151             out.push_char(ch);
152           }
153           _ => {
154               if full_url {
155                 match ch {
156                   // gen-delims:
157                   ':' | '/' | '?' | '#' | '[' | ']' | '@' |
158
159                   // sub-delims:
160                   '!' | '$' | '&' | '"' | '(' | ')' | '*' |
161                   '+' | ',' | ';' | '=' => {
162                     out.push_char(ch);
163                   }
164
165                   _ => out.push_str(format!("%{:X}", ch as uint))
166                 }
167             } else {
168                 out.push_str(format!("%{:X}", ch as uint));
169             }
170           }
171         }
172     }
173
174     out
175 }
176
177 /**
178  * Encodes a URI by replacing reserved characters with percent-encoded
179  * character sequences.
180  *
181  * This function is compliant with RFC 3986.
182  *
183  * # Example
184  *
185  * ```rust
186  * use url::encode;
187  *
188  * let url = encode(&"https://example.com/Rust (programming language)");
189  * println!("{}", url); // https://example.com/Rust%20(programming%20language)
190  * ```
191  */
192 pub fn encode(s: &str) -> ~str {
193     encode_inner(s, true)
194 }
195
196 /**
197  * Encodes a URI component by replacing reserved characters with percent
198  * encoded character sequences.
199  *
200  * This function is compliant with RFC 3986.
201  */
202
203 pub fn encode_component(s: &str) -> ~str {
204     encode_inner(s, false)
205 }
206
207 fn decode_inner(s: &str, full_url: bool) -> ~str {
208     let mut rdr = BufReader::new(s.as_bytes());
209     let mut out = ~"";
210
211     loop {
212         let mut buf = [0];
213         let ch = match rdr.read(buf) {
214             Err(..) => break,
215             Ok(..) => buf[0] as char
216         };
217         match ch {
218           '%' => {
219             let mut bytes = [0, 0];
220             match rdr.read(bytes) {
221                 Ok(2) => {}
222                 _ => fail!() // FIXME: malformed url?
223             }
224             let ch = uint::parse_bytes(bytes, 16u).unwrap() as u8 as char;
225
226             if full_url {
227                 // Only decode some characters:
228                 match ch {
229                   // gen-delims:
230                   ':' | '/' | '?' | '#' | '[' | ']' | '@' |
231
232                   // sub-delims:
233                   '!' | '$' | '&' | '"' | '(' | ')' | '*' |
234                   '+' | ',' | ';' | '=' => {
235                     out.push_char('%');
236                     out.push_char(bytes[0u] as char);
237                     out.push_char(bytes[1u] as char);
238                   }
239
240                   ch => out.push_char(ch)
241                 }
242             } else {
243                   out.push_char(ch);
244             }
245           }
246           ch => out.push_char(ch)
247         }
248     }
249
250     out
251 }
252
253 /**
254  * Decodes a percent-encoded string representing a URI.
255  *
256  * This will only decode escape sequences generated by `encode`.
257  *
258  * # Example
259  *
260  * ```rust
261  * use url::decode;
262  *
263  * let url = decode(&"https://example.com/Rust%20(programming%20language)");
264  * println!("{}", url); // https://example.com/Rust (programming language)
265  * ```
266  */
267 pub fn decode(s: &str) -> ~str {
268     decode_inner(s, true)
269 }
270
271 /**
272  * Decode a string encoded with percent encoding.
273  */
274 pub fn decode_component(s: &str) -> ~str {
275     decode_inner(s, false)
276 }
277
278 fn encode_plus(s: &str) -> ~str {
279     let mut rdr = BufReader::new(s.as_bytes());
280     let mut out = ~"";
281
282     loop {
283         let mut buf = [0];
284         let ch = match rdr.read(buf) {
285             Ok(..) => buf[0] as char,
286             Err(..) => break,
287         };
288         match ch {
289           'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => {
290             out.push_char(ch);
291           }
292           ' ' => out.push_char('+'),
293           _ => out.push_str(format!("%{:X}", ch as uint))
294         }
295     }
296
297     out
298 }
299
300 /**
301  * Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
302  */
303 pub fn encode_form_urlencoded(m: &HashMap<~str, Vec<~str>>) -> ~str {
304     let mut out = ~"";
305     let mut first = true;
306
307     for (key, values) in m.iter() {
308         let key = encode_plus(*key);
309
310         for value in values.iter() {
311             if first {
312                 first = false;
313             } else {
314                 out.push_char('&');
315                 first = false;
316             }
317
318             out.push_str(format!("{}={}", key, encode_plus(*value)));
319         }
320     }
321
322     out
323 }
324
325 /**
326  * Decode a string encoded with the 'application/x-www-form-urlencoded' media
327  * type into a hashmap.
328  */
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();
332     let mut key = ~"";
333     let mut value = ~"";
334     let mut parsing_key = true;
335
336     loop {
337         let mut buf = [0];
338         let ch = match rdr.read(buf) {
339             Ok(..) => buf[0] as char,
340             Err(..) => break,
341         };
342         match ch {
343             '&' | ';' => {
344                 if key != ~"" && value != ~"" {
345                     let mut values = match m.pop(&key) {
346                         Some(values) => values,
347                         None => vec!(),
348                     };
349
350                     values.push(value);
351                     m.insert(key, values);
352                 }
353
354                 parsing_key = true;
355                 key = ~"";
356                 value = ~"";
357             }
358             '=' => parsing_key = false,
359             ch => {
360                 let ch = match ch {
361                     '%' => {
362                         let mut bytes = [0, 0];
363                         match rdr.read(bytes) {
364                             Ok(2) => {}
365                             _ => fail!() // FIXME: malformed?
366                         }
367                         uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
368                     }
369                     '+' => ' ',
370                     ch => ch
371                 };
372
373                 if parsing_key {
374                     key.push_char(ch)
375                 } else {
376                     value.push_char(ch)
377                 }
378             }
379         }
380     }
381
382     if key != ~"" && value != ~"" {
383         let mut values = match m.pop(&key) {
384             Some(values) => values,
385             None => vec!(),
386         };
387
388         values.push(value);
389         m.insert(key, values);
390     }
391
392     m
393 }
394
395
396 fn split_char_first(s: &str, c: char) -> (~str, ~str) {
397     let len = s.len();
398     let mut index = len;
399     let mut mat = 0;
400     let mut rdr = BufReader::new(s.as_bytes());
401     loop {
402         let mut buf = [0];
403         let ch = match rdr.read(buf) {
404             Ok(..) => buf[0] as char,
405             Err(..) => break,
406         };
407         if ch == c {
408             // found a match, adjust markers
409             index = (rdr.tell().unwrap() as uint) - 1;
410             mat = 1;
411             break;
412         }
413     }
414     if index+mat == len {
415         return (s.slice(0, index).to_owned(), ~"");
416     } else {
417         return (s.slice(0, index).to_owned(),
418              s.slice(index + mat, s.len()).to_owned());
419     }
420 }
421
422 impl fmt::Show for UserInfo {
423     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424         match self.pass {
425             Some(ref pass) => write!(f.buf, "{}:{}@", self.user, *pass),
426             None => write!(f.buf, "{}@", self.user),
427         }
428     }
429 }
430
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)));
437         };
438     }
439     return query;
440 }
441
442 /**
443  * Converts an instance of a URI `Query` type to a string.
444  *
445  * # Example
446  *
447  * ```rust
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
450  * ```
451  */
452 #[allow(unused_must_use)]
453 pub fn query_to_str(query: &Query) -> ~str {
454     use std::io::MemWriter;
455     use std::str;
456
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));
462     }
463     str::from_utf8_lossy(writer.unwrap()).into_owned()
464 }
465
466 /**
467  * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
468  *
469  * Does not include the separating `:` character.
470  *
471  * # Example
472  *
473  * ```rust
474  * use url::get_scheme;
475  *
476  * let scheme = match get_scheme("https://example.com/") {
477  *     Ok((sch, _)) => sch,
478  *     Err(_) => ~"(None)",
479  * };
480  * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
481  * ```
482  */
483 pub fn get_scheme(rawurl: &str) -> Result<(~str, ~str), ~str> {
484     for (i,c) in rawurl.chars().enumerate() {
485         match c {
486           'A' .. 'Z' | 'a' .. 'z' => continue,
487           '0' .. '9' | '+' | '-' | '.' => {
488             if i == 0 {
489                 return Err(~"url: Scheme must begin with a letter.");
490             }
491             continue;
492           }
493           ':' => {
494             if i == 0 {
495                 return Err(~"url: Scheme cannot be empty.");
496             } else {
497                 return Ok((rawurl.slice(0,i).to_owned(),
498                                 rawurl.slice(i+1,rawurl.len()).to_owned()));
499             }
500           }
501           _ => {
502             return Err(~"url: Invalid character in scheme.");
503           }
504         }
505     };
506     return Err(~"url: Scheme must be terminated with a colon.");
507 }
508
509 #[deriving(Clone, Eq)]
510 enum Input {
511     Digit, // all digits
512     Hex, // digits and letters a-f
513     Unreserved // all other legal characters
514 }
515
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()));
522     }
523
524     enum State {
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
531     }
532
533     let len = rawurl.len();
534     let mut st = Start;
535     let mut input = Digit; // most restricted, start here.
536
537     let mut userinfo = None;
538     let mut host = ~"";
539     let mut port = None;
540
541     let mut colon_count = 0;
542     let mut pos = 0;
543     let mut begin = 2;
544     let mut end = len;
545
546     for (i,c) in rawurl.chars().enumerate() {
547         if i < 2 { continue; } // ignore the leading //
548
549         // deal with input class first
550         match c {
551           '0' .. '9' => (),
552           'A' .. 'F' | 'a' .. 'f' => {
553             if input == Digit {
554                 input = Hex;
555             }
556           }
557           'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
558           '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
559             input = Unreserved;
560           }
561           ':' | '@' | '?' | '#' | '/' => {
562             // separators, don't change anything
563           }
564           _ => {
565             return Err(~"Illegal character in authority");
566           }
567         }
568
569         // now process states
570         match c {
571           ':' => {
572             colon_count += 1;
573             match st {
574               Start => {
575                 pos = i;
576                 st = PassHostPort;
577               }
578               PassHostPort => {
579                 // multiple colons means ipv6 address.
580                 if input == Unreserved {
581                     return Err(
582                         ~"Illegal characters in IPv6 address.");
583                 }
584                 st = Ip6Host;
585               }
586               InHost => {
587                 pos = i;
588                 if input == Unreserved {
589                     // must be port
590                     host = rawurl.slice(begin, i).to_owned();
591                     st = InPort;
592                 } else {
593                     // can't be sure whether this is an ipv6 address or a port
594                     st = Ip6Port;
595                 }
596               }
597               Ip6Port => {
598                 if input == Unreserved {
599                     return Err(~"Illegal characters in authority.");
600                 }
601                 st = Ip6Host;
602               }
603               Ip6Host => {
604                 if colon_count > 7 {
605                     host = rawurl.slice(begin, i).to_owned();
606                     pos = i;
607                     st = InPort;
608                 }
609               }
610               _ => {
611                 return Err(~"Invalid ':' in authority.");
612               }
613             }
614             input = Digit; // reset input class
615           }
616
617           '@' => {
618             input = Digit; // reset input class
619             colon_count = 0; // reset count
620             match st {
621               Start => {
622                 let user = rawurl.slice(begin, i).to_owned();
623                 userinfo = Some(UserInfo::new(user, None));
624                 st = InHost;
625               }
626               PassHostPort => {
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)));
630                 st = InHost;
631               }
632               _ => {
633                 return Err(~"Invalid '@' in authority.");
634               }
635             }
636             begin = i+1;
637           }
638
639           '?' | '#' | '/' => {
640             end = i;
641             break;
642           }
643           _ => ()
644         }
645     }
646
647     // finish up
648     match st {
649       Start => {
650         host = rawurl.slice(begin, end).to_owned();
651       }
652       PassHostPort | Ip6Port => {
653         if input != Digit {
654             return Err(~"Non-digit characters in port.");
655         }
656         host = rawurl.slice(begin, pos).to_owned();
657         port = Some(rawurl.slice(pos+1, end).to_owned());
658       }
659       Ip6Host | InHost => {
660         host = rawurl.slice(begin, end).to_owned();
661       }
662       InPort => {
663         if input != Digit {
664             return Err(~"Non-digit characters in port.");
665         }
666         port = Some(rawurl.slice(pos+1, end).to_owned());
667       }
668     }
669
670     let rest = rawurl.slice(end, len).to_owned();
671     return Ok((userinfo, host, port, rest));
672 }
673
674
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();
679     let mut end = len;
680     for (i,c) in rawurl.chars().enumerate() {
681         match c {
682           'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
683           | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
684           | '_' | '-' | '~' => {
685             continue;
686           }
687           '?' | '#' => {
688             end = i;
689             break;
690           }
691           _ => return Err(~"Invalid character in path.")
692         }
693     }
694
695     if authority {
696         if end != 0 && !rawurl.starts_with("/") {
697             return Err(~"Non-empty path must begin with\
698                                '/' in presence of authority.");
699         }
700     }
701
702     return Ok((decode_component(rawurl.slice(0, end)),
703                     rawurl.slice(end, len).to_owned()));
704 }
705
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(
712                                                 1,
713                                                 rawurl.len()));
714             return Ok((vec!(), Some(f)));
715         } else {
716             return Ok((vec!(), None));
717         }
718     }
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));
723 }
724
725 /**
726  * Parses a URL, converting it from a string to `Url` representation.
727  *
728  * # Arguments
729  *
730  * `rawurl` - a string representing the full URL, including scheme.
731  *
732  * # Returns
733  *
734  * A `Url` struct type representing the URL.
735  */
736 pub fn from_str(rawurl: &str) -> Result<Url, ~str> {
737     // scheme
738     let (scheme, rest) = match get_scheme(rawurl) {
739         Ok(val) => val,
740         Err(e) => return Err(e),
741     };
742
743     // authority
744     let (userinfo, host, port, rest) = match get_authority(rest) {
745         Ok(val) => val,
746         Err(e) => return Err(e),
747     };
748
749     // path
750     let has_authority = if host == ~"" { false } else { true };
751     let (path, rest) = match get_path(rest, has_authority) {
752         Ok(val) => val,
753         Err(e) => return Err(e),
754     };
755
756     // query and fragment
757     let (query, fragment) = match get_query_fragment(rest) {
758         Ok(val) => val,
759         Err(e) => return Err(e),
760     };
761
762     Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
763 }
764
765 pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
766     let (path, rest) = match get_path(rawpath, false) {
767         Ok(val) => val,
768         Err(e) => return Err(e)
769     };
770
771     // query and fragment
772     let (query, fragment) = match get_query_fragment(rest) {
773         Ok(val) => val,
774         Err(e) => return Err(e),
775     };
776
777     Ok(Path{ path: path, query: query, fragment: fragment })
778 }
779
780 impl FromStr for Url {
781     fn from_str(s: &str) -> Option<Url> {
782         match from_str(s) {
783             Ok(url) => Some(url),
784             Err(_) => None
785         }
786     }
787 }
788
789 impl FromStr for Path {
790     fn from_str(s: &str) -> Option<Path> {
791         match path_from_str(s) {
792             Ok(path) => Some(path),
793             Err(_) => None
794         }
795     }
796 }
797
798 impl fmt::Show for Url {
799     /**
800      * Converts a URL from `Url` to string representation.
801      *
802      * # Arguments
803      *
804      * `url` - a URL.
805      *
806      * # Returns
807      *
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".
812      */
813     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
814         try!(write!(f.buf, "{}:", self.scheme));
815
816         if !self.host.is_empty() {
817             try!(write!(f.buf, "//"));
818             match self.user {
819                 Some(ref user) => try!(write!(f.buf, "{}", *user)),
820                 None => {}
821             }
822             match self.port {
823                 Some(ref port) => try!(write!(f.buf, "{}:{}", self.host,
824                                                 *port)),
825                 None => try!(write!(f.buf, "{}", self.host)),
826             }
827         }
828
829         try!(write!(f.buf, "{}", self.path));
830
831         if !self.query.is_empty() {
832             try!(write!(f.buf, "?{}", query_to_str(&self.query)));
833         }
834
835         match self.fragment {
836             Some(ref fragment) => write!(f.buf, "\\#{}",
837                                          encode_component(*fragment)),
838             None => Ok(()),
839         }
840     }
841 }
842
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))
848         }
849
850         match self.fragment {
851             Some(ref fragment) => {
852                 write!(f.buf, "\\#{}", encode_component(*fragment))
853             }
854             None => Ok(())
855         }
856     }
857 }
858
859 impl<S: Writer> Hash<S> for Url {
860     fn hash(&self, state: &mut S) {
861         self.to_str().hash(state)
862     }
863 }
864
865 impl<S: Writer> Hash<S> for Path {
866     fn hash(&self, state: &mut S) {
867         self.to_str().hash(state)
868     }
869 }
870
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'
873
874 #[test]
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");
879
880     let (u,v) = split_char_first("hello sweet world", ',');
881     assert_eq!(u, ~"hello sweet world");
882     assert_eq!(v, ~"");
883 }
884
885 #[test]
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");
893
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");
900
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");
907
908     // ipv6 tests
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");
912
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"));
917
918     let (u, h, p, _) = get_authority(
919         "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
920     ).unwrap();
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"));
924
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());
932
933     // these parse as empty, because they don't start with '//'
934     let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
935     assert_eq!(h, ~"");
936     let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
937     assert_eq!(h, ~"");
938 }
939
940 #[test]
941 fn test_get_path() {
942     let (p, r) = get_path("/something+%20orother", true).unwrap();
943     assert_eq!(p, ~"/something+ orother");
944     assert_eq!(r, ~"");
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");
951
952     //failure cases
953     assert!(get_path("something?q", true).is_err());
954 }
955
956 #[cfg(test)]
957 mod tests {
958     use {encode_form_urlencoded, decode_form_urlencoded,
959          decode, encode, from_str, encode_component, decode_component,
960          path_from_str, UserInfo, get_scheme};
961
962     use collections::HashMap;
963
964     #[test]
965     fn test_url_parse() {
966         let url = ~"http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
967
968         let up = from_str(url);
969         let u = up.unwrap();
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"));
977     }
978
979     #[test]
980     fn test_path_parse() {
981         let path = ~"/doc/~u?s=v#something";
982
983         let up = path_from_str(path);
984         let u = up.unwrap();
985         assert_eq!(&u.path, &~"/doc/~u");
986         assert_eq!(&u.query, &vec!((~"s", ~"v")));
987         assert_eq!(&u.fragment, &Some(~"something"));
988     }
989
990     #[test]
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 == ~"/");
996     }
997
998     #[test]
999     fn test_path_parse_host_slash() {
1000         let pathstr = ~"/";
1001         let path = path_from_str(pathstr).unwrap();
1002         assert!(path.path == ~"/");
1003     }
1004
1005     #[test]
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, &~"/");
1019     }
1020
1021     #[test]
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");
1026     }
1027
1028     #[test]
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");
1033     }
1034
1035     #[test]
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");
1040     }
1041
1042     #[test]
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");
1047     }
1048
1049     #[test]
1050     fn test_no_scheme() {
1051         assert!(get_scheme("noschemehere.html").is_err());
1052     }
1053
1054     #[test]
1055     fn test_invalid_scheme_errors() {
1056         assert!(from_str("99://something").is_err());
1057         assert!(from_str("://something").is_err());
1058     }
1059
1060     #[test]
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);
1064     }
1065
1066     #[test]
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);
1070     }
1071
1072     #[test]
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);
1076     }
1077
1078     #[test]
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);
1083     }
1084
1085     #[test]
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);
1089     }
1090
1091     #[test]
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);
1095     }
1096
1097     #[test]
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);
1101     }
1102
1103     #[test]
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);
1107     }
1108
1109     #[test]
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);
1113     }
1114
1115     #[test]
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);
1119     }
1120
1121     #[test]
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 ", ~"#&+")));
1127     }
1128
1129     #[test]
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 ", ~"#&+")));
1135     }
1136
1137     #[test]
1138     fn test_url_without_authority() {
1139         let url = ~"mailto:test@email.com";
1140         assert_eq!(from_str(url).unwrap().to_str(), url);
1141     }
1142
1143     #[test]
1144     fn test_encode() {
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("]"), ~"]");
1169     }
1170
1171     #[test]
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");
1198     }
1199
1200     #[test]
1201     fn test_decode() {
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");
1226     }
1227
1228     #[test]
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"), ~"]");
1254     }
1255
1256     #[test]
1257     fn test_encode_form_urlencoded() {
1258         let mut m = HashMap::new();
1259         assert_eq!(encode_form_urlencoded(&m), ~"");
1260
1261         m.insert(~"", vec!());
1262         m.insert(~"foo", vec!());
1263         assert_eq!(encode_form_urlencoded(&m), ~"");
1264
1265         let mut m = HashMap::new();
1266         m.insert(~"foo", vec!(~"bar", ~"123"));
1267         assert_eq!(encode_form_urlencoded(&m), ~"foo=bar&foo=123");
1268
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");
1273     }
1274
1275     #[test]
1276     fn test_decode_form_urlencoded() {
1277         assert_eq!(decode_form_urlencoded([]).len(), 0);
1278
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"));
1284     }
1285 }