lib.rs 40.6 KB
Newer Older
1
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
B
Brian Anderson 已提交
2 3 4 5 6 7 8 9 10 11 12
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Types/fns concerning URLs (see RFC 3986)

13
#![crate_id = "url#0.11.0-pre"]
14 15 16 17 18
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![license = "MIT/ASL2"]
#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
       html_favicon_url = "http://www.rust-lang.org/favicon.ico",
19
       html_root_url = "http://doc.rust-lang.org/")]
20
#![feature(default_type_params)]
21 22

extern crate collections;
B
Brian Anderson 已提交
23

24
use collections::HashMap;
25
use std::cmp::PartialEq;
26
use std::fmt;
27
use std::from_str::FromStr;
E
Erick Tryzelaar 已提交
28
use std::hash::Hash;
29
use std::io::BufReader;
30
use std::string::String;
B
Brian Anderson 已提交
31 32
use std::uint;

33 34 35
/// A Uniform Resource Locator (URL).  A URL is a form of URI (Uniform Resource
/// Identifier) that includes network location information, such as hostname or
/// port number.
36 37 38 39
///
/// # Example
///
/// ```rust
40
/// use url::{Url, UserInfo};
A
Alex Crichton 已提交
41
///
42 43 44 45 46 47 48
/// let url = Url { scheme: "https".to_string(),
///                 user: Some(UserInfo { user: "username".to_string(), pass: None }),
///                 host: "example.com".to_string(),
///                 port: Some("8080".to_string()),
///                 path: "/foo/bar".to_string(),
///                 query: vec!(("baz".to_string(), "qux".to_string())),
///                 fragment: Some("quz".to_string()) };
49 50
/// // https://username@example.com:8080/foo/bar?baz=qux#quz
/// ```
51
#[deriving(Clone, PartialEq, TotalEq)]
S
Steven Fackler 已提交
52
pub struct Url {
53
    /// The scheme part of a URL, such as `https` in the above example.
54
    pub scheme: String,
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: String,
59
    /// A TCP port number, for example `8080`.
60
    pub port: Option<String>,
61
    /// The path component of a URL, for example `/foo/bar`.
62
    pub path: String,
63
    /// The query component of a URL.
64
    /// `vec!(("baz".to_string(), "qux".to_string()))` represents the fragment
65
    /// `baz=qux` in the above example.
66
    pub query: Query,
67
    /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
68
    pub fragment: Option<String>
B
Brian Anderson 已提交
69 70
}

71
#[deriving(Clone, PartialEq)]
72 73
pub struct Path {
    /// The path component of a URL, for example `/foo/bar`.
74
    pub path: String,
75
    /// The query component of a URL.
76
    /// `vec!(("baz".to_string(), "qux".to_string()))` represents the fragment
77
    /// `baz=qux` in the above example.
78
    pub query: Query,
79
    /// The fragment component, such as `quz`.  Doesn't include the leading `#` character.
80
    pub fragment: Option<String>
81 82
}

83
/// An optional subcomponent of a URI authority component.
84
#[deriving(Clone, PartialEq, TotalEq)]
S
Steven Fackler 已提交
85
pub struct UserInfo {
86
    /// The user name.
87
    pub user: String,
88
    /// Password or other scheme-specific authentication information.
89
    pub pass: Option<String>
B
Brian Anderson 已提交
90 91
}

92
/// Represents the query component of a URI.
93
pub type Query = Vec<(String, String)>;
B
Brian Anderson 已提交
94 95

impl Url {
96
    pub fn new(scheme: String,
B
Brian Anderson 已提交
97
               user: Option<UserInfo>,
98 99 100
               host: String,
               port: Option<String>,
               path: String,
B
Brian Anderson 已提交
101
               query: Query,
102
               fragment: Option<String>)
B
Brian Anderson 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115
               -> Url {
        Url {
            scheme: scheme,
            user: user,
            host: host,
            port: port,
            path: path,
            query: query,
            fragment: fragment,
        }
    }
}

116
impl Path {
117
    pub fn new(path: String,
118
               query: Query,
119
               fragment: Option<String>)
120 121 122 123 124 125 126 127 128
               -> Path {
        Path {
            path: path,
            query: query,
            fragment: fragment,
        }
    }
}

B
Brian Anderson 已提交
129
impl UserInfo {
130
    #[inline]
131
    pub fn new(user: String, pass: Option<String>) -> UserInfo {
B
Brian Anderson 已提交
132 133 134 135
        UserInfo { user: user, pass: pass }
    }
}

136
fn encode_inner(s: &str, full_url: bool) -> String {
A
Alex Crichton 已提交
137
    let mut rdr = BufReader::new(s.as_bytes());
138
    let mut out = String::new();
A
Alex Crichton 已提交
139 140 141 142

    loop {
        let mut buf = [0];
        let ch = match rdr.read(buf) {
A
Alex Crichton 已提交
143 144
            Err(..) => break,
            Ok(..) => buf[0] as char,
A
Alex Crichton 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
        };

        match ch {
          // unreserved:
          'A' .. 'Z' |
          'a' .. 'z' |
          '0' .. '9' |
          '-' | '.' | '_' | '~' => {
            out.push_char(ch);
          }
          _ => {
              if full_url {
                match ch {
                  // gen-delims:
                  ':' | '/' | '?' | '#' | '[' | ']' | '@' |

                  // sub-delims:
                  '!' | '$' | '&' | '"' | '(' | ')' | '*' |
                  '+' | ',' | ';' | '=' => {
                    out.push_char(ch);
                  }

167
                  _ => out.push_str(format!("%{:X}", ch as uint).as_slice())
B
Brian Anderson 已提交
168
                }
A
Alex Crichton 已提交
169
            } else {
170
                out.push_str(format!("%{:X}", ch as uint).as_slice());
B
Brian Anderson 已提交
171
            }
A
Alex Crichton 已提交
172
          }
B
Brian Anderson 已提交
173 174
        }
    }
A
Alex Crichton 已提交
175

176
    out
B
Brian Anderson 已提交
177 178 179
}

/**
180
 * Encodes a URI by replacing reserved characters with percent-encoded
B
Brian Anderson 已提交
181 182 183
 * character sequences.
 *
 * This function is compliant with RFC 3986.
184 185 186 187
 *
 * # Example
 *
 * ```rust
188
 * use url::encode;
189
 *
190
 * let url = encode("https://example.com/Rust (programming language)");
191 192
 * println!("{}", url); // https://example.com/Rust%20(programming%20language)
 * ```
B
Brian Anderson 已提交
193
 */
