(index<- )        ./liburl/lib.rs

    git branch:    * master           5200215 auto merge of #14035 : alexcrichton/rust/experimental, r=huonw
    modified:    Fri May  9 13:02:28 2014
    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-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 collections::HashMap;
   25  use std::cmp::Eq;
   26  use std::fmt;
   27  use std::from_str::FromStr;
   28  use std::hash::Hash;
   29  use std::io::BufReader;
   30  use std::strbuf::StrBuf;
   31  use std::uint;
   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".to_owned(),
   43  ///                 user: Some(UserInfo { user: "username".to_owned(), pass: None }),
   44  ///                 host: "example.com".to_owned(),
   45  ///                 port: Some("8080".to_owned()),
   46  ///                 path: "/foo/bar".to_owned(),
   47  ///                 query: vec!(("baz".to_owned(), "qux".to_owned())),
   48  ///                 fragment: Some("quz".to_owned()) };
   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      pub scheme: ~str,
   55      /// A URL subcomponent for user authentication.  `username` in the above example.
   56      pub user: Option<UserInfo>,
   57      /// A domain name or IP address.  For example, `example.com`.
   58      pub host: ~str,
   59      /// A TCP port number, for example `8080`.
   60      pub port: Option<~str>,
   61      /// The path component of a URL, for example `/foo/bar`.
   62      pub path: ~str,
   63      /// The query component of a URL.  `vec!(("baz".to_owned(), "qux".to_owned()))` represents the
   64      /// fragment `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<~str>
   68  }
   69  
   70  #[deriving(Clone, Eq)]
   71  pub struct Path {
   72      /// The path component of a URL, for example `/foo/bar`.
   73      pub path: ~str,
   74      /// The query component of a URL.  `vec!(("baz".to_owned(), "qux".to_owned()))` represents the
   75      /// fragment `baz=qux` in the above example.
   76      pub query: Query,
   77      /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
   78      pub 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      pub user: ~str,
   86      /// Password or other scheme-specific authentication information.
   87      pub 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                 userOption<UserInfo>,
   96                 host~str,
   97                 portOption<~str>,
   98                 path~str,
   99                 queryQuery,
  100                 fragmentOption<~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                 queryQuery,
  117                 fragmentOption<~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, passOption<~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 = StrBuf::new();
  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.into_owned()
  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 = StrBuf::new();
  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.into_owned()
  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 = StrBuf::new();
  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.into_owned()
  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 = StrBuf::new();
  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.into_owned()
  323  }
  324  
  325  /**
  326   * Decode a string encoded with the 'application/x-www-form-urlencoded' media
  327   * type into a hashmap.
  328   */
  329  #[allow(experimental)]
  330  pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<~str, Vec<~str>> {
  331      let mut rdr = BufReader::new(s);
  332      let mut mHashMap<~str,Vec<~str>> = HashMap::new();
  333      let mut key = StrBuf::new();
  334      let mut value = StrBuf::new();
  335      let mut parsing_key = true;
  336  
  337      loop {
  338          let mut buf = [0];
  339          let ch = match rdr.read(buf) {
  340              Ok(..) => buf[0] as char,
  341              Err(..) => break,
  342          };
  343          match ch {
  344              '&' | ';' => {
  345                  if key.len() > 0 && value.len() > 0 {
  346                      let mut values = match m.pop_equiv(&key.as_slice()) {
  347                          Some(values) => values,
  348                          None => vec!(),
  349                      };
  350  
  351                      values.push(value.into_owned());
  352                      m.insert(key.into_owned(), values);
  353                  }
  354  
  355                  parsing_key = true;
  356                  key = StrBuf::new();
  357                  value = StrBuf::new();
  358              }
  359              '=' => parsing_key = false,
  360              ch => {
  361                  let ch = match ch {
  362                      '%' => {
  363                          let mut bytes = [0, 0];
  364                          match rdr.read(bytes) {
  365                              Ok(2) => {}
  366                              _ => fail!() // FIXME: malformed?
  367                          }
  368                          uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
  369                      }
  370                      '+' => ' ',
  371                      ch => ch
  372                  };
  373  
  374                  if parsing_key {
  375                      key.push_char(ch)
  376                  } else {
  377                      value.push_char(ch)
  378                  }
  379              }
  380          }
  381      }
  382  
  383      if key.len() > 0 && value.len() > 0 {
  384          let mut values = match m.pop_equiv(&key.as_slice()) {
  385              Some(values) => values,
  386              None => vec!(),
  387          };
  388  
  389          values.push(value.into_owned());
  390          m.insert(key.into_owned(), values);
  391      }
  392  
  393      m
  394  }
  395  
  396  
  397  fn split_char_first(s: &str, c: char) -> (~str, ~str) {
  398      let len = s.len();
  399      let mut index = len;
  400      let mut mat = 0;
  401      let mut rdr = BufReader::new(s.as_bytes());
  402      loop {
  403          let mut buf = [0];
  404          let ch = match rdr.read(buf) {
  405              Ok(..) => buf[0] as char,
  406              Err(..) => break,
  407          };
  408          if ch == c {
  409              // found a match, adjust markers
  410              index = (rdr.tell().unwrap() as uint) - 1;
  411              mat = 1;
  412              break;
  413          }
  414      }
  415      if index+mat == len {
  416          return (s.slice(0, index).to_owned(), "".to_owned());
  417      } else {
  418          return (s.slice(0, index).to_owned(),
  419               s.slice(index + mat, s.len()).to_owned());
  420      }
  421  }
  422  
  423  impl fmt::Show for UserInfo {
  424      fn fmt(&self, f&mut fmt::Formatter) -> fmt::Result {
  425          match self.pass {
  426              Some(ref pass) => write!(f.buf, "{}:{}@", self.user, *pass),
  427              None => write!(f.buf, "{}@", self.user),
  428          }
  429      }
  430  }
  431  
  432  fn query_from_str(rawquery: &str) -> Query {
  433      let mut queryQuery = vec!();
  434      if !rawquery.is_empty() {
  435          for p in rawquery.split('&') {
  436              let (k, v) = split_char_first(p, '=');
  437              query.push((decode_component(k), decode_component(v)));
  438          };
  439      }
  440      return query;
  441  }
  442  
  443  /**
  444   * Converts an instance of a URI `Query` type to a string.
  445   *
  446   * # Example
  447   *
  448   * ```rust
  449   * let query = vec!(("title".to_owned(), "The Village".to_owned()),
  450                      ("north".to_owned(), "52.91".to_owned()),
  451                      ("west".to_owned(), "4.10".to_owned()));
  452   * println!("{}", url::query_to_str(&query));  // title=The%20Village&north=52.91&west=4.10
  453   * ```
  454   */
  455  #[allow(unused_must_use)]
  456  pub fn query_to_str(query: &Query) -> ~str {
  457      use std::io::MemWriter;
  458      use std::str;
  459  
  460      let mut writer = MemWriter::new();
  461      for (i, &(ref k, ref v)) in query.iter().enumerate() {
  462          if i != 0 { write!(&mut writer, "&"); }
  463          write!(&mut writer, "{}={}", encode_component(*k),
  464                 encode_component(*v));
  465      }
  466      str::from_utf8_lossy(writer.unwrap().as_slice()).into_owned()
  467  }
  468  
  469  /**
  470   * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
  471   *
  472   * Does not include the separating `:` character.
  473   *
  474   * # Example
  475   *
  476   * ```rust
  477   * use url::get_scheme;
  478   *
  479   * let scheme = match get_scheme("https://example.com/") {
  480   *     Ok((sch, _)) => sch,
  481   *     Err(_) => "(None)".to_owned(),
  482   * };
  483   * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
  484   * ```
  485   */
  486  pub fn get_scheme(rawurl: &str) -> Result<(~str, ~str), ~str> {
  487      for (i,c) in rawurl.chars().enumerate() {
  488          match c {
  489            'A' .. 'Z' | 'a' .. 'z' => continue,
  490            '0' .. '9' | '+' | '-' | '.' => {
  491              if i == 0 {
  492                  return Err("url: Scheme must begin with a letter.".to_owned());
  493              }
  494              continue;
  495            }
  496            ':' => {
  497              if i == 0 {
  498                  return Err("url: Scheme cannot be empty.".to_owned());
  499              } else {
  500                  return Ok((rawurl.slice(0,i).to_owned(),
  501                                  rawurl.slice(i+1,rawurl.len()).to_owned()));
  502              }
  503            }
  504            _ => {
  505              return Err("url: Invalid character in scheme.".to_owned());
  506            }
  507          }
  508      };
  509      return Err("url: Scheme must be terminated with a colon.".to_owned());
  510  }
  511  
  512  #[deriving(Clone, Eq)]
  513  enum Input {
  514      Digit, // all digits
  515      Hex, // digits and letters a-f
  516      Unreserved // all other legal characters
  517  }
  518  
  519  // returns userinfo, host, port, and unparsed part, or an error
  520  fn get_authority(rawurl: &str) ->
  521      Result<(Option<UserInfo>, ~str, Option<~str>, ~str), ~str> {
  522      if !rawurl.starts_with("//") {
  523          // there is no authority.
  524          return Ok((None, "".to_owned(), None, rawurl.to_str()));
  525      }
  526  
  527      enum State {
  528          Start, // starting state
  529          PassHostPort, // could be in user or port
  530          Ip6Port, // either in ipv6 host or port
  531          Ip6Host, // are in an ipv6 host
  532          InHost, // are in a host - may be ipv6, but don't know yet
  533          InPort // are in port
  534      }
  535  
  536      let len = rawurl.len();
  537      let mut st = Start;
  538      let mut input = Digit; // most restricted, start here.
  539  
  540      let mut userinfo = None;
  541      let mut host = "".to_owned();
  542      let mut port = None;
  543  
  544      let mut colon_count = 0;
  545      let mut pos = 0;
  546      let mut begin = 2;
  547      let mut end = len;
  548  
  549      for (i,c) in rawurl.chars().enumerate() {
  550          if i < 2 { continue; } // ignore the leading //
  551  
  552          // deal with input class first
  553          match c {
  554            '0' .. '9' => (),
  555            'A' .. 'F' | 'a' .. 'f' => {
  556              if input == Digit {
  557                  input = Hex;
  558              }
  559            }
  560            'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
  561            '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
  562              input = Unreserved;
  563            }
  564            ':' | '@' | '?' | '#' | '/' => {
  565              // separators, don't change anything
  566            }
  567            _ => {
  568              return Err("Illegal character in authority".to_owned());
  569            }
  570          }
  571  
  572          // now process states
  573          match c {
  574            ':' => {
  575              colon_count += 1;
  576              match st {
  577                Start => {
  578                  pos = i;
  579                  st = PassHostPort;
  580                }
  581                PassHostPort => {
  582                  // multiple colons means ipv6 address.
  583                  if input == Unreserved {
  584                      return Err(
  585                          "Illegal characters in IPv6 address.".to_owned());
  586                  }
  587                  st = Ip6Host;
  588                }
  589                InHost => {
  590                  pos = i;
  591                  if input == Unreserved {
  592                      // must be port
  593                      host = rawurl.slice(begin, i).to_owned();
  594                      st = InPort;
  595                  } else {
  596                      // can't be sure whether this is an ipv6 address or a port
  597                      st = Ip6Port;
  598                  }
  599                }
  600                Ip6Port => {
  601                  if input == Unreserved {
  602                      return Err("Illegal characters in authority.".to_owned());
  603                  }
  604                  st = Ip6Host;
  605                }
  606                Ip6Host => {
  607                  if colon_count > 7 {
  608                      host = rawurl.slice(begin, i).to_owned();
  609                      pos = i;
  610                      st = InPort;
  611                  }
  612                }
  613                _ => {
  614                  return Err("Invalid ':' in authority.".to_owned());
  615                }
  616              }
  617              input = Digit; // reset input class
  618            }
  619  
  620            '@' => {
  621              input = Digit; // reset input class
  622              colon_count = 0; // reset count
  623              match st {
  624                Start => {
  625                  let user = rawurl.slice(begin, i).to_owned();
  626                  userinfo = Some(UserInfo::new(user, None));
  627                  st = InHost;
  628                }
  629                PassHostPort => {
  630                  let user = rawurl.slice(begin, pos).to_owned();
  631                  let pass = rawurl.slice(pos+1, i).to_owned();
  632                  userinfo = Some(UserInfo::new(user, Some(pass)));
  633                  st = InHost;
  634                }
  635                _ => {
  636                  return Err("Invalid '@' in authority.".to_owned());
  637                }
  638              }
  639              begin = i+1;
  640            }
  641  
  642            '?' | '#' | '/' => {
  643              end = i;
  644              break;
  645            }
  646            _ => ()
  647          }
  648      }
  649  
  650      // finish up
  651      match st {
  652        Start => {
  653          host = rawurl.slice(begin, end).to_owned();
  654        }
  655        PassHostPort | Ip6Port => {
  656          if input != Digit {
  657              return Err("Non-digit characters in port.".to_owned());
  658          }
  659          host = rawurl.slice(begin, pos).to_owned();
  660          port = Some(rawurl.slice(pos+1, end).to_owned());
  661        }
  662        Ip6Host | InHost => {
  663          host = rawurl.slice(begin, end).to_owned();
  664        }
  665        InPort => {
  666          if input != Digit {
  667              return Err("Non-digit characters in port.".to_owned());
  668          }
  669          port = Some(rawurl.slice(pos+1, end).to_owned());
  670        }
  671      }
  672  
  673      let rest = rawurl.slice(end, len).to_owned();
  674      return Ok((userinfo, host, port, rest));
  675  }
  676  
  677  
  678  // returns the path and unparsed part of url, or an error
  679  fn get_path(rawurl: &str, authority: bool) ->
  680      Result<(~str, ~str), ~str> {
  681      let len = rawurl.len();
  682      let mut end = len;
  683      for (i,c) in rawurl.chars().enumerate() {
  684          match c {
  685            'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
  686            | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
  687            | '_' | '-' | '~' => {
  688              continue;
  689            }
  690            '?' | '#' => {
  691              end = i;
  692              break;
  693            }
  694            _ => return Err("Invalid character in path.".to_owned())
  695          }
  696      }
  697  
  698      if authority {
  699          if end != 0 && !rawurl.starts_with("/") {
  700              return Err("Non-empty path must begin with\
  701                                '/' in presence of authority.".to_owned());
  702          }
  703      }
  704  
  705      return Ok((decode_component(rawurl.slice(0, end)),
  706                      rawurl.slice(end, len).to_owned()));
  707  }
  708  
  709  // returns the parsed query and the fragment, if present
  710  fn get_query_fragment(rawurl: &str) ->
  711      Result<(Query, Option<~str>), ~str> {
  712      if !rawurl.starts_with("?") {
  713          if rawurl.starts_with("#") {
  714              let f = decode_component(rawurl.slice(
  715                                                  1,
  716                                                  rawurl.len()));
  717              return Ok((vec!(), Some(f)));
  718          } else {
  719              return Ok((vec!(), None));
  720          }
  721      }
  722      let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#');
  723      let f = if r.len() != 0 {
  724          Some(decode_component(r)) } else { None };
  725      return Ok((query_from_str(q), f));
  726  }
  727  
  728  /**
  729   * Parses a URL, converting it from a string to `Url` representation.
  730   *
  731   * # Arguments
  732   *
  733   * `rawurl` - a string representing the full URL, including scheme.
  734   *
  735   * # Returns
  736   *
  737   * A `Url` struct type representing the URL.
  738   */
  739  pub fn from_str(rawurl: &str) -> Result<Url, ~str> {
  740      // scheme
  741      let (scheme, rest) = match get_scheme(rawurl) {
  742          Ok(val) => val,
  743          Err(e) => return Err(e),
  744      };
  745  
  746      // authority
  747      let (userinfo, host, port, rest) = match get_authority(rest) {
  748          Ok(val) => val,
  749          Err(e) => return Err(e),
  750      };
  751  
  752      // path
  753      let has_authority = if host == "".to_owned() { false } else { true };
  754      let (path, rest) = match get_path(rest, has_authority) {
  755          Ok(val) => val,
  756          Err(e) => return Err(e),
  757      };
  758  
  759      // query and fragment
  760      let (query, fragment) = match get_query_fragment(rest) {
  761          Ok(val) => val,
  762          Err(e) => return Err(e),
  763      };
  764  
  765      Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
  766  }
  767  
  768  pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
  769      let (path, rest) = match get_path(rawpath, false) {
  770          Ok(val) => val,
  771          Err(e) => return Err(e)
  772      };
  773  
  774      // query and fragment
  775      let (query, fragment) = match get_query_fragment(rest) {
  776          Ok(val) => val,
  777          Err(e) => return Err(e),
  778      };
  779  
  780      Ok(Path{ path: path, query: query, fragment: fragment })
  781  }
  782  
  783  impl FromStr for Url {
  784      fn from_str(s&str) -> Option<Url> {
  785          match from_str(s) {
  786              Ok(url) => Some(url),
  787              Err(_) => None
  788          }
  789      }
  790  }
  791  
  792  impl FromStr for Path {
  793      fn from_str(s&str) -> Option<Path> {
  794          match path_from_str(s) {
  795              Ok(path) => Some(path),
  796              Err(_) => None
  797          }
  798      }
  799  }
  800  
  801  impl fmt::Show for Url {
  802      /**
  803       * Converts a URL from `Url` to string representation.
  804       *
  805       * # Arguments
  806       *
  807       * `url` - a URL.
  808       *
  809       * # Returns
  810       *
  811       * A string that contains the formatted URL. Note that this will usually
  812       * be an inverse of `from_str` but might strip out unneeded separators;
  813       * for example, "http://somehost.com?", when parsed and formatted, will
  814       * result in just "http://somehost.com".
  815       */
  816      fn fmt(&self, f&mut fmt::Formatter) -> fmt::Result {
  817          try!(write!(f.buf, "{}:", self.scheme));
  818  
  819          if !self.host.is_empty() {
  820              try!(write!(f.buf, "//"));
  821              match self.user {
  822                  Some(ref user) => try!(write!(f.buf, "{}", *user)),
  823                  None => {}
  824              }
  825              match self.port {
  826                  Some(ref port) => try!(write!(f.buf, "{}:{}", self.host,
  827                                                  *port)),
  828                  None => try!(write!(f.buf, "{}", self.host)),
  829              }
  830          }
  831  
  832          try!(write!(f.buf, "{}", self.path));
  833  
  834          if !self.query.is_empty() {
  835              try!(write!(f.buf, "?{}", query_to_str(&self.query)));
  836          }
  837  
  838          match self.fragment {
  839              Some(ref fragment) => write!(f.buf, "\\#{}",
  840                                           encode_component(*fragment)),
  841              None => Ok(()),
  842          }
  843      }
  844  }
  845  
  846  impl fmt::Show for Path {
  847      fn fmt(&self, f&mut fmt::Formatter) -> fmt::Result {
  848          try!(write!(f.buf, "{}", self.path));
  849          if !self.query.is_empty() {
  850              try!(write!(f.buf, "?{}", self.query))
  851          }
  852  
  853          match self.fragment {
  854              Some(ref fragment) => {
  855                  write!(f.buf, "\\#{}", encode_component(*fragment))
  856              }
  857              None => Ok(())
  858          }
  859      }
  860  }
  861  
  862  impl<S: Writer> Hash<S> for Url {
  863      fn hash(&self, state&mut S) {
  864          self.to_str().hash(state)
  865      }
  866  }
  867  
  868  impl<S: Writer> Hash<S> for Path {
  869      fn hash(&self, state&mut S) {
  870          self.to_str().hash(state)
  871      }
  872  }
  873  
  874  // Put a few tests outside of the 'test' module so they can test the internal
  875  // functions and those functions don't need 'pub'
  876  
  877  #[test]
  878  fn test_split_char_first() {
  879      let (u,v) = split_char_first("hello, sweet world", ',');
  880      assert_eq!(u, "hello".to_owned());
  881      assert_eq!(v, " sweet world".to_owned());
  882  
  883      let (u,v) = split_char_first("hello sweet world", ',');
  884      assert_eq!(u, "hello sweet world".to_owned());
  885      assert_eq!(v, "".to_owned());
  886  }
  887  
  888  #[test]
  889  fn test_get_authority() {
  890      let (u, h, p, r) = get_authority(
  891          "//user:pass@rust-lang.org/something").unwrap();
  892      assert_eq!(u, Some(UserInfo::new("user".to_owned(), Some("pass".to_owned()))));
  893      assert_eq!(h, "rust-lang.org".to_owned());
  894      assert!(p.is_none());
  895      assert_eq!(r, "/something".to_owned());
  896  
  897      let (u, h, p, r) = get_authority(
  898          "//rust-lang.org:8000?something").unwrap();
  899      assert!(u.is_none());
  900      assert_eq!(h, "rust-lang.org".to_owned());
  901      assert_eq!(p, Some("8000".to_owned()));
  902      assert_eq!(r, "?something".to_owned());
  903  
  904      let (u, h, p, r) = get_authority(
  905          "//rust-lang.org#blah").unwrap();
  906      assert!(u.is_none());
  907      assert_eq!(h, "rust-lang.org".to_owned());
  908      assert!(p.is_none());
  909      assert_eq!(r, "#blah".to_owned());
  910  
  911      // ipv6 tests
  912      let (_, h, _, _) = get_authority(
  913          "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap();
  914      assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_owned());
  915  
  916      let (_, h, p, _) = get_authority(
  917          "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap();
  918      assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_owned());
  919      assert_eq!(p, Some("8000".to_owned()));
  920  
  921      let (u, h, p, _) = get_authority(
  922          "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
  923      ).unwrap();
  924      assert_eq!(u, Some(UserInfo::new("us".to_owned(), Some("p".to_owned()))));
  925      assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_owned());
  926      assert_eq!(p, Some("8000".to_owned()));
  927  
  928      // invalid authorities;
  929      assert!(get_authority("//user:pass@rust-lang:something").is_err());
  930      assert!(get_authority("//user@rust-lang:something:/path").is_err());
  931      assert!(get_authority(
  932          "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err());
  933      assert!(get_authority(
  934          "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err());
  935  
  936      // these parse as empty, because they don't start with '//'
  937      let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
  938      assert_eq!(h, "".to_owned());
  939      let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
  940      assert_eq!(h, "".to_owned());
  941  }
  942  
  943  #[test]
  944  fn test_get_path() {
  945      let (p, r) = get_path("/something+%20orother", true).unwrap();
  946      assert_eq!(p, "/something+ orother".to_owned());
  947      assert_eq!(r, "".to_owned());
  948      let (p, r) = get_path("test@email.com#fragment", false).unwrap();
  949      assert_eq!(p, "test@email.com".to_owned());
  950      assert_eq!(r, "#fragment".to_owned());
  951      let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap();
  952      assert_eq!(p, "/gen/:addr=".to_owned());
  953      assert_eq!(r, "?q=v".to_owned());
  954  
  955      //failure cases
  956      assert!(get_path("something?q", true).is_err());
  957  }
  958  
  959  #[cfg(test)]
  960  mod tests {
  961      use {encode_form_urlencoded, decode_form_urlencoded,
  962           decode, encode, from_str, encode_component, decode_component,
  963           path_from_str, UserInfo, get_scheme};
  964  
  965      use collections::HashMap;
  966  
  967      #[test]
  968      fn test_url_parse() {
  969          let url = "http://user:pass@rust-lang.org:8080/doc/~u?s=v#something".to_owned();
  970  
  971          let up = from_str(url);
  972          let u = up.unwrap();
  973          assert_eq!(&u.scheme, &"http".to_owned());
  974          assert_eq!(&u.user, &Some(UserInfo::new("user".to_owned(), Some("pass".to_owned()))));
  975          assert_eq!(&u.host, &"rust-lang.org".to_owned());
  976          assert_eq!(&u.port, &Some("8080".to_owned()));
  977          assert_eq!(&u.path, &"/doc/~u".to_owned());
  978          assert_eq!(&u.query, &vec!(("s".to_owned(), "v".to_owned())));
  979          assert_eq!(&u.fragment, &Some("something".to_owned()));
  980      }
  981  
  982      #[test]
  983      fn test_path_parse() {
  984          let path = "/doc/~u?s=v#something".to_owned();
  985  
  986          let up = path_from_str(path);
  987          let u = up.unwrap();
  988          assert_eq!(&u.path, &"/doc/~u".to_owned());
  989          assert_eq!(&u.query, &vec!(("s".to_owned(), "v".to_owned())));
  990          assert_eq!(&u.fragment, &Some("something".to_owned()));
  991      }
  992  
  993      #[test]
  994      fn test_url_parse_host_slash() {
  995          let urlstr = "http://0.42.42.42/".to_owned();
  996          let url = from_str(urlstr).unwrap();
  997          assert!(url.host == "0.42.42.42".to_owned());
  998          assert!(url.path == "/".to_owned());
  999      }
 1000  
 1001      #[test]
 1002      fn test_path_parse_host_slash() {
 1003          let pathstr = "/".to_owned();
 1004          let path = path_from_str(pathstr).unwrap();
 1005          assert!(path.path == "/".to_owned());
 1006      }
 1007  
 1008      #[test]
 1009      fn test_url_host_with_port() {
 1010          let urlstr = "scheme://host:1234".to_owned();
 1011          let url = from_str(urlstr).unwrap();
 1012          assert_eq!(&url.scheme, &"scheme".to_owned());
 1013          assert_eq!(&url.host, &"host".to_owned());
 1014          assert_eq!(&url.port, &Some("1234".to_owned()));
 1015          assert_eq!(&url.path, &"".to_owned()); // is empty path really correct? Other tests think so
 1016          let urlstr = "scheme://host:1234/".to_owned();
 1017          let url = from_str(urlstr).unwrap();
 1018          assert_eq!(&url.scheme, &"scheme".to_owned());
 1019          assert_eq!(&url.host, &"host".to_owned());
 1020          assert_eq!(&url.port, &Some("1234".to_owned()));
 1021          assert_eq!(&url.path, &"/".to_owned());
 1022      }
 1023  
 1024      #[test]
 1025      fn test_url_with_underscores() {
 1026          let urlstr = "http://dotcom.com/file_name.html".to_owned();
 1027          let url = from_str(urlstr).unwrap();
 1028          assert!(url.path == "/file_name.html".to_owned());
 1029      }
 1030  
 1031      #[test]
 1032      fn test_path_with_underscores() {
 1033          let pathstr = "/file_name.html".to_owned();
 1034          let path = path_from_str(pathstr).unwrap();
 1035          assert!(path.path == "/file_name.html".to_owned());
 1036      }
 1037  
 1038      #[test]
 1039      fn test_url_with_dashes() {
 1040          let urlstr = "http://dotcom.com/file-name.html".to_owned();
 1041          let url = from_str(urlstr).unwrap();
 1042          assert!(url.path == "/file-name.html".to_owned());
 1043      }
 1044  
 1045      #[test]
 1046      fn test_path_with_dashes() {
 1047          let pathstr = "/file-name.html".to_owned();
 1048          let path = path_from_str(pathstr).unwrap();
 1049          assert!(path.path == "/file-name.html".to_owned());
 1050      }
 1051  
 1052      #[test]
 1053      fn test_no_scheme() {
 1054          assert!(get_scheme("noschemehere.html").is_err());
 1055      }
 1056  
 1057      #[test]
 1058      fn test_invalid_scheme_errors() {
 1059          assert!(from_str("99://something").is_err());
 1060          assert!(from_str("://something").is_err());
 1061      }
 1062  
 1063      #[test]
 1064      fn test_full_url_parse_and_format() {
 1065          let url = "http://user:pass@rust-lang.org/doc?s=v#something".to_owned();
 1066          assert_eq!(from_str(url).unwrap().to_str(), url);
 1067      }
 1068  
 1069      #[test]
 1070      fn test_userless_url_parse_and_format() {
 1071          let url = "http://rust-lang.org/doc?s=v#something".to_owned();
 1072          assert_eq!(from_str(url).unwrap().to_str(), url);
 1073      }
 1074  
 1075      #[test]
 1076      fn test_queryless_url_parse_and_format() {
 1077          let url = "http://user:pass@rust-lang.org/doc#something".to_owned();
 1078          assert_eq!(from_str(url).unwrap().to_str(), url);
 1079      }
 1080  
 1081      #[test]
 1082      fn test_empty_query_url_parse_and_format() {
 1083          let url = "http://user:pass@rust-lang.org/doc?#something".to_owned();
 1084          let should_be = "http://user:pass@rust-lang.org/doc#something".to_owned();
 1085          assert_eq!(from_str(url).unwrap().to_str(), should_be);
 1086      }
 1087  
 1088      #[test]
 1089      fn test_fragmentless_url_parse_and_format() {
 1090          let url = "http://user:pass@rust-lang.org/doc?q=v".to_owned();
 1091          assert_eq!(from_str(url).unwrap().to_str(), url);
 1092      }
 1093  
 1094      #[test]
 1095      fn test_minimal_url_parse_and_format() {
 1096          let url = "http://rust-lang.org/doc".to_owned();
 1097          assert_eq!(from_str(url).unwrap().to_str(), url);
 1098      }
 1099  
 1100      #[test]
 1101      fn test_url_with_port_parse_and_format() {
 1102          let url = "http://rust-lang.org:80/doc".to_owned();
 1103          assert_eq!(from_str(url).unwrap().to_str(), url);
 1104      }
 1105  
 1106      #[test]
 1107      fn test_scheme_host_only_url_parse_and_format() {
 1108          let url = "http://rust-lang.org".to_owned();
 1109          assert_eq!(from_str(url).unwrap().to_str(), url);
 1110      }
 1111  
 1112      #[test]
 1113      fn test_pathless_url_parse_and_format() {
 1114          let url = "http://user:pass@rust-lang.org?q=v#something".to_owned();
 1115          assert_eq!(from_str(url).unwrap().to_str(), url);
 1116      }
 1117  
 1118      #[test]
 1119      fn test_scheme_host_fragment_only_url_parse_and_format() {
 1120          let url = "http://rust-lang.org#something".to_owned();
 1121          assert_eq!(from_str(url).unwrap().to_str(), url);
 1122      }
 1123  
 1124      #[test]
 1125      fn test_url_component_encoding() {
 1126          let url = "http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B".to_owned();
 1127          let u = from_str(url).unwrap();
 1128          assert!(u.path == "/doc uments".to_owned());
 1129          assert!(u.query == vec!(("ba%d ".to_owned(), "#&+".to_owned())));
 1130      }
 1131  
 1132      #[test]
 1133      fn test_path_component_encoding() {
 1134          let path = "/doc%20uments?ba%25d%20=%23%26%2B".to_owned();
 1135          let p = path_from_str(path).unwrap();
 1136          assert!(p.path == "/doc uments".to_owned());
 1137          assert!(p.query == vec!(("ba%d ".to_owned(), "#&+".to_owned())));
 1138      }
 1139  
 1140      #[test]
 1141      fn test_url_without_authority() {
 1142          let url = "mailto:test@email.com".to_owned();
 1143          assert_eq!(from_str(url).unwrap().to_str(), url);
 1144      }
 1145  
 1146      #[test]
 1147      fn test_encode() {
 1148          assert_eq!(encode(""), "".to_owned());
 1149          assert_eq!(encode("http://example.com"), "http://example.com".to_owned());
 1150          assert_eq!(encode("foo bar% baz"), "foo%20bar%25%20baz".to_owned());
 1151          assert_eq!(encode(" "), "%20".to_owned());
 1152          assert_eq!(encode("!"), "!".to_owned());
 1153          assert_eq!(encode("\""), "\"".to_owned());
 1154          assert_eq!(encode("#"), "#".to_owned());
 1155          assert_eq!(encode("$"), "$".to_owned());
 1156          assert_eq!(encode("%"), "%25".to_owned());
 1157          assert_eq!(encode("&"), "&".to_owned());
 1158          assert_eq!(encode("'"), "%27".to_owned());
 1159          assert_eq!(encode("("), "(".to_owned());
 1160          assert_eq!(encode(")"), ")".to_owned());
 1161          assert_eq!(encode("*"), "*".to_owned());
 1162          assert_eq!(encode("+"), "+".to_owned());
 1163          assert_eq!(encode(","), ",".to_owned());
 1164          assert_eq!(encode("/"), "/".to_owned());
 1165          assert_eq!(encode(":"), ":".to_owned());
 1166          assert_eq!(encode(";"), ";".to_owned());
 1167          assert_eq!(encode("="), "=".to_owned());
 1168          assert_eq!(encode("?"), "?".to_owned());
 1169          assert_eq!(encode("@"), "@".to_owned());
 1170          assert_eq!(encode("["), "[".to_owned());
 1171          assert_eq!(encode("]"), "]".to_owned());
 1172      }
 1173  
 1174      #[test]
 1175      fn test_encode_component() {
 1176          assert_eq!(encode_component(""), "".to_owned());
 1177          assert!(encode_component("http://example.com") ==
 1178              "http%3A%2F%2Fexample.com".to_owned());
 1179          assert!(encode_component("foo bar% baz") ==
 1180              "foo%20bar%25%20baz".to_owned());
 1181          assert_eq!(encode_component(" "), "%20".to_owned());
 1182          assert_eq!(encode_component("!"), "%21".to_owned());
 1183          assert_eq!(encode_component("#"), "%23".to_owned());
 1184          assert_eq!(encode_component("$"), "%24".to_owned());
 1185          assert_eq!(encode_component("%"), "%25".to_owned());
 1186          assert_eq!(encode_component("&"), "%26".to_owned());
 1187          assert_eq!(encode_component("'"), "%27".to_owned());
 1188          assert_eq!(encode_component("("), "%28".to_owned());
 1189          assert_eq!(encode_component(")"), "%29".to_owned());
 1190          assert_eq!(encode_component("*"), "%2A".to_owned());
 1191          assert_eq!(encode_component("+"), "%2B".to_owned());
 1192          assert_eq!(encode_component(","), "%2C".to_owned());
 1193          assert_eq!(encode_component("/"), "%2F".to_owned());
 1194          assert_eq!(encode_component(":"), "%3A".to_owned());
 1195          assert_eq!(encode_component(";"), "%3B".to_owned());
 1196          assert_eq!(encode_component("="), "%3D".to_owned());
 1197          assert_eq!(encode_component("?"), "%3F".to_owned());
 1198          assert_eq!(encode_component("@"), "%40".to_owned());
 1199          assert_eq!(encode_component("["), "%5B".to_owned());
 1200          assert_eq!(encode_component("]"), "%5D".to_owned());
 1201      }
 1202  
 1203      #[test]
 1204      fn test_decode() {
 1205          assert_eq!(decode(""), "".to_owned());
 1206          assert_eq!(decode("abc/def 123"), "abc/def 123".to_owned());
 1207          assert_eq!(decode("abc%2Fdef%20123"), "abc%2Fdef 123".to_owned());
 1208          assert_eq!(decode("%20"), " ".to_owned());
 1209          assert_eq!(decode("%21"), "%21".to_owned());
 1210          assert_eq!(decode("%22"), "%22".to_owned());
 1211          assert_eq!(decode("%23"), "%23".to_owned());
 1212          assert_eq!(decode("%24"), "%24".to_owned());
 1213          assert_eq!(decode("%25"), "%".to_owned());
 1214          assert_eq!(decode("%26"), "%26".to_owned());
 1215          assert_eq!(decode("%27"), "'".to_owned());
 1216          assert_eq!(decode("%28"), "%28".to_owned());
 1217          assert_eq!(decode("%29"), "%29".to_owned());
 1218          assert_eq!(decode("%2A"), "%2A".to_owned());
 1219          assert_eq!(decode("%2B"), "%2B".to_owned());
 1220          assert_eq!(decode("%2C"), "%2C".to_owned());
 1221          assert_eq!(decode("%2F"), "%2F".to_owned());
 1222          assert_eq!(decode("%3A"), "%3A".to_owned());
 1223          assert_eq!(decode("%3B"), "%3B".to_owned());
 1224          assert_eq!(decode("%3D"), "%3D".to_owned());
 1225          assert_eq!(decode("%3F"), "%3F".to_owned());
 1226          assert_eq!(decode("%40"), "%40".to_owned());
 1227          assert_eq!(decode("%5B"), "%5B".to_owned());
 1228          assert_eq!(decode("%5D"), "%5D".to_owned());
 1229      }
 1230  
 1231      #[test]
 1232      fn test_decode_component() {
 1233          assert_eq!(decode_component(""), "".to_owned());
 1234          assert_eq!(decode_component("abc/def 123"), "abc/def 123".to_owned());
 1235          assert_eq!(decode_component("abc%2Fdef%20123"), "abc/def 123".to_owned());
 1236          assert_eq!(decode_component("%20"), " ".to_owned());
 1237          assert_eq!(decode_component("%21"), "!".to_owned());
 1238          assert_eq!(decode_component("%22"), "\"".to_owned());
 1239          assert_eq!(decode_component("%23"), "#".to_owned());
 1240          assert_eq!(decode_component("%24"), "$".to_owned());
 1241          assert_eq!(decode_component("%25"), "%".to_owned());
 1242          assert_eq!(decode_component("%26"), "&".to_owned());
 1243          assert_eq!(decode_component("%27"), "'".to_owned());
 1244          assert_eq!(decode_component("%28"), "(".to_owned());
 1245          assert_eq!(decode_component("%29"), ")".to_owned());
 1246          assert_eq!(decode_component("%2A"), "*".to_owned());
 1247          assert_eq!(decode_component("%2B"), "+".to_owned());
 1248          assert_eq!(decode_component("%2C"), ",".to_owned());
 1249          assert_eq!(decode_component("%2F"), "/".to_owned());
 1250          assert_eq!(decode_component("%3A"), ":".to_owned());
 1251          assert_eq!(decode_component("%3B"), ";".to_owned());
 1252          assert_eq!(decode_component("%3D"), "=".to_owned());
 1253          assert_eq!(decode_component("%3F"), "?".to_owned());
 1254          assert_eq!(decode_component("%40"), "@".to_owned());
 1255          assert_eq!(decode_component("%5B"), "[".to_owned());
 1256          assert_eq!(decode_component("%5D"), "]".to_owned());
 1257      }
 1258  
 1259      #[test]
 1260      fn test_encode_form_urlencoded() {
 1261          let mut m = HashMap::new();
 1262          assert_eq!(encode_form_urlencoded(&m), "".to_owned());
 1263  
 1264          m.insert("".to_owned(), vec!());
 1265          m.insert("foo".to_owned(), vec!());
 1266          assert_eq!(encode_form_urlencoded(&m), "".to_owned());
 1267  
 1268          let mut m = HashMap::new();
 1269          m.insert("foo".to_owned(), vec!("bar".to_owned(), "123".to_owned()));
 1270          assert_eq!(encode_form_urlencoded(&m), "foo=bar&foo=123".to_owned());
 1271  
 1272          let mut m = HashMap::new();
 1273          m.insert("foo bar".to_owned(), vec!("abc".to_owned(), "12 = 34".to_owned()));
 1274          assert!(encode_form_urlencoded(&m) ==
 1275              "foo+bar=abc&foo+bar=12+%3D+34".to_owned());
 1276      }
 1277  
 1278      #[test]
 1279      fn test_decode_form_urlencoded() {
 1280          assert_eq!(decode_form_urlencoded([]).len(), 0);
 1281  
 1282          let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes();
 1283          let form = decode_form_urlencoded(s);
 1284          assert_eq!(form.len(), 2);
 1285          assert_eq!(form.get(&"a".to_owned()), &vec!("1".to_owned()));
 1286          assert_eq!(form.get(&"foo bar".to_owned()), &vec!("abc".to_owned(), "12 = 34".to_owned()));
 1287      }
 1288  }


liburl/lib.rs:70:23-70:23 -struct- definition:
pub struct Path {
    /// The path component of a URL, for example `/foo/bar`.
    pub path: ~str,
references:- 20
780:     Ok(Path{ path: path, query: query, fragment: fragment })
781: }
--
792: impl FromStr for Path {
793:     fn from_str(s: &str) -> Option<Path> {
--
868: impl<S: Writer> Hash<S> for Path {
869:     fn hash(&self, state: &mut S) {


liburl/lib.rs:202:1-202:1 -fn- definition:
pub fn encode_component(s: &str) -> ~str {
    encode_inner(s, false)
}
references:- 4
839:             Some(ref fragment) => write!(f.buf, "\\#{}",
840:                                          encode_component(*fragment)),
841:             None => Ok(()),
--
854:             Some(ref fragment) => {
855:                 write!(f.buf, "\\#{}", encode_component(*fragment))
856:             }


liburl/lib.rs:82:32-82:32 -struct- definition:
pub struct UserInfo {
    /// The user name.
    pub user: ~str,
references:- 20
81: /// An optional subcomponent of a URI authority component.
83: pub struct UserInfo {
--
129:     pub fn new(user: ~str, pass: Option<~str>) -> UserInfo {
130:         UserInfo { user: user, pass: pass }
131:     }
--
520: fn get_authority(rawurl: &str) ->
521:     Result<(Option<UserInfo>, ~str, Option<~str>, ~str), ~str> {
522:     if !rawurl.starts_with("//") {


liburl/lib.rs:396:1-396:1 -fn- definition:
fn split_char_first(s: &str, c: char) -> (~str, ~str) {
    let len = s.len();
    let mut index = len;
references:- 2
721:     }
722:     let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#');
723:     let f = if r.len() != 0 {


liburl/lib.rs:133:1-133:1 -fn- definition:
fn encode_inner(s: &str, full_url: bool) -> ~str {
    let mut rdr = BufReader::new(s.as_bytes());
    let mut out = StrBuf::new();
references:- 2
203: pub fn encode_component(s: &str) -> ~str {
204:     encode_inner(s, false)
205: }


liburl/lib.rs:206:1-206:1 -fn- definition:
fn decode_inner(s: &str, full_url: bool) -> ~str {
    let mut rdr = BufReader::new(s.as_bytes());
    let mut out = StrBuf::new();
references:- 2
267: pub fn decode(s: &str) -> ~str {
268:     decode_inner(s, true)
269: }
--
274: pub fn decode_component(s: &str) -> ~str {
275:     decode_inner(s, false)
276: }


liburl/lib.rs:709:57-709:57 -fn- definition:
// returns the parsed query and the fragment, if present
fn get_query_fragment(rawurl: &str) ->
    Result<(Query, Option<~str>), ~str> {
references:- 2
759:     // query and fragment
760:     let (query, fragment) = match get_query_fragment(rest) {
761:         Ok(val) => val,
--
774:     // query and fragment
775:     let (query, fragment) = match get_query_fragment(rest) {
776:         Ok(val) => val,


liburl/lib.rs:90:45-90:45 -NK_AS_STR_TODO- definition:
/// Represents the query component of a URI.
pub type Query = Vec<(~str, ~str)>;
impl Url {
references:- 8
98:                path: ~str,
99:                query: Query,
100:                fragment: Option<~str>)
--
432: fn query_from_str(rawquery: &str) -> Query {
433:     let mut query: Query = vec!();
--
710: fn get_query_fragment(rawurl: &str) ->
711:     Result<(Query, Option<~str>), ~str> {
712:     if !rawurl.starts_with("?") {


liburl/lib.rs:512:23-512:23 -enum- definition:
enum Input {
    Digit, // all digits
    Hex, // digits and letters a-f
references:- 5
513: enum Input {


liburl/lib.rs:678:58-678:58 -fn- definition:
// returns the path and unparsed part of url, or an error
fn get_path(rawurl: &str, authority: bool) ->
    Result<(~str, ~str), ~str> {
references:- 2
768: pub fn path_from_str(rawpath: &str) -> Result<Path, ~str> {
769:     let (path, rest) = match get_path(rawpath, false) {
770:         Ok(val) => val,


liburl/lib.rs:51:32-51:32 -struct- definition:
pub struct Url {
    /// The scheme part of a URL, such as `https` in the above example.
    pub scheme: ~str,
references:- 21
50: /// ```
52: pub struct Url {
--
101:                -> Url {
102:         Url {
103:             scheme: scheme,
--
783: impl FromStr for Url {
784:     fn from_str(s: &str) -> Option<Url> {
785:         match from_str(s) {
--
801: impl fmt::Show for Url {
802:     /**
--
862: impl<S: Writer> Hash<S> for Url {
863:     fn hash(&self, state: &mut S) {


liburl/lib.rs:273:4-273:4 -fn- definition:
 */
pub fn decode_component(s: &str) -> ~str {
    decode_inner(s, false)
references:- 5
713:         if rawurl.starts_with("#") {
714:             let f = decode_component(rawurl.slice(
715:                                                 1,
--
723:     let f = if r.len() != 0 {
724:         Some(decode_component(r)) } else { None };
725:     return Ok((query_from_str(q), f));


liburl/lib.rs:277:1-277:1 -fn- definition:
fn encode_plus(s: &str) -> ~str {
    let mut rdr = BufReader::new(s.as_bytes());
    let mut out = StrBuf::new();
references:- 2
318:             out.push_str(format!("{}={}", key, encode_plus(*value)));
319:         }