194
pub fn encode(s: &str) -> String {
B
Brian Anderson 已提交
195 196 197 198
    encode_inner(s, true)
}

/**
C
Chris Shea 已提交
199
 * Encodes a URI component by replacing reserved characters with percent-
B
Brian Anderson 已提交
200 201 202 203 204
 * encoded character sequences.
 *
 * This function is compliant with RFC 3986.
 */

205
pub fn encode_component(s: &str) -> String {
B
Brian Anderson 已提交
206 207 208
    encode_inner(s, false)
}

209
fn decode_inner(s: &str, full_url: bool) -> String {
A
Alex Crichton 已提交
210
    let mut rdr = BufReader::new(s.as_bytes());
211
    let mut out = String::new();
A
Alex Crichton 已提交
212 213 214 215

    loop {
        let mut buf = [0];
        let ch = match rdr.read(buf) {
A
Alex Crichton 已提交
216 217
            Err(..) => break,
            Ok(..) => buf[0] as char
A
Alex Crichton 已提交
218 219 220 221 222
        };
        match ch {
          '%' => {
            let mut bytes = [0, 0];
            match rdr.read(bytes) {
A
Alex Crichton 已提交
223
                Ok(2) => {}
224
                _ => fail!() // FIXME: malformed url?
A
Alex Crichton 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
            }
            let ch = uint::parse_bytes(bytes, 16u).unwrap() as u8 as char;

            if full_url {
                // Only decode some characters:
                match ch {
                  // gen-delims:
                  ':' | '/' | '?' | '#' | '[' | ']' | '@' |

                  // sub-delims:
                  '!' | '$' | '&' | '"' | '(' | ')' | '*' |
                  '+' | ',' | ';' | '=' => {
                    out.push_char('%');
                    out.push_char(bytes[0u] as char);
                    out.push_char(bytes[1u] as char);
                  }

                  ch => out.push_char(ch)
B
Brian Anderson 已提交
243
                }
A
Alex Crichton 已提交
244 245
            } else {
                  out.push_char(ch);
B
Brian Anderson 已提交
246
            }
A
Alex Crichton 已提交
247 248
          }
          ch => out.push_char(ch)
B
Brian Anderson 已提交
249 250
        }
    }
A
Alex Crichton 已提交
251

252
    out
B
Brian Anderson 已提交
253 254 255
}

/**
256
 * Decodes a percent-encoded string representing a URI.
B
Brian Anderson 已提交
257
 *
258 259 260 261 262
 * This will only decode escape sequences generated by `encode`.
 *
 * # Example
 *
 * ```rust
263
 * use url::decode;
264
 *
265
 * let url = decode("https://example.com/Rust%20(programming%20language)");
266 267
 * println!("{}", url); // https://example.com/Rust (programming language)
 * ```
B
Brian Anderson 已提交
268
 */
269
pub fn decode(s: &str) -> String {
B
Brian Anderson 已提交
270 271 272 273 274 275
    decode_inner(s, true)
}

/**
 * Decode a string encoded with percent encoding.
 */
276
pub fn decode_component(s: &str) -> String {
B
Brian Anderson 已提交
277 278 279
    decode_inner(s, false)
}

280
fn encode_plus(s: &str) -> String {
A
Alex Crichton 已提交
281
    let mut rdr = BufReader::new(s.as_bytes());
282
    let mut out = String::new();
B
Brian Anderson 已提交
283

A
Alex Crichton 已提交
284 285 286
    loop {
        let mut buf = [0];
        let ch = match rdr.read(buf) {
A
Alex Crichton 已提交
287 288
            Ok(..) => buf[0] as char,
            Err(..) => break,
A
Alex Crichton 已提交
289 290 291 292 293 294
        };
        match ch {
          'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '.' | '-' => {
            out.push_char(ch);
          }
          ' ' => out.push_char('+'),
295
          _ => out.push_str(format!("%{:X}", ch as uint).as_slice())
A
Alex Crichton 已提交
296
        }
B
Brian Anderson 已提交
297
    }
A
Alex Crichton 已提交
298

299
    out
B
Brian Anderson 已提交
300 301 302 303 304
}

/**
 * Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
 */
305 306
pub fn encode_form_urlencoded(m: &HashMap<String, Vec<String>>) -> String {
    let mut out = String::new();
B
Brian Anderson 已提交
307 308
    let mut first = true;

D
Daniel Micay 已提交
309
    for (key, values) in m.iter() {
310
        let key = encode_plus(key.as_slice());
B
Brian Anderson 已提交
311

D
Daniel Micay 已提交
312
        for value in values.iter() {
B
Brian Anderson 已提交
313 314 315 316 317 318 319
            if first {
                first = false;
            } else {
                out.push_char('&');
                first = false;
            }

320 321
            out.push_str(format!("{}={}",
                                 key,
322
                                 encode_plus(value.as_slice())).as_slice());
B
Brian Anderson 已提交
323 324 325
        }
    }

326
    out
B
Brian Anderson 已提交
327 328 329 330 331 332
}

/**
 * Decode a string encoded with the 'application/x-www-form-urlencoded' media
 * type into a hashmap.
 */
333
#[allow(experimental)]
334
pub fn decode_form_urlencoded(s: &[u8]) -> HashMap<String, Vec<String>> {
A
Alex Crichton 已提交
335
    let mut rdr = BufReader::new(s);
336 337 338
    let mut m: HashMap<String,Vec<String>> = HashMap::new();
    let mut key = String::new();
    let mut value = String::new();
A
Alex Crichton 已提交
339 340 341 342 343
    let mut parsing_key = true;

    loop {
        let mut buf = [0];
        let ch = match rdr.read(buf) {
A
Alex Crichton 已提交
344 345
            Ok(..) => buf[0] as char,
            Err(..) => break,
A
Alex Crichton 已提交
346 347 348
        };
        match ch {
            '&' | ';' => {
349 350
                if key.len() > 0 && value.len() > 0 {
                    let mut values = match m.pop_equiv(&key.as_slice()) {
A
Alex Crichton 已提交
351
                        Some(values) => values,
S
Steven Fackler 已提交
352
                        None => vec!(),
A
Alex Crichton 已提交
353
                    };
B
Brian Anderson 已提交
354

355 356
                    values.push(value);
                    m.insert(key, values);
B
Brian Anderson 已提交
357 358
                }

A
Alex Crichton 已提交
359
                parsing_key = true;
360 361
                key = String::new();
                value = String::new();
A
Alex Crichton 已提交
362 363 364 365 366 367 368
            }
            '=' => parsing_key = false,
            ch => {
                let ch = match ch {
                    '%' => {
                        let mut bytes = [0, 0];
                        match rdr.read(bytes) {
A
Alex Crichton 已提交
369
                            Ok(2) => {}
370
                            _ => fail!() // FIXME: malformed?
A
Alex Crichton 已提交
371 372
                        }
                        uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
B
Brian Anderson 已提交
373
                    }
A
Alex Crichton 已提交
374 375 376 377 378 379 380 381
                    '+' => ' ',
                    ch => ch
                };

                if parsing_key {
                    key.push_char(ch)
                } else {
                    value.push_char(ch)
B
Brian Anderson 已提交
382 383 384
                }
            }
        }
A
Alex Crichton 已提交
385
    }
B
Brian Anderson 已提交
386

387 388
    if key.len() > 0 && value.len() > 0 {
        let mut values = match m.pop_equiv(&key.as_slice()) {
A
Alex Crichton 已提交
389
            Some(values) => values,
S
Steven Fackler 已提交
390
            None => vec!(),
A
Alex Crichton 已提交
391
        };
B
Brian Anderson 已提交
392

393 394
        values.push(value);
        m.insert(key, values);
B
Brian Anderson 已提交
395
    }
A
Alex Crichton 已提交
396 397

    m
B
Brian Anderson 已提交
398 399 400
}


401
fn split_char_first(s: &str, c: char) -> (String, String) {
B
Brian Anderson 已提交
402 403 404
    let len = s.len();
    let mut index = len;
    let mut mat = 0;
A
Alex Crichton 已提交
405 406 407 408
    let mut rdr = BufReader::new(s.as_bytes());
    loop {
        let mut buf = [0];
        let ch = match rdr.read(buf) {
A
Alex Crichton 已提交
409 410
            Ok(..) => buf[0] as char,
            Err(..) => break,
A
Alex Crichton 已提交
411 412 413
        };
        if ch == c {
            // found a match, adjust markers
A
Alex Crichton 已提交
414
            index = (rdr.tell().unwrap() as uint) - 1;
A
Alex Crichton 已提交
415 416
            mat = 1;
            break;
B
Brian Anderson 已提交
417 418 419
        }
    }
    if index+mat == len {
420
        return (s.slice(0, index).to_string(), "".to_string());
B
Brian Anderson 已提交
421
    } else {
422 423
        return (s.slice(0, index).to_string(),
                s.slice(index + mat, s.len()).to_string());
B
Brian Anderson 已提交
424 425 426
    }
}

427 428 429
impl fmt::Show for UserInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.pass {
A
Alex Crichton 已提交
430 431
            Some(ref pass) => write!(f, "{}:{}@", self.user, *pass),
            None => write!(f, "{}@", self.user),
432
        }
B
Brian Anderson 已提交
433 434 435 436
    }
}

fn query_from_str(rawquery: &str) -> Query {
S
Steven Fackler 已提交
437
    let mut query: Query = vec!();
B
Brian Anderson 已提交
438
    if !rawquery.is_empty() {
439
        for p in rawquery.split('&') {
B
Brian Anderson 已提交
440
            let (k, v) = split_char_first(p, '=');
441 442
            query.push((decode_component(k.as_slice()),
                        decode_component(v.as_slice())));
B
Brian Anderson 已提交
443 444 445 446 447
        };
    }
    return query;
}

448 449 450 451 452 453
/**
 * Converts an instance of a URI `Query` type to a string.
 *
 * # Example
 *
 * ```rust
454
 * let query = vec!(("title".to_string(), "The Village".to_string()),
455 456
 *                  ("north".to_string(), "52.91".to_string()),
 *                  ("west".to_string(), "4.10".to_string()));
457
 * println!("{}", url::query_to_str(&query));  // title=The%20Village&north=52.91&west=4.10
458 459
 * ```
 */
460
#[allow(unused_must_use)]
461
pub fn query_to_str(query: &Query) -> String {
462 463 464 465 466 467
    use std::io::MemWriter;
    use std::str;

    let mut writer = MemWriter::new();
    for (i, &(ref k, ref v)) in query.iter().enumerate() {
        if i != 0 { write!(&mut writer, "&"); }
468 469
        write!(&mut writer, "{}={}", encode_component(k.as_slice()),
               encode_component(v.as_slice()));
B
Brian Anderson 已提交
470
    }
471
    str::from_utf8_lossy(writer.unwrap().as_slice()).to_string()
B
Brian Anderson 已提交
472 473
}

474 475 476 477 478 479 480 481
/**
 * Returns a tuple of the URI scheme and the rest of the URI, or a parsing error.
 *
 * Does not include the separating `:` character.
 *
 * # Example
 *
 * ```rust
482
 * use url::get_scheme;
483 484 485
 *
 * let scheme = match get_scheme("https://example.com/") {
 *     Ok((sch, _)) => sch,
486
 *     Err(_) => "(None)".to_string(),
487 488 489 490
 * };
 * println!("Scheme in use: {}.", scheme); // Scheme in use: https.
 * ```
 */
491
pub fn get_scheme(rawurl: &str) -> Result<(String, String), String> {
492
    for (i,c) in rawurl.chars().enumerate() {
B
Brian Anderson 已提交
493
        match c {
494
          'A' .. 'Z' | 'a' .. 'z' => continue,
B
Brian Anderson 已提交
495 496
          '0' .. '9' | '+' | '-' | '.' => {
            if i == 0 {
497
                return Err("url: Scheme must begin with a \
498
                            letter.".to_string());
B
Brian Anderson 已提交
499
            }
500
            continue;
B
Brian Anderson 已提交
501 502 503
          }
          ':' => {
            if i == 0 {
504
                return Err("url: Scheme cannot be empty.".to_string());
B
Brian Anderson 已提交
505
            } else {
506 507
                return Ok((rawurl.slice(0,i).to_string(),
                           rawurl.slice(i+1,rawurl.len()).to_string()));
B
Brian Anderson 已提交
508 509 510
            }
          }
          _ => {
511
            return Err("url: Invalid character in scheme.".to_string());
B
Brian Anderson 已提交
512 513 514
          }
        }
    };
515
    return Err("url: Scheme must be terminated with a colon.".to_string());
B
Brian Anderson 已提交
516 517
}

518
#[deriving(Clone, PartialEq)]
B
Brian Anderson 已提交
519 520 521 522 523 524 525 526
enum Input {
    Digit, // all digits
    Hex, // digits and letters a-f
    Unreserved // all other legal characters
}

// returns userinfo, host, port, and unparsed part, or an error
fn get_authority(rawurl: &str) ->
527
    Result<(Option<UserInfo>, String, Option<String>, String), String> {
B
Brian Anderson 已提交
528 529
    if !rawurl.starts_with("//") {
        // there is no authority.
530
        return Ok((None, "".to_string(), None, rawurl.to_str().to_string()));
B
Brian Anderson 已提交
531 532 533 534 535 536 537 538 539 540 541 542 543
    }

    enum State {
        Start, // starting state
        PassHostPort, // could be in user or port
        Ip6Port, // either in ipv6 host or port
        Ip6Host, // are in an ipv6 host
        InHost, // are in a host - may be ipv6, but don't know yet
        InPort // are in port
    }

    let len = rawurl.len();
    let mut st = Start;
544
    let mut input = Digit; // most restricted, start here.
B
Brian Anderson 已提交
545 546

    let mut userinfo = None;
547
    let mut host = "".to_string();
B
Brian Anderson 已提交
548 549 550 551 552 553 554
    let mut port = None;

    let mut colon_count = 0;
    let mut pos = 0;
    let mut begin = 2;
    let mut end = len;

555
    for (i,c) in rawurl.chars().enumerate() {
556
        if i < 2 { continue; } // ignore the leading //
B
Brian Anderson 已提交
557 558 559 560 561

        // deal with input class first
        match c {
          '0' .. '9' => (),
          'A' .. 'F' | 'a' .. 'f' => {
562 563
            if input == Digit {
                input = Hex;
B
Brian Anderson 已提交
564 565 566 567
            }
          }
          'G' .. 'Z' | 'g' .. 'z' | '-' | '.' | '_' | '~' | '%' |
          '&' |'\'' | '(' | ')' | '+' | '!' | '*' | ',' | ';' | '=' => {
568
            input = Unreserved;
B
Brian Anderson 已提交
569 570 571 572 573
          }
          ':' | '@' | '?' | '#' | '/' => {
            // separators, don't change anything
          }
          _ => {
574
            return Err("Illegal character in authority".to_string());
B
Brian Anderson 已提交
575 576 577 578 579 580 581 582 583 584 585 586 587 588
          }
        }

        // now process states
        match c {
          ':' => {
            colon_count += 1;
            match st {
              Start => {
                pos = i;
                st = PassHostPort;
              }
              PassHostPort => {
                // multiple colons means ipv6 address.
589
                if input == Unreserved {
B
Brian Anderson 已提交
590
                    return Err(
591
                        "Illegal characters in IPv6 address.".to_string());
B
Brian Anderson 已提交
592 593 594 595 596
                }
                st = Ip6Host;
              }
              InHost => {
                pos = i;
597
                if input == Unreserved {
598
                    // must be port
599
                    host = rawurl.slice(begin, i).to_string();
600 601 602 603
                    st = InPort;
                } else {
                    // can't be sure whether this is an ipv6 address or a port
                    st = Ip6Port;
B
Brian Anderson 已提交
604 605 606
                }
              }
              Ip6Port => {
607
                if input == Unreserved {
608
                    return Err("Illegal characters in \
609
                                authority.".to_string());
B
Brian Anderson 已提交
610 611 612 613 614
                }
                st = Ip6Host;
              }
              Ip6Host => {
                if colon_count > 7 {
615
                    host = rawurl.slice(begin, i).to_string();
B
Brian Anderson 已提交
616 617 618 619 620
                    pos = i;
                    st = InPort;
                }
              }
              _ => {
621
                return Err("Invalid ':' in authority.".to_string());
B
Brian Anderson 已提交
622 623
              }
            }
624
            input = Digit; // reset input class
B
Brian Anderson 已提交
625 626 627
          }

          '@' => {
628
            input = Digit; // reset input class
B
Brian Anderson 已提交
629 630 631
            colon_count = 0; // reset count
            match st {
              Start => {
632
                let user = rawurl.slice(begin, i).to_string();
B
Brian Anderson 已提交
633 634 635 636
                userinfo = Some(UserInfo::new(user, None));
                st = InHost;
              }
              PassHostPort => {
637 638
                let user = rawurl.slice(begin, pos).to_string();
                let pass = rawurl.slice(pos+1, i).to_string();
B
Brian Anderson 已提交
639 640 641 642
                userinfo = Some(UserInfo::new(user, Some(pass)));
                st = InHost;
              }
              _ => {
643
                return Err("Invalid '@' in authority.".to_string());
B
Brian Anderson 已提交
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
              }
            }
            begin = i+1;
          }

          '?' | '#' | '/' => {
            end = i;
            break;
          }
          _ => ()
        }
    }

    // finish up
    match st {
      Start => {
660
        host = rawurl.slice(begin, end).to_string();
B
Brian Anderson 已提交
661 662
      }
      PassHostPort | Ip6Port => {
663
        if input != Digit {
664
            return Err("Non-digit characters in port.".to_string());
B
Brian Anderson 已提交
665
        }
666 667
        host = rawurl.slice(begin, pos).to_string();
        port = Some(rawurl.slice(pos+1, end).to_string());
B
Brian Anderson 已提交
668 669
      }
      Ip6Host | InHost => {
670
        host = rawurl.slice(begin, end).to_string();
B
Brian Anderson 已提交
671 672
      }
      InPort => {
673
        if input != Digit {
674
            return Err("Non-digit characters in port.".to_string());
B
Brian Anderson 已提交
675
        }
676
        port = Some(rawurl.slice(pos+1, end).to_string());
B
Brian Anderson 已提交
677 678 679
      }
    }

680
    let rest = rawurl.slice(end, len).to_string();
B
Brian Anderson 已提交
681 682 683 684 685 686
    return Ok((userinfo, host, port, rest));
}


// returns the path and unparsed part of url, or an error
fn get_path(rawurl: &str, authority: bool) ->
687
    Result<(String, String), String> {
B
Brian Anderson 已提交
688 689
    let len = rawurl.len();
    let mut end = len;
690
    for (i,c) in rawurl.chars().enumerate() {
B
Brian Anderson 已提交
691 692 693
        match c {
          'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '&' |'\'' | '(' | ')' | '.'
          | '@' | ':' | '%' | '/' | '+' | '!' | '*' | ',' | ';' | '='
694
          | '_' | '-' | '~' => {
695
            continue;
B
Brian Anderson 已提交
696 697 698 699 700
          }
          '?' | '#' => {
            end = i;
            break;
          }
701
          _ => return Err("Invalid character in path.".to_string())
B
Brian Anderson 已提交
702 703 704 705 706
        }
    }

    if authority {
        if end != 0 && !rawurl.starts_with("/") {
707
            return Err("Non-empty path must begin with\
708
                              '/' in presence of authority.".to_string());
B
Brian Anderson 已提交
709 710 711 712
        }
    }

    return Ok((decode_component(rawurl.slice(0, end)),
713
                    rawurl.slice(end, len).to_string()));
B
Brian Anderson 已提交
714 715 716 717
}

// returns the parsed query and the fragment, if present
fn get_query_fragment(rawurl: &str) ->
718
    Result<(Query, Option<String>), String> {
B
Brian Anderson 已提交
719 720 721 722 723
    if !rawurl.starts_with("?") {
        if rawurl.starts_with("#") {
            let f = decode_component(rawurl.slice(
                                                1,
                                                rawurl.len()));
S
Steven Fackler 已提交
724
            return Ok((vec!(), Some(f)));
B
Brian Anderson 已提交
725
        } else {
S
Steven Fackler 已提交
726
            return Ok((vec!(), None));
B
Brian Anderson 已提交
727 728 729 730
        }
    }
    let (q, r) = split_char_first(rawurl.slice(1, rawurl.len()), '#');
    let f = if r.len() != 0 {
731 732 733 734 735
        Some(decode_component(r.as_slice()))
    } else {
        None
    };
    return Ok((query_from_str(q.as_slice()), f));
B
Brian Anderson 已提交
736 737 738
}

/**
739
 * Parses a URL, converting it from a string to `Url` representation.
B
Brian Anderson 已提交
740 741 742
 *
 * # Arguments
 *
743
 * `rawurl` - a string representing the full URL, including scheme.
B
Brian Anderson 已提交
744 745 746
 *
 * # Returns
 *
747
 * A `Url` struct type representing the URL.
B
Brian Anderson 已提交
748
 */
749
pub fn from_str(rawurl: &str) -> Result<Url, String> {
B
Brian Anderson 已提交
750 751 752 753 754 755 756
    // scheme
    let (scheme, rest) = match get_scheme(rawurl) {
        Ok(val) => val,
        Err(e) => return Err(e),
    };

    // authority
757
    let (userinfo, host, port, rest) = match get_authority(rest.as_slice()) {
B
Brian Anderson 已提交
758 759 760 761 762
        Ok(val) => val,
        Err(e) => return Err(e),
    };

    // path
763 764
    let has_authority = host.len() > 0;
    let (path, rest) = match get_path(rest.as_slice(), has_authority) {
B
Brian Anderson 已提交
765 766 767 768 769
        Ok(val) => val,
        Err(e) => return Err(e),
    };

    // query and fragment
770
    let (query, fragment) = match get_query_fragment(rest.as_slice()) {
B
Brian Anderson 已提交
771 772 773 774 775 776 777
        Ok(val) => val,
        Err(e) => return Err(e),
    };

    Ok(Url::new(scheme, userinfo, host, port, path, query, fragment))
}

778
pub fn path_from_str(rawpath: &str) -> Result<Path, String> {
779 780 781 782 783 784
    let (path, rest) = match get_path(rawpath, false) {
        Ok(val) => val,
        Err(e) => return Err(e)
    };

    // query and fragment
785
    let (query, fragment) = match get_query_fragment(rest.as_slice()) {
786 787 788 789 790 791 792
        Ok(val) => val,
        Err(e) => return Err(e),
    };

    Ok(Path{ path: path, query: query, fragment: fragment })
}

B
Brian Anderson 已提交
793 794 795 796 797 798 799 800 801
impl FromStr for Url {
    fn from_str(s: &str) -> Option<Url> {
        match from_str(s) {
            Ok(url) => Some(url),
            Err(_) => None
        }
    }
}

802 803 804 805 806 807 808 809 810
impl FromStr for Path {
    fn from_str(s: &str) -> Option<Path> {
        match path_from_str(s) {
            Ok(path) => Some(path),
            Err(_) => None
        }
    }
}

811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
impl fmt::Show for Url {
    /**
     * Converts a URL from `Url` to string representation.
     *
     * # Arguments
     *
     * `url` - a URL.
     *
     * # Returns
     *
     * A string that contains the formatted URL. Note that this will usually
     * be an inverse of `from_str` but might strip out unneeded separators;
     * for example, "http://somehost.com?", when parsed and formatted, will
     * result in just "http://somehost.com".
     */
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
A
Alex Crichton 已提交
827
        try!(write!(f, "{}:", self.scheme));
828 829

        if !self.host.is_empty() {
A
Alex Crichton 已提交
830
            try!(write!(f, "//"));
831
            match self.user {
A
Alex Crichton 已提交
832
                Some(ref user) => try!(write!(f, "{}", *user)),
833 834 835
                None => {}
            }
            match self.port {
A
Alex Crichton 已提交
836
                Some(ref port) => try!(write!(f, "{}:{}", self.host,
837
                                                *port)),
A
Alex Crichton 已提交
838
                None => try!(write!(f, "{}", self.host)),
839
            }
840
        }
841

A
Alex Crichton 已提交
842
        try!(write!(f, "{}", self.path));
843

844
        if !self.query.is_empty() {
A
Alex Crichton 已提交
845
            try!(write!(f, "?{}", query_to_str(&self.query)));
846
        }
847

848
        match self.fragment {
849
            Some(ref fragment) => {
A
Alex Crichton 已提交
850
                write!(f, "\\#{}", encode_component(fragment.as_slice()))
851
            }
852 853
            None => Ok(()),
        }
B
Brian Anderson 已提交
854 855 856
    }
}

857 858
impl fmt::Show for Path {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
A
Alex Crichton 已提交
859
        try!(write!(f, "{}", self.path));
860
        if !self.query.is_empty() {
A
Alex Crichton 已提交
861
            try!(write!(f, "?{}", self.query))
862 863 864 865
        }

        match self.fragment {
            Some(ref fragment) => {
A
Alex Crichton 已提交
866
                write!(f, "\\#{}", encode_component(fragment.as_slice()))
867 868 869
            }
            None => Ok(())
        }
870 871 872
    }
}

E
Erick Tryzelaar 已提交
873 874 875
impl<S: Writer> Hash<S> for Url {
    fn hash(&self, state: &mut S) {
        self.to_str().hash(state)
B
Brian Anderson 已提交
876 877 878
    }
}

E
Erick Tryzelaar 已提交
879 880 881
impl<S: Writer> Hash<S> for Path {
    fn hash(&self, state: &mut S) {
        self.to_str().hash(state)
882 883 884
    }
}

B
Brian Anderson 已提交
885 886 887 888 889 890
// Put a few tests outside of the 'test' module so they can test the internal
// functions and those functions don't need 'pub'

#[test]
fn test_split_char_first() {
    let (u,v) = split_char_first("hello, sweet world", ',');
891 892
    assert_eq!(u, "hello".to_string());
    assert_eq!(v, " sweet world".to_string());
B
Brian Anderson 已提交
893 894

    let (u,v) = split_char_first("hello sweet world", ',');
895 896
    assert_eq!(u, "hello sweet world".to_string());
    assert_eq!(v, "".to_string());
B
Brian Anderson 已提交
897 898 899 900 901 902
}

#[test]
fn test_get_authority() {
    let (u, h, p, r) = get_authority(
        "//user:pass@rust-lang.org/something").unwrap();
903 904
    assert_eq!(u, Some(UserInfo::new("user".to_string(), Some("pass".to_string()))));
    assert_eq!(h, "rust-lang.org".to_string());
B
Brian Anderson 已提交
905
    assert!(p.is_none());
906
    assert_eq!(r, "/something".to_string());
B
Brian Anderson 已提交
907 908 909 910

    let (u, h, p, r) = get_authority(
        "//rust-lang.org:8000?something").unwrap();
    assert!(u.is_none());
911 912 913
    assert_eq!(h, "rust-lang.org".to_string());
    assert_eq!(p, Some("8000".to_string()));
    assert_eq!(r, "?something".to_string());
B
Brian Anderson 已提交
914 915 916 917

    let (u, h, p, r) = get_authority(
        "//rust-lang.org#blah").unwrap();
    assert!(u.is_none());
918
    assert_eq!(h, "rust-lang.org".to_string());
B
Brian Anderson 已提交
919
    assert!(p.is_none());
920
    assert_eq!(r, "#blah".to_string());
B
Brian Anderson 已提交
921 922 923 924

    // ipv6 tests
    let (_, h, _, _) = get_authority(
        "//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap();
925
    assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
B
Brian Anderson 已提交
926 927 928

    let (_, h, p, _) = get_authority(
        "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap();
929 930
    assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
    assert_eq!(p, Some("8000".to_string()));
B
Brian Anderson 已提交
931 932 933 934

    let (u, h, p, _) = get_authority(
        "//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
    ).unwrap();
935 936 937
    assert_eq!(u, Some(UserInfo::new("us".to_string(), Some("p".to_string()))));
    assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334".to_string());
    assert_eq!(p, Some("8000".to_string()));
B
Brian Anderson 已提交
938 939 940 941 942 943 944 945 946 947 948

    // invalid authorities;
    assert!(get_authority("//user:pass@rust-lang:something").is_err());
    assert!(get_authority("//user@rust-lang:something:/path").is_err());
    assert!(get_authority(
        "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:800a").is_err());
    assert!(get_authority(
        "//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000:00").is_err());

    // these parse as empty, because they don't start with '//'
    let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
949
    assert_eq!(h, "".to_string());
B
Brian Anderson 已提交
950
    let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
951
    assert_eq!(h, "".to_string());
B
Brian Anderson 已提交
952 953 954 955 956
}

#[test]
fn test_get_path() {
    let (p, r) = get_path("/something+%20orother", true).unwrap();
957 958
    assert_eq!(p, "/something+ orother".to_string());
    assert_eq!(r, "".to_string());
B
Brian Anderson 已提交
959
    let (p, r) = get_path("test@email.com#fragment", false).unwrap();
960 961
    assert_eq!(p, "test@email.com".to_string());
    assert_eq!(r, "#fragment".to_string());
B
Brian Anderson 已提交
962
    let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap();
963 964
    assert_eq!(p, "/gen/:addr=".to_string());
    assert_eq!(r, "?q=v".to_string());
B
Brian Anderson 已提交
965 966 967 968 969 970 971

    //failure cases
    assert!(get_path("something?q", true).is_err());
}

#[cfg(test)]
mod tests {
S
Steven Fackler 已提交
972
    use {encode_form_urlencoded, decode_form_urlencoded,
973 974
         decode, encode, from_str, encode_component, decode_component,
         path_from_str, UserInfo, get_scheme};
B
Brian Anderson 已提交
975

976
    use collections::HashMap;
B
Brian Anderson 已提交
977 978 979

    #[test]
    fn test_url_parse() {
980
        let url = "http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
B
Brian Anderson 已提交
981 982 983

        let up = from_str(url);
        let u = up.unwrap();
984 985 986 987 988 989 990
        assert_eq!(&u.scheme, &"http".to_string());
        assert_eq!(&u.user, &Some(UserInfo::new("user".to_string(), Some("pass".to_string()))));
        assert_eq!(&u.host, &"rust-lang.org".to_string());
        assert_eq!(&u.port, &Some("8080".to_string()));
        assert_eq!(&u.path, &"/doc/~u".to_string());
        assert_eq!(&u.query, &vec!(("s".to_string(), "v".to_string())));
        assert_eq!(&u.fragment, &Some("something".to_string()));
B
Brian Anderson 已提交
991 992
    }

993 994
    #[test]
    fn test_path_parse() {
995
        let path = "/doc/~u?s=v#something";
996 997 998

        let up = path_from_str(path);
        let u = up.unwrap();
999 1000 1001
        assert_eq!(&u.path, &"/doc/~u".to_string());
        assert_eq!(&u.query, &vec!(("s".to_string(), "v".to_string())));
        assert_eq!(&u.fragment, &Some("something".to_string()));
1002 1003
    }

B
Brian Anderson 已提交
1004 1005
    #[test]
    fn test_url_parse_host_slash() {
1006
        let urlstr = "http://0.42.42.42/";
B
Brian Anderson 已提交
1007
        let url = from_str(urlstr).unwrap();
1008 1009
        assert!(url.host == "0.42.42.42".to_string());
        assert!(url.path == "/".to_string());
B
Brian Anderson 已提交
1010 1011
    }

1012 1013
    #[test]
    fn test_path_parse_host_slash() {
1014
        let pathstr = "/";
1015
        let path = path_from_str(pathstr).unwrap();
1016
        assert!(path.path == "/".to_string());
1017 1018
    }

1019 1020
    #[test]
    fn test_url_host_with_port() {
1021
        let urlstr = "scheme://host:1234";
1022
        let url = from_str(urlstr).unwrap();
1023 1024 1025
        assert_eq!(&url.scheme, &"scheme".to_string());
        assert_eq!(&url.host, &"host".to_string());
        assert_eq!(&url.port, &Some("1234".to_string()));
1026
        // is empty path really correct? Other tests think so
1027
        assert_eq!(&url.path, &"".to_string());
1028
        let urlstr = "scheme://host:1234/";
1029
        let url = from_str(urlstr).unwrap();
1030 1031 1032 1033
        assert_eq!(&url.scheme, &"scheme".to_string());
        assert_eq!(&url.host, &"host".to_string());
        assert_eq!(&url.port, &Some("1234".to_string()));
        assert_eq!(&url.path, &"/".to_string());
1034 1035
    }

B
Brian Anderson 已提交
1036 1037
    #[test]
    fn test_url_with_underscores() {
1038
        let urlstr = "http://dotcom.com/file_name.html";
B
Brian Anderson 已提交
1039
        let url = from_str(urlstr).unwrap();
1040
        assert!(url.path == "/file_name.html".to_string());
B
Brian Anderson 已提交
1041 1042
    }

1043 1044
    #[test]
    fn test_path_with_underscores() {
1045
        let pathstr = "/file_name.html";
1046
        let path = path_from_str(pathstr).unwrap();
1047
        assert!(path.path == "/file_name.html".to_string());
1048 1049
    }

B
Brian Anderson 已提交
1050 1051
    #[test]
    fn test_url_with_dashes() {
1052
        let urlstr = "http://dotcom.com/file-name.html";
B
Brian Anderson 已提交
1053
        let url = from_str(urlstr).unwrap();
1054
        assert!(url.path == "/file-name.html".to_string());
B
Brian Anderson 已提交
1055 1056
    }

1057 1058
    #[test]
    fn test_path_with_dashes() {
1059
        let pathstr = "/file-name.html";
1060
        let path = path_from_str(pathstr).unwrap();
1061
        assert!(path.path == "/file-name.html".to_string());
1062 1063
    }

B
Brian Anderson 已提交
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
    #[test]
    fn test_no_scheme() {
        assert!(get_scheme("noschemehere.html").is_err());
    }

    #[test]
    fn test_invalid_scheme_errors() {
        assert!(from_str("99://something").is_err());
        assert!(from_str("://something").is_err());
    }

    #[test]
    fn test_full_url_parse_and_format() {
1077 1078
        let url = "http://user:pass@rust-lang.org/doc?s=v#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1079 1080 1081 1082
    }

    #[test]
    fn test_userless_url_parse_and_format() {
1083 1084
        let url = "http://rust-lang.org/doc?s=v#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1085 1086 1087 1088
    }

    #[test]
    fn test_queryless_url_parse_and_format() {
1089 1090
        let url = "http://user:pass@rust-lang.org/doc#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1091 1092 1093 1094
    }

    #[test]
    fn test_empty_query_url_parse_and_format() {
1095 1096 1097
        let url = "http://user:pass@rust-lang.org/doc?#something";
        let should_be = "http://user:pass@rust-lang.org/doc#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), should_be);
B
Brian Anderson 已提交
1098 1099 1100 1101
    }

    #[test]
    fn test_fragmentless_url_parse_and_format() {
1102 1103
        let url = "http://user:pass@rust-lang.org/doc?q=v";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1104 1105 1106 1107
    }

    #[test]
    fn test_minimal_url_parse_and_format() {
1108 1109
        let url = "http://rust-lang.org/doc";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1110 1111
    }

1112 1113
    #[test]
    fn test_url_with_port_parse_and_format() {
1114 1115
        let url = "http://rust-lang.org:80/doc";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
1116 1117
    }

B
Brian Anderson 已提交
1118 1119
    #[test]
    fn test_scheme_host_only_url_parse_and_format() {
1120 1121
        let url = "http://rust-lang.org";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1122 1123 1124 1125
    }

    #[test]
    fn test_pathless_url_parse_and_format() {
1126 1127
        let url = "http://user:pass@rust-lang.org?q=v#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1128 1129 1130 1131
    }

    #[test]
    fn test_scheme_host_fragment_only_url_parse_and_format() {
1132 1133
        let url = "http://rust-lang.org#something";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1134 1135 1136 1137
    }

    #[test]
    fn test_url_component_encoding() {
1138
        let url = "http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B";
B
Brian Anderson 已提交
1139
        let u = from_str(url).unwrap();
1140 1141
        assert!(u.path == "/doc uments".to_string());
        assert!(u.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
B
Brian Anderson 已提交
1142 1143
    }

1144 1145
    #[test]
    fn test_path_component_encoding() {
1146
        let path = "/doc%20uments?ba%25d%20=%23%26%2B";
1147
        let p = path_from_str(path).unwrap();
1148 1149
        assert!(p.path == "/doc uments".to_string());
        assert!(p.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
1150 1151
    }

B
Brian Anderson 已提交
1152 1153
    #[test]
    fn test_url_without_authority() {
1154 1155
        let url = "mailto:test@email.com";
        assert_eq!(from_str(url).unwrap().to_str().as_slice(), url);
B
Brian Anderson 已提交
1156 1157 1158 1159
    }

    #[test]
    fn test_encode() {
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
        assert_eq!(encode(""), "".to_string());
        assert_eq!(encode("http://example.com"), "http://example.com".to_string());
        assert_eq!(encode("foo bar% baz"), "foo%20bar%25%20baz".to_string());
        assert_eq!(encode(" "), "%20".to_string());
        assert_eq!(encode("!"), "!".to_string());
        assert_eq!(encode("\""), "\"".to_string());
        assert_eq!(encode("#"), "#".to_string());
        assert_eq!(encode("$"), "$".to_string());
        assert_eq!(encode("%"), "%25".to_string());
        assert_eq!(encode("&"), "&".to_string());
        assert_eq!(encode("'"), "%27".to_string());
        assert_eq!(encode("("), "(".to_string());
        assert_eq!(encode(")"), ")".to_string());
        assert_eq!(encode("*"), "*".to_string());
        assert_eq!(encode("+"), "+".to_string());
        assert_eq!(encode(","), ",".to_string());
        assert_eq!(encode("/"), "/".to_string());
        assert_eq!(encode(":"), ":".to_string());
        assert_eq!(encode(";"), ";".to_string());
        assert_eq!(encode("="), "=".to_string());
        assert_eq!(encode("?"), "?".to_string());
        assert_eq!(encode("@"), "@".to_string());
        assert_eq!(encode("["), "[".to_string());
        assert_eq!(encode("]"), "]".to_string());
B
Brian Anderson 已提交
1184 1185 1186 1187
    }

    #[test]
    fn test_encode_component() {
1188
        assert_eq!(encode_component(""), "".to_string());
B
Brian Anderson 已提交
1189
        assert!(encode_component("http://example.com") ==
1190
            "http%3A%2F%2Fexample.com".to_string());
B
Brian Anderson 已提交
1191
        assert!(encode_component("foo bar% baz") ==
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
            "foo%20bar%25%20baz".to_string());
        assert_eq!(encode_component(" "), "%20".to_string());
        assert_eq!(encode_component("!"), "%21".to_string());
        assert_eq!(encode_component("#"), "%23".to_string());
        assert_eq!(encode_component("$"), "%24".to_string());
        assert_eq!(encode_component("%"), "%25".to_string());
        assert_eq!(encode_component("&"), "%26".to_string());
        assert_eq!(encode_component("'"), "%27".to_string());
        assert_eq!(encode_component("("), "%28".to_string());
        assert_eq!(encode_component(")"), "%29".to_string());
        assert_eq!(encode_component("*"), "%2A".to_string());
        assert_eq!(encode_component("+"), "%2B".to_string());
        assert_eq!(encode_component(","), "%2C".to_string());
        assert_eq!(encode_component("/"), "%2F".to_string());
        assert_eq!(encode_component(":"), "%3A".to_string());
        assert_eq!(encode_component(";"), "%3B".to_string());
        assert_eq!(encode_component("="), "%3D".to_string());
        assert_eq!(encode_component("?"), "%3F".to_string());
        assert_eq!(encode_component("@"), "%40".to_string());
        assert_eq!(encode_component("["), "%5B".to_string());
        assert_eq!(encode_component("]"), "%5D".to_string());
B
Brian Anderson 已提交
1213 1214 1215 1216
    }

    #[test]
    fn test_decode() {
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
        assert_eq!(decode(""), "".to_string());
        assert_eq!(decode("abc/def 123"), "abc/def 123".to_string());
        assert_eq!(decode("abc%2Fdef%20123"), "abc%2Fdef 123".to_string());
        assert_eq!(decode("%20"), " ".to_string());
        assert_eq!(decode("%21"), "%21".to_string());
        assert_eq!(decode("%22"), "%22".to_string());
        assert_eq!(decode("%23"), "%23".to_string());
        assert_eq!(decode("%24"), "%24".to_string());
        assert_eq!(decode("%25"), "%".to_string());
        assert_eq!(decode("%26"), "%26".to_string());
        assert_eq!(decode("%27"), "'".to_string());
        assert_eq!(decode("%28"), "%28".to_string());
        assert_eq!(decode("%29"), "%29".to_string());
        assert_eq!(decode("%2A"), "%2A".to_string());
        assert_eq!(decode("%2B"), "%2B".to_string());
        assert_eq!(decode("%2C"), "%2C".to_string());
        assert_eq!(decode("%2F"), "%2F".to_string());
        assert_eq!(decode("%3A"), "%3A".to_string());
        assert_eq!(decode("%3B"), "%3B".to_string());
        assert_eq!(decode("%3D"), "%3D".to_string());
        assert_eq!(decode("%3F"), "%3F".to_string());
        assert_eq!(decode("%40"), "%40".to_string());
        assert_eq!(decode("%5B"), "%5B".to_string());
        assert_eq!(decode("%5D"), "%5D".to_string());
B
Brian Anderson 已提交
1241 1242 1243 1244
    }

    #[test]
    fn test_decode_component() {
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
        assert_eq!(decode_component(""), "".to_string());
        assert_eq!(decode_component("abc/def 123"), "abc/def 123".to_string());
        assert_eq!(decode_component("abc%2Fdef%20123"), "abc/def 123".to_string());
        assert_eq!(decode_component("%20"), " ".to_string());
        assert_eq!(decode_component("%21"), "!".to_string());
        assert_eq!(decode_component("%22"), "\"".to_string());
        assert_eq!(decode_component("%23"), "#".to_string());
        assert_eq!(decode_component("%24"), "$".to_string());
        assert_eq!(decode_component("%25"), "%".to_string());
        assert_eq!(decode_component("%26"), "&".to_string());
        assert_eq!(decode_component("%27"), "'".to_string());
        assert_eq!(decode_component("%28"), "(".to_string());
        assert_eq!(decode_component("%29"), ")".to_string());
        assert_eq!(decode_component("%2A"), "*".to_string());
        assert_eq!(decode_component("%2B"), "+".to_string());
        assert_eq!(decode_component("%2C"), ",".to_string());
        assert_eq!(decode_component("%2F"), "/".to_string());
        assert_eq!(decode_component("%3A"), ":".to_string());
        assert_eq!(decode_component("%3B"), ";".to_string());
        assert_eq!(decode_component("%3D"), "=".to_string());
        assert_eq!(decode_component("%3F"), "?".to_string());
        assert_eq!(decode_component("%40"), "@".to_string());
        assert_eq!(decode_component("%5B"), "[".to_string());
        assert_eq!(decode_component("%5D"), "]".to_string());
B
Brian Anderson 已提交
1269 1270 1271 1272 1273
    }

    #[test]
    fn test_encode_form_urlencoded() {
        let mut m = HashMap::new();
1274
        assert_eq!(encode_form_urlencoded(&m), "".to_string());
B
Brian Anderson 已提交
1275

1276 1277 1278
        m.insert("".to_string(), vec!());
        m.insert("foo".to_string(), vec!());
        assert_eq!(encode_form_urlencoded(&m), "".to_string());
B
Brian Anderson 已提交
1279 1280

        let mut m = HashMap::new();
1281 1282
        m.insert("foo".to_string(), vec!("bar".to_string(), "123".to_string()));
        assert_eq!(encode_form_urlencoded(&m), "foo=bar&foo=123".to_string());
B
Brian Anderson 已提交
1283 1284

        let mut m = HashMap::new();
1285
        m.insert("foo bar".to_string(), vec!("abc".to_string(), "12 = 34".to_string()));
B
Brian Anderson 已提交
1286
        assert!(encode_form_urlencoded(&m) ==
1287
            "foo+bar=abc&foo+bar=12+%3D+34".to_string());
B
Brian Anderson 已提交
1288 1289 1290 1291 1292 1293 1294 1295 1296
    }

    #[test]
    fn test_decode_form_urlencoded() {
        assert_eq!(decode_form_urlencoded([]).len(), 0);

        let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes();
        let form = decode_form_urlencoded(s);
        assert_eq!(form.len(), 2);
1297 1298 1299
        assert_eq!(form.get(&"a".to_string()), &vec!("1".to_string()));
        assert_eq!(form.get(&"foo bar".to_string()),
                   &vec!("abc".to_string(), "12 = 34".to_string()));
B
Brian Anderson 已提交
1300 1301
    }
}