2014-02-13 22:28:22 +00:00
|
|
|
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
2013-07-17 17:47:20 -07:00
|
|
|
// 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)
|
|
|
|
|
2014-07-01 07:12:04 -07:00
|
|
|
#![crate_name = "url"]
|
2014-07-29 14:07:09 +01:00
|
|
|
#![deprecated="This is being removed. Use rust-url instead. http://servo.github.io/rust-url/"]
|
2014-08-11 17:33:43 -07:00
|
|
|
#![allow(deprecated)]
|
2014-03-21 18:05:05 -07:00
|
|
|
#![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",
|
2014-07-11 11:21:57 -07:00
|
|
|
html_root_url = "http://doc.rust-lang.org/master/",
|
2014-06-06 09:12:18 -07:00
|
|
|
html_playground_url = "http://play.rust-lang.org/")]
|
2014-03-21 18:05:05 -07:00
|
|
|
#![feature(default_type_params)]
|
2014-03-14 11:16:10 -07:00
|
|
|
|
2014-05-29 19:03:06 -07:00
|
|
|
use std::collections::HashMap;
|
2014-02-19 18:56:33 -08:00
|
|
|
use std::fmt;
|
2014-04-02 16:54:22 -07:00
|
|
|
use std::from_str::FromStr;
|
2014-05-29 19:03:06 -07:00
|
|
|
use std::hash;
|
2013-07-17 17:47:20 -07:00
|
|
|
use std::uint;
|
2014-06-26 16:43:32 +01:00
|
|
|
use std::path::BytesContainer;
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2013-12-01 12:30:32 +00:00
|
|
|
/// 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.
|
2013-12-01 22:25:58 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2014-07-29 14:07:09 +01:00
|
|
|
/// # #![allow(deprecated)]
|
2014-06-20 01:11:32 +01:00
|
|
|
/// use url::Url;
|
2013-12-22 13:31:37 -08:00
|
|
|
///
|
2014-06-20 01:11:32 +01:00
|
|
|
/// let raw = "https://username@example.com:8080/foo/bar?baz=qux#quz";
|
|
|
|
/// match Url::parse(raw) {
|
|
|
|
/// Ok(u) => println!("Parsed '{}'", u),
|
|
|
|
/// Err(e) => println!("Couldn't parse '{}': {}", raw, e),
|
|
|
|
/// }
|
2013-12-01 22:25:58 +00:00
|
|
|
/// ```
|
2014-05-31 10:43:52 -07:00
|
|
|
#[deriving(Clone, PartialEq, Eq)]
|
2013-09-25 19:42:02 -07:00
|
|
|
pub struct Url {
|
2013-12-01 22:25:58 +00:00
|
|
|
/// The scheme part of a URL, such as `https` in the above example.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub scheme: String,
|
2013-12-01 22:25:58 +00:00
|
|
|
/// A URL subcomponent for user authentication. `username` in the above example.
|
2014-03-28 12:41:44 -07:00
|
|
|
pub user: Option<UserInfo>,
|
2013-12-01 22:25:58 +00:00
|
|
|
/// A domain name or IP address. For example, `example.com`.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub host: String,
|
2013-12-01 12:30:32 +00:00
|
|
|
/// A TCP port number, for example `8080`.
|
2014-06-23 23:13:11 +01:00
|
|
|
pub port: Option<u16>,
|
2014-06-21 00:05:06 +01:00
|
|
|
/// The path component of a URL, for example `/foo/bar?baz=qux#quz`.
|
|
|
|
pub path: Path,
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:05:06 +01:00
|
|
|
#[deriving(Clone, PartialEq, Eq)]
|
2014-02-12 21:28:58 -08:00
|
|
|
pub struct Path {
|
|
|
|
/// The path component of a URL, for example `/foo/bar`.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub path: String,
|
2014-05-13 17:51:05 -07:00
|
|
|
/// The query component of a URL.
|
2014-06-21 00:05:06 +01:00
|
|
|
/// `vec![("baz".to_string(), "qux".to_string())]` represents the fragment
|
2014-05-13 17:51:05 -07:00
|
|
|
/// `baz=qux` in the above example.
|
2014-03-28 12:41:44 -07:00
|
|
|
pub query: Query,
|
2014-06-20 01:30:12 +01:00
|
|
|
/// The fragment component, such as `quz`. Not including the leading `#` character.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fragment: Option<String>
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-12-01 12:30:32 +00:00
|
|
|
/// An optional subcomponent of a URI authority component.
|
2014-05-31 10:43:52 -07:00
|
|
|
#[deriving(Clone, PartialEq, Eq)]
|
2013-09-25 19:42:02 -07:00
|
|
|
pub struct UserInfo {
|
2013-12-01 12:30:32 +00:00
|
|
|
/// The user name.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub user: String,
|
2013-12-01 12:30:32 +00:00
|
|
|
/// Password or other scheme-specific authentication information.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub pass: Option<String>
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2013-12-01 12:30:32 +00:00
|
|
|
/// Represents the query component of a URI.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub type Query = Vec<(String, String)>;
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
impl Url {
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn new(scheme: String,
|
2013-07-17 17:47:20 -07:00
|
|
|
user: Option<UserInfo>,
|
2014-05-22 16:57:53 -07:00
|
|
|
host: String,
|
2014-06-23 23:13:11 +01:00
|
|
|
port: Option<u16>,
|
2014-05-22 16:57:53 -07:00
|
|
|
path: String,
|
2013-07-17 17:47:20 -07:00
|
|
|
query: Query,
|
2014-05-22 16:57:53 -07:00
|
|
|
fragment: Option<String>)
|
2013-07-17 17:47:20 -07:00
|
|
|
-> Url {
|
|
|
|
Url {
|
|
|
|
scheme: scheme,
|
|
|
|
user: user,
|
|
|
|
host: host,
|
|
|
|
port: port,
|
2014-06-21 00:05:06 +01:00
|
|
|
path: Path::new(path, query, fragment)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
2014-06-20 01:11:32 +01:00
|
|
|
|
|
|
|
/// Parses a URL, converting it from a string to a `Url` representation.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * rawurl - a string representing the full URL, including scheme.
|
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// `Err(e)` if the string did not represent a valid URL, where `e` is a
|
|
|
|
/// `String` error message. Otherwise, `Ok(u)` where `u` is a `Url` struct
|
|
|
|
/// representing the URL.
|
2014-06-21 00:42:21 +01:00
|
|
|
pub fn parse(rawurl: &str) -> DecodeResult<Url> {
|
2014-06-20 01:11:32 +01:00
|
|
|
// scheme
|
|
|
|
let (scheme, rest) = try!(get_scheme(rawurl));
|
|
|
|
|
|
|
|
// authority
|
2014-06-23 23:13:11 +01:00
|
|
|
let (userinfo, host, port, rest) = try!(get_authority(rest));
|
2014-06-20 01:11:32 +01:00
|
|
|
|
|
|
|
// path
|
|
|
|
let has_authority = host.len() > 0;
|
2014-06-23 23:13:11 +01:00
|
|
|
let (path, rest) = try!(get_path(rest, has_authority));
|
2014-06-20 01:11:32 +01:00
|
|
|
|
|
|
|
// query and fragment
|
2014-06-23 23:13:11 +01:00
|
|
|
let (query, fragment) = try!(get_query_fragment(rest));
|
2014-06-20 01:11:32 +01:00
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
let url = Url::new(scheme.to_string(),
|
|
|
|
userinfo,
|
|
|
|
host.to_string(),
|
|
|
|
port,
|
|
|
|
path,
|
|
|
|
query,
|
|
|
|
fragment);
|
|
|
|
Ok(url)
|
2014-06-20 01:11:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[deprecated="use `Url::parse`"]
|
|
|
|
pub fn from_str(s: &str) -> Result<Url, String> {
|
|
|
|
Url::parse(s)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
impl Path {
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn new(path: String,
|
2014-02-12 21:28:58 -08:00
|
|
|
query: Query,
|
2014-05-22 16:57:53 -07:00
|
|
|
fragment: Option<String>)
|
2014-02-12 21:28:58 -08:00
|
|
|
-> Path {
|
|
|
|
Path {
|
|
|
|
path: path,
|
|
|
|
query: query,
|
|
|
|
fragment: fragment,
|
|
|
|
}
|
|
|
|
}
|
2014-06-20 01:30:12 +01:00
|
|
|
|
|
|
|
/// Parses a URL path, converting it from a string to a `Path` representation.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
/// * rawpath - a string representing the path component of a URL.
|
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// `Err(e)` if the string did not represent a valid URL path, where `e` is a
|
|
|
|
/// `String` error message. Otherwise, `Ok(p)` where `p` is a `Path` struct
|
|
|
|
/// representing the URL path.
|
2014-06-21 00:42:21 +01:00
|
|
|
pub fn parse(rawpath: &str) -> DecodeResult<Path> {
|
2014-06-20 01:30:12 +01:00
|
|
|
let (path, rest) = try!(get_path(rawpath, false));
|
|
|
|
|
|
|
|
// query and fragment
|
|
|
|
let (query, fragment) = try!(get_query_fragment(rest.as_slice()));
|
|
|
|
|
|
|
|
Ok(Path{ path: path, query: query, fragment: fragment })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[deprecated="use `Path::parse`"]
|
|
|
|
pub fn path_from_str(s: &str) -> Result<Path, String> {
|
|
|
|
Path::parse(s)
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
impl UserInfo {
|
2013-08-19 02:27:57 -07:00
|
|
|
#[inline]
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn new(user: String, pass: Option<String>) -> UserInfo {
|
2013-07-17 17:47:20 -07:00
|
|
|
UserInfo { user: user, pass: pass }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-26 16:43:32 +01:00
|
|
|
fn encode_inner<T: BytesContainer>(c: T, full_url: bool) -> String {
|
|
|
|
c.container_as_bytes().iter().fold(String::new(), |mut out, &b| {
|
2014-06-21 00:42:21 +01:00
|
|
|
match b as char {
|
|
|
|
// unreserved:
|
|
|
|
'A' .. 'Z'
|
|
|
|
| 'a' .. 'z'
|
|
|
|
| '0' .. '9'
|
|
|
|
| '-' | '.' | '_' | '~' => out.push_char(b as char),
|
|
|
|
|
|
|
|
// gen-delims:
|
|
|
|
':' | '/' | '?' | '#' | '[' | ']' | '@' |
|
|
|
|
// sub-delims:
|
|
|
|
'!' | '$' | '&' | '"' | '(' | ')' | '*' |
|
|
|
|
'+' | ',' | ';' | '='
|
|
|
|
if full_url => out.push_char(b as char),
|
|
|
|
|
|
|
|
ch => out.push_str(format!("%{:02X}", ch as uint).as_slice()),
|
2013-10-13 18:48:47 -07:00
|
|
|
};
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
out
|
|
|
|
})
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Encodes a URI by replacing reserved characters with percent-encoded
|
|
|
|
/// character sequences.
|
|
|
|
///
|
|
|
|
/// This function is compliant with RFC 3986.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2014-07-29 14:07:09 +01:00
|
|
|
/// # #![allow(deprecated)]
|
2014-06-21 00:42:21 +01:00
|
|
|
/// use url::encode;
|
|
|
|
///
|
|
|
|
/// let url = encode("https://example.com/Rust (programming language)");
|
|
|
|
/// println!("{}", url); // https://example.com/Rust%20(programming%20language)
|
|
|
|
/// ```
|
2014-06-26 16:43:32 +01:00
|
|
|
pub fn encode<T: BytesContainer>(container: T) -> String {
|
|
|
|
encode_inner(container, true)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Encodes a URI component by replacing reserved characters with percent-
|
|
|
|
/// encoded character sequences.
|
|
|
|
///
|
|
|
|
/// This function is compliant with RFC 3986.
|
2014-06-26 16:43:32 +01:00
|
|
|
pub fn encode_component<T: BytesContainer>(container: T) -> String {
|
|
|
|
encode_inner(container, false)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
pub type DecodeResult<T> = Result<T, String>;
|
2013-10-13 18:48:47 -07:00
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Decodes a percent-encoded string representing a URI.
|
|
|
|
///
|
|
|
|
/// This will only decode escape sequences generated by `encode`.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2014-07-29 14:07:09 +01:00
|
|
|
/// # #![allow(deprecated)]
|
2014-06-21 00:42:21 +01:00
|
|
|
/// use url::decode;
|
|
|
|
///
|
|
|
|
/// let url = decode("https://example.com/Rust%20(programming%20language)");
|
|
|
|
/// println!("{}", url); // https://example.com/Rust (programming language)
|
|
|
|
/// ```
|
2014-06-26 16:43:32 +01:00
|
|
|
pub fn decode<T: BytesContainer>(container: T) -> DecodeResult<String> {
|
|
|
|
decode_inner(container, true)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Decode a string encoded with percent encoding.
|
2014-06-26 16:43:32 +01:00
|
|
|
pub fn decode_component<T: BytesContainer>(container: T) -> DecodeResult<String> {
|
|
|
|
decode_inner(container, false)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-26 16:43:32 +01:00
|
|
|
fn decode_inner<T: BytesContainer>(c: T, full_url: bool) -> DecodeResult<String> {
|
2014-05-22 16:57:53 -07:00
|
|
|
let mut out = String::new();
|
2014-06-26 16:43:32 +01:00
|
|
|
let mut iter = c.container_as_bytes().iter().map(|&b| b);
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2013-10-13 18:48:47 -07:00
|
|
|
loop {
|
2014-06-21 00:42:21 +01:00
|
|
|
match iter.next() {
|
|
|
|
Some(b) => match b as char {
|
|
|
|
'%' => {
|
|
|
|
let bytes = match (iter.next(), iter.next()) {
|
|
|
|
(Some(one), Some(two)) => [one as u8, two as u8],
|
|
|
|
_ => return Err(format!("Malformed input: found '%' \
|
|
|
|
without two trailing bytes")),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Only decode some characters if full_url:
|
|
|
|
match uint::parse_bytes(bytes, 16u).unwrap() as u8 as char {
|
|
|
|
// gen-delims:
|
|
|
|
':' | '/' | '?' | '#' | '[' | ']' | '@' |
|
|
|
|
|
|
|
|
// sub-delims:
|
|
|
|
'!' | '$' | '&' | '"' | '(' | ')' | '*' |
|
|
|
|
'+' | ',' | ';' | '='
|
|
|
|
if full_url => {
|
|
|
|
out.push_char('%');
|
|
|
|
out.push_char(bytes[0u] as char);
|
|
|
|
out.push_char(bytes[1u] as char);
|
|
|
|
}
|
|
|
|
|
|
|
|
ch => out.push_char(ch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ch => out.push_char(ch)
|
|
|
|
},
|
|
|
|
None => return Ok(out),
|
2013-10-13 18:48:47 -07:00
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Encode a hashmap to the 'application/x-www-form-urlencoded' media type.
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn encode_form_urlencoded(m: &HashMap<String, Vec<String>>) -> String {
|
2014-06-21 00:42:21 +01:00
|
|
|
fn encode_plus<T: Str>(s: &T) -> String {
|
|
|
|
s.as_slice().bytes().fold(String::new(), |mut out, b| {
|
|
|
|
match b as char {
|
|
|
|
'A' .. 'Z'
|
|
|
|
| 'a' .. 'z'
|
|
|
|
| '0' .. '9'
|
|
|
|
| '_' | '.' | '-' => out.push_char(b as char),
|
|
|
|
' ' => out.push_char('+'),
|
|
|
|
ch => out.push_str(format!("%{:X}", ch as uint).as_slice())
|
|
|
|
}
|
|
|
|
|
|
|
|
out
|
|
|
|
})
|
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
let mut first = true;
|
|
|
|
m.iter().fold(String::new(), |mut out, (key, values)| {
|
|
|
|
let key = encode_plus(key);
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2013-08-03 12:45:23 -04:00
|
|
|
for value in values.iter() {
|
2013-07-17 17:47:20 -07:00
|
|
|
if first {
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
out.push_char('&');
|
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
out.push_str(key.as_slice());
|
|
|
|
out.push_char('=');
|
|
|
|
out.push_str(encode_plus(value).as_slice());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
out
|
|
|
|
})
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Decode a string encoded with the 'application/x-www-form-urlencoded' media
|
|
|
|
/// type into a hashmap.
|
|
|
|
pub fn decode_form_urlencoded(s: &[u8])
|
|
|
|
-> DecodeResult<HashMap<String, Vec<String>>> {
|
|
|
|
fn maybe_push_value(map: &mut HashMap<String, Vec<String>>,
|
|
|
|
key: String,
|
|
|
|
value: String) {
|
|
|
|
if key.len() > 0 && value.len() > 0 {
|
|
|
|
let values = map.find_or_insert_with(key, |_| vec!());
|
|
|
|
values.push(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut out = HashMap::new();
|
|
|
|
let mut iter = s.iter().map(|&x| x);
|
|
|
|
|
2014-05-22 16:57:53 -07:00
|
|
|
let mut key = String::new();
|
|
|
|
let mut value = String::new();
|
2013-10-13 18:48:47 -07:00
|
|
|
let mut parsing_key = true;
|
|
|
|
|
|
|
|
loop {
|
2014-06-21 00:42:21 +01:00
|
|
|
match iter.next() {
|
|
|
|
Some(b) => match b as char {
|
|
|
|
'&' | ';' => {
|
|
|
|
maybe_push_value(&mut out, key, value);
|
|
|
|
|
|
|
|
parsing_key = true;
|
|
|
|
key = String::new();
|
|
|
|
value = String::new();
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-21 00:42:21 +01:00
|
|
|
'=' => parsing_key = false,
|
|
|
|
ch => {
|
|
|
|
let ch = match ch {
|
|
|
|
'%' => {
|
|
|
|
let bytes = match (iter.next(), iter.next()) {
|
|
|
|
(Some(one), Some(two)) => [one as u8, two as u8],
|
|
|
|
_ => return Err(format!("Malformed input: found \
|
|
|
|
'%' without two trailing bytes"))
|
|
|
|
};
|
|
|
|
|
|
|
|
uint::parse_bytes(bytes, 16u).unwrap() as u8 as char
|
2013-10-13 18:48:47 -07:00
|
|
|
}
|
2014-06-21 00:42:21 +01:00
|
|
|
'+' => ' ',
|
|
|
|
ch => ch
|
|
|
|
};
|
2013-10-13 18:48:47 -07:00
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
if parsing_key {
|
|
|
|
key.push_char(ch)
|
|
|
|
} else {
|
|
|
|
value.push_char(ch)
|
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-21 00:42:21 +01:00
|
|
|
},
|
|
|
|
None => {
|
|
|
|
maybe_push_value(&mut out, key, value);
|
|
|
|
return Ok(out)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
2013-10-13 18:48:47 -07:00
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-07-31 07:56:39 +09:00
|
|
|
fn split_char_first(s: &str, c: char) -> (&str, &str) {
|
2014-08-07 12:56:26 -07:00
|
|
|
let mut iter = s.splitn(1, c);
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
match (iter.next(), iter.next()) {
|
|
|
|
(Some(a), Some(b)) => (a, b),
|
|
|
|
(Some(a), None) => (a, ""),
|
|
|
|
(None, _) => unreachable!(),
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-19 18:56:33 -08:00
|
|
|
impl fmt::Show for UserInfo {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self.pass {
|
2014-05-10 14:05:06 -07:00
|
|
|
Some(ref pass) => write!(f, "{}:{}@", self.user, *pass),
|
|
|
|
None => write!(f, "{}@", self.user),
|
2014-02-19 18:56:33 -08:00
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
fn query_from_str(rawquery: &str) -> DecodeResult<Query> {
|
2014-03-15 15:13:00 -07:00
|
|
|
let mut query: Query = vec!();
|
2013-07-17 17:47:20 -07:00
|
|
|
if !rawquery.is_empty() {
|
2013-11-23 11:18:51 +01:00
|
|
|
for p in rawquery.split('&') {
|
2013-07-17 17:47:20 -07:00
|
|
|
let (k, v) = split_char_first(p, '=');
|
2014-06-21 00:42:21 +01:00
|
|
|
query.push((try!(decode_component(k)),
|
|
|
|
try!(decode_component(v))));
|
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-21 00:42:21 +01:00
|
|
|
|
|
|
|
Ok(query)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-21 00:42:21 +01:00
|
|
|
/// Converts an instance of a URI `Query` type to a string.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2014-07-29 14:07:09 +01:00
|
|
|
/// # #![allow(deprecated)]
|
2014-06-25 23:51:36 +01:00
|
|
|
/// let query = vec![("title".to_string(), "The Village".to_string()),
|
2014-06-21 00:42:21 +01:00
|
|
|
/// ("north".to_string(), "52.91".to_string()),
|
2014-06-25 23:51:36 +01:00
|
|
|
/// ("west".to_string(), "4.10".to_string())];
|
2014-06-21 00:42:21 +01:00
|
|
|
/// println!("{}", url::query_to_str(&query)); // title=The%20Village&north=52.91&west=4.10
|
|
|
|
/// ```
|
2014-05-22 16:57:53 -07:00
|
|
|
pub fn query_to_str(query: &Query) -> String {
|
2014-06-21 00:42:21 +01:00
|
|
|
query.iter().enumerate().fold(String::new(), |mut out, (i, &(ref k, ref v))| {
|
|
|
|
if i != 0 {
|
|
|
|
out.push_char('&');
|
|
|
|
}
|
|
|
|
|
|
|
|
out.push_str(encode_component(k.as_slice()).as_slice());
|
|
|
|
out.push_char('=');
|
|
|
|
out.push_str(encode_component(v.as_slice()).as_slice());
|
|
|
|
out
|
|
|
|
})
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
/// 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
|
2014-07-29 14:07:09 +01:00
|
|
|
/// # #![allow(deprecated)]
|
2014-06-23 23:13:11 +01:00
|
|
|
/// use url::get_scheme;
|
|
|
|
///
|
|
|
|
/// let scheme = match get_scheme("https://example.com/") {
|
|
|
|
/// Ok((sch, _)) => sch,
|
|
|
|
/// Err(_) => "(None)",
|
|
|
|
/// };
|
|
|
|
/// println!("Scheme in use: {}.", scheme); // Scheme in use: https.
|
|
|
|
/// ```
|
2014-07-31 07:56:39 +09:00
|
|
|
pub fn get_scheme(rawurl: &str) -> DecodeResult<(&str, &str)> {
|
2013-11-23 11:18:51 +01:00
|
|
|
for (i,c) in rawurl.chars().enumerate() {
|
2014-06-23 23:13:11 +01:00
|
|
|
let result = match c {
|
|
|
|
'A' .. 'Z'
|
|
|
|
| 'a' .. 'z' => continue,
|
|
|
|
'0' .. '9' | '+' | '-' | '.' => {
|
|
|
|
if i != 0 { continue }
|
|
|
|
|
|
|
|
Err("url: Scheme must begin with a letter.".to_string())
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
':' => {
|
|
|
|
if i == 0 {
|
|
|
|
Err("url: Scheme cannot be empty.".to_string())
|
|
|
|
} else {
|
|
|
|
Ok((rawurl.slice(0,i), rawurl.slice(i+1,rawurl.len())))
|
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
_ => Err("url: Invalid character in scheme.".to_string()),
|
|
|
|
};
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Err("url: Scheme must be terminated with a colon.".to_string())
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// returns userinfo, host, port, and unparsed part, or an error
|
2014-07-31 07:56:39 +09:00
|
|
|
fn get_authority(rawurl: &str) ->
|
|
|
|
DecodeResult<(Option<UserInfo>, &str, Option<u16>, &str)> {
|
2013-07-17 17:47:20 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
#[deriving(Clone, PartialEq)]
|
|
|
|
enum Input {
|
|
|
|
Digit, // all digits
|
|
|
|
Hex, // digits and letters a-f
|
|
|
|
Unreserved // all other legal characters
|
|
|
|
}
|
|
|
|
|
|
|
|
if !rawurl.starts_with("//") {
|
|
|
|
// there is no authority.
|
|
|
|
return Ok((None, "", None, rawurl));
|
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
let len = rawurl.len();
|
|
|
|
let mut st = Start;
|
2013-07-31 17:59:59 -04:00
|
|
|
let mut input = Digit; // most restricted, start here.
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let mut userinfo = None;
|
2014-06-23 23:13:11 +01:00
|
|
|
let mut host = "";
|
2013-07-17 17:47:20 -07:00
|
|
|
let mut port = None;
|
|
|
|
|
2014-06-27 12:30:25 -07:00
|
|
|
let mut colon_count = 0u;
|
2013-07-17 17:47:20 -07:00
|
|
|
let mut pos = 0;
|
|
|
|
let mut begin = 2;
|
|
|
|
let mut end = len;
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
for (i,c) in rawurl.chars().enumerate()
|
|
|
|
// ignore the leading '//' handled by early return
|
|
|
|
.skip(2) {
|
2013-07-17 17:47:20 -07:00
|
|
|
// deal with input class first
|
|
|
|
match c {
|
2014-06-23 23:13:11 +01:00
|
|
|
'0' .. '9' => (),
|
|
|
|
'A' .. 'F'
|
|
|
|
| 'a' .. 'f' => {
|
|
|
|
if input == Digit {
|
|
|
|
input = Hex;
|
|
|
|
}
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
'G' .. 'Z'
|
|
|
|
| 'g' .. 'z'
|
|
|
|
| '-' | '.' | '_' | '~' | '%'
|
|
|
|
| '&' |'\'' | '(' | ')' | '+'
|
|
|
|
| '!' | '*' | ',' | ';' | '=' => input = Unreserved,
|
|
|
|
':' | '@' | '?' | '#' | '/' => {
|
|
|
|
// separators, don't change anything
|
|
|
|
}
|
|
|
|
_ => return Err("Illegal character in authority".to_string()),
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// now process states
|
|
|
|
match c {
|
|
|
|
':' => {
|
|
|
|
colon_count += 1;
|
|
|
|
match st {
|
|
|
|
Start => {
|
|
|
|
pos = i;
|
|
|
|
st = PassHostPort;
|
|
|
|
}
|
|
|
|
PassHostPort => {
|
|
|
|
// multiple colons means ipv6 address.
|
2013-07-31 17:59:59 -04:00
|
|
|
if input == Unreserved {
|
2013-07-17 17:47:20 -07:00
|
|
|
return Err(
|
2014-05-25 03:17:19 -07:00
|
|
|
"Illegal characters in IPv6 address.".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
st = Ip6Host;
|
|
|
|
}
|
|
|
|
InHost => {
|
|
|
|
pos = i;
|
2013-07-31 17:59:59 -04:00
|
|
|
if input == Unreserved {
|
2013-08-19 02:27:57 -07:00
|
|
|
// must be port
|
2014-06-23 23:13:11 +01:00
|
|
|
host = rawurl.slice(begin, i);
|
2013-08-19 02:27:57 -07:00
|
|
|
st = InPort;
|
|
|
|
} else {
|
|
|
|
// can't be sure whether this is an ipv6 address or a port
|
|
|
|
st = Ip6Port;
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ip6Port => {
|
2013-07-31 17:59:59 -04:00
|
|
|
if input == Unreserved {
|
2014-06-23 23:13:11 +01:00
|
|
|
return Err("Illegal characters in authority.".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
st = Ip6Host;
|
|
|
|
}
|
|
|
|
Ip6Host => {
|
|
|
|
if colon_count > 7 {
|
2014-06-23 23:13:11 +01:00
|
|
|
host = rawurl.slice(begin, i);
|
2013-07-17 17:47:20 -07:00
|
|
|
pos = i;
|
|
|
|
st = InPort;
|
|
|
|
}
|
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
_ => return Err("Invalid ':' in authority.".to_string()),
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2013-07-31 17:59:59 -04:00
|
|
|
input = Digit; // reset input class
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
'@' => {
|
2013-07-31 17:59:59 -04:00
|
|
|
input = Digit; // reset input class
|
2013-07-17 17:47:20 -07:00
|
|
|
colon_count = 0; // reset count
|
|
|
|
match st {
|
|
|
|
Start => {
|
2014-05-25 03:17:19 -07:00
|
|
|
let user = rawurl.slice(begin, i).to_string();
|
2013-07-17 17:47:20 -07:00
|
|
|
userinfo = Some(UserInfo::new(user, None));
|
|
|
|
st = InHost;
|
|
|
|
}
|
|
|
|
PassHostPort => {
|
2014-05-25 03:17:19 -07:00
|
|
|
let user = rawurl.slice(begin, pos).to_string();
|
|
|
|
let pass = rawurl.slice(pos+1, i).to_string();
|
2013-07-17 17:47:20 -07:00
|
|
|
userinfo = Some(UserInfo::new(user, Some(pass)));
|
|
|
|
st = InHost;
|
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
_ => return Err("Invalid '@' in authority.".to_string()),
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
begin = i+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
'?' | '#' | '/' => {
|
|
|
|
end = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
_ => ()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// finish up
|
|
|
|
match st {
|
2014-06-23 23:13:11 +01:00
|
|
|
Start => host = rawurl.slice(begin, end),
|
|
|
|
PassHostPort
|
|
|
|
| Ip6Port => {
|
2013-07-31 17:59:59 -04:00
|
|
|
if input != Digit {
|
2014-05-25 03:17:19 -07:00
|
|
|
return Err("Non-digit characters in port.".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
host = rawurl.slice(begin, pos);
|
|
|
|
port = Some(rawurl.slice(pos+1, end));
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
Ip6Host
|
|
|
|
| InHost => host = rawurl.slice(begin, end),
|
2013-07-17 17:47:20 -07:00
|
|
|
InPort => {
|
2013-07-31 17:59:59 -04:00
|
|
|
if input != Digit {
|
2014-05-25 03:17:19 -07:00
|
|
|
return Err("Non-digit characters in port.".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
2014-06-23 23:13:11 +01:00
|
|
|
port = Some(rawurl.slice(pos+1, end));
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
let rest = rawurl.slice(end, len);
|
|
|
|
// If we have a port string, ensure it parses to u16.
|
|
|
|
let port = match port {
|
|
|
|
None => None,
|
|
|
|
opt => match opt.and_then(|p| FromStr::from_str(p)) {
|
|
|
|
None => return Err(format!("Failed to parse port: {}", port)),
|
|
|
|
opt => opt
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((userinfo, host, port, rest))
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// returns the path and unparsed part of url, or an error
|
2014-07-31 07:56:39 +09:00
|
|
|
fn get_path(rawurl: &str, is_authority: bool) -> DecodeResult<(String, &str)> {
|
2013-07-17 17:47:20 -07:00
|
|
|
let len = rawurl.len();
|
|
|
|
let mut end = len;
|
2013-11-23 11:18:51 +01:00
|
|
|
for (i,c) in rawurl.chars().enumerate() {
|
2013-07-17 17:47:20 -07:00
|
|
|
match c {
|
2014-06-23 23:13:11 +01:00
|
|
|
'A' .. 'Z'
|
|
|
|
| 'a' .. 'z'
|
|
|
|
| '0' .. '9'
|
|
|
|
| '&' |'\'' | '(' | ')' | '.'
|
|
|
|
| '@' | ':' | '%' | '/' | '+'
|
|
|
|
| '!' | '*' | ',' | ';' | '='
|
|
|
|
| '_' | '-' | '~' => continue,
|
2013-07-17 17:47:20 -07:00
|
|
|
'?' | '#' => {
|
|
|
|
end = i;
|
|
|
|
break;
|
|
|
|
}
|
2014-05-25 03:17:19 -07:00
|
|
|
_ => return Err("Invalid character in path.".to_string())
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
if is_authority && end != 0 && !rawurl.starts_with("/") {
|
2014-06-21 00:42:21 +01:00
|
|
|
Err("Non-empty path must begin with \
|
|
|
|
'/' in presence of authority.".to_string())
|
|
|
|
} else {
|
|
|
|
Ok((try!(decode_component(rawurl.slice(0, end))),
|
2014-06-23 23:13:11 +01:00
|
|
|
rawurl.slice(end, len)))
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the parsed query and the fragment, if present
|
2014-06-21 00:42:21 +01:00
|
|
|
fn get_query_fragment(rawurl: &str) -> DecodeResult<(Query, Option<String>)> {
|
2014-06-23 23:13:11 +01:00
|
|
|
let (before_fragment, raw_fragment) = split_char_first(rawurl, '#');
|
2014-02-12 21:28:58 -08:00
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
// Parse the fragment if available
|
|
|
|
let fragment = match raw_fragment {
|
|
|
|
"" => None,
|
|
|
|
raw => Some(try!(decode_component(raw)))
|
2014-02-12 21:28:58 -08:00
|
|
|
};
|
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
match before_fragment.slice_shift_char() {
|
|
|
|
(Some('?'), rest) => Ok((try!(query_from_str(rest)), fragment)),
|
|
|
|
(None, "") => Ok((vec!(), fragment)),
|
|
|
|
_ => Err(format!("Query didn't start with '?': '{}..'", before_fragment)),
|
|
|
|
}
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
impl FromStr for Url {
|
|
|
|
fn from_str(s: &str) -> Option<Url> {
|
2014-06-23 23:13:11 +01:00
|
|
|
Url::parse(s).ok()
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
impl FromStr for Path {
|
|
|
|
fn from_str(s: &str) -> Option<Path> {
|
2014-06-23 23:13:11 +01:00
|
|
|
Path::parse(s).ok()
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-19 18:56:33 -08:00
|
|
|
impl fmt::Show for Url {
|
2014-06-23 23:13:11 +01:00
|
|
|
/// Converts a URL from `Url` to string representation.
|
|
|
|
///
|
|
|
|
/// # 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".
|
2014-02-19 18:56:33 -08:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2014-05-10 14:05:06 -07:00
|
|
|
try!(write!(f, "{}:", self.scheme));
|
2014-02-19 18:56:33 -08:00
|
|
|
|
|
|
|
if !self.host.is_empty() {
|
2014-05-10 14:05:06 -07:00
|
|
|
try!(write!(f, "//"));
|
2014-02-19 18:56:33 -08:00
|
|
|
match self.user {
|
2014-05-10 14:05:06 -07:00
|
|
|
Some(ref user) => try!(write!(f, "{}", *user)),
|
2014-02-19 18:56:33 -08:00
|
|
|
None => {}
|
|
|
|
}
|
|
|
|
match self.port {
|
2014-05-10 14:05:06 -07:00
|
|
|
Some(ref port) => try!(write!(f, "{}:{}", self.host,
|
2014-02-19 18:56:33 -08:00
|
|
|
*port)),
|
2014-05-10 14:05:06 -07:00
|
|
|
None => try!(write!(f, "{}", self.host)),
|
2014-02-19 18:56:33 -08:00
|
|
|
}
|
2013-10-19 00:25:03 +11:00
|
|
|
}
|
2014-02-12 21:28:58 -08:00
|
|
|
|
2014-06-21 00:05:06 +01:00
|
|
|
write!(f, "{}", self.path)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-19 18:56:33 -08:00
|
|
|
impl fmt::Show for Path {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2014-05-10 14:05:06 -07:00
|
|
|
try!(write!(f, "{}", self.path));
|
2014-02-19 18:56:33 -08:00
|
|
|
if !self.query.is_empty() {
|
2014-06-21 00:05:06 +01:00
|
|
|
try!(write!(f, "?{}", query_to_str(&self.query)))
|
2014-02-19 18:56:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.fragment {
|
|
|
|
Some(ref fragment) => {
|
2014-05-28 09:24:28 -07:00
|
|
|
write!(f, "#{}", encode_component(fragment.as_slice()))
|
2014-02-19 18:56:33 -08:00
|
|
|
}
|
|
|
|
None => Ok(())
|
|
|
|
}
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-29 19:03:06 -07:00
|
|
|
impl<S: hash::Writer> hash::Hash<S> for Url {
|
2014-02-25 08:03:41 -08:00
|
|
|
fn hash(&self, state: &mut S) {
|
2014-06-21 03:39:03 -07:00
|
|
|
self.to_string().hash(state)
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-29 19:03:06 -07:00
|
|
|
impl<S: hash::Writer> hash::Hash<S> for Path {
|
2014-02-25 08:03:41 -08:00
|
|
|
fn hash(&self, state: &mut S) {
|
2014-06-21 03:39:03 -07:00
|
|
|
self.to_string().hash(state)
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
// 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", ',');
|
2014-06-21 00:42:21 +01:00
|
|
|
assert_eq!(u, "hello");
|
|
|
|
assert_eq!(v, " sweet world");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let (u,v) = split_char_first("hello sweet world", ',');
|
2014-06-21 00:42:21 +01:00
|
|
|
assert_eq!(u, "hello sweet world");
|
|
|
|
assert_eq!(v, "");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_authority() {
|
|
|
|
let (u, h, p, r) = get_authority(
|
|
|
|
"//user:pass@rust-lang.org/something").unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(u, Some(UserInfo::new("user".to_string(), Some("pass".to_string()))));
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "rust-lang.org");
|
2013-07-17 17:47:20 -07:00
|
|
|
assert!(p.is_none());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(r, "/something");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let (u, h, p, r) = get_authority(
|
|
|
|
"//rust-lang.org:8000?something").unwrap();
|
|
|
|
assert!(u.is_none());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "rust-lang.org");
|
|
|
|
assert_eq!(p, Some(8000));
|
|
|
|
assert_eq!(r, "?something");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-06-23 23:13:11 +01:00
|
|
|
let (u, h, p, r) = get_authority("//rust-lang.org#blah").unwrap();
|
2013-07-17 17:47:20 -07:00
|
|
|
assert!(u.is_none());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "rust-lang.org");
|
2013-07-17 17:47:20 -07:00
|
|
|
assert!(p.is_none());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(r, "#blah");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
// ipv6 tests
|
|
|
|
let (_, h, _, _) = get_authority(
|
|
|
|
"//2001:0db8:85a3:0042:0000:8a2e:0370:7334#blah").unwrap();
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let (_, h, p, _) = get_authority(
|
|
|
|
"//2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah").unwrap();
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334");
|
|
|
|
assert_eq!(p, Some(8000));
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let (u, h, p, _) = get_authority(
|
|
|
|
"//us:p@2001:0db8:85a3:0042:0000:8a2e:0370:7334:8000#blah"
|
|
|
|
).unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(u, Some(UserInfo::new("us".to_string(), Some("p".to_string()))));
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "2001:0db8:85a3:0042:0000:8a2e:0370:7334");
|
|
|
|
assert_eq!(p, Some(8000));
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
// 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());
|
2014-06-23 23:13:11 +01:00
|
|
|
// outside u16 range
|
|
|
|
assert!(get_authority("//user:pass@rust-lang:65536").is_err());
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
// these parse as empty, because they don't start with '//'
|
|
|
|
let (_, h, _, _) = get_authority("user:pass@rust-lang").unwrap();
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "");
|
2013-07-17 17:47:20 -07:00
|
|
|
let (_, h, _, _) = get_authority("rust-lang.org").unwrap();
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(h, "");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_path() {
|
|
|
|
let (p, r) = get_path("/something+%20orother", true).unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(p, "/something+ orother".to_string());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(r, "");
|
2013-07-17 17:47:20 -07:00
|
|
|
let (p, r) = get_path("test@email.com#fragment", false).unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(p, "test@email.com".to_string());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(r, "#fragment");
|
2013-07-17 17:47:20 -07:00
|
|
|
let (p, r) = get_path("/gen/:addr=?q=v", false).unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(p, "/gen/:addr=".to_string());
|
2014-06-23 23:13:11 +01:00
|
|
|
assert_eq!(r, "?q=v");
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
//failure cases
|
|
|
|
assert!(get_path("something?q", true).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2014-06-20 01:30:12 +01:00
|
|
|
use {encode_form_urlencoded, decode_form_urlencoded, decode, encode,
|
|
|
|
encode_component, decode_component, UserInfo, get_scheme, Url, Path};
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-05-29 19:03:06 -07:00
|
|
|
use std::collections::HashMap;
|
2014-06-26 16:43:32 +01:00
|
|
|
use std::path::BytesContainer;
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_url_parse() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org:8080/doc/~u?s=v#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-06-25 23:51:36 +01:00
|
|
|
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));
|
|
|
|
assert_eq!(u.path.path, "/doc/~u".to_string());
|
|
|
|
assert_eq!(u.path.query, vec!(("s".to_string(), "v".to_string())));
|
|
|
|
assert_eq!(u.path.fragment, Some("something".to_string()));
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
#[test]
|
|
|
|
fn test_path_parse() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let path = "/doc/~u?s=v#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Path>(path).unwrap();
|
2014-02-12 21:28:58 -08:00
|
|
|
|
2014-06-25 23:51:36 +01:00
|
|
|
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()));
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_url_parse_host_slash() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let urlstr = "http://0.42.42.42/";
|
2014-06-20 01:11:32 +01:00
|
|
|
let url = from_str::<Url>(urlstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.host, "0.42.42.42".to_string());
|
|
|
|
assert_eq!(url.path.path, "/".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
#[test]
|
|
|
|
fn test_path_parse_host_slash() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let pathstr = "/";
|
2014-06-20 01:30:12 +01:00
|
|
|
let path = from_str::<Path>(pathstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(path.path, "/".to_string());
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-08-19 02:27:57 -07:00
|
|
|
#[test]
|
|
|
|
fn test_url_host_with_port() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let urlstr = "scheme://host:1234";
|
2014-06-20 01:11:32 +01:00
|
|
|
let url = from_str::<Url>(urlstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.scheme, "scheme".to_string());
|
|
|
|
assert_eq!(url.host, "host".to_string());
|
|
|
|
assert_eq!(url.port, Some(1234));
|
2014-05-13 17:51:05 -07:00
|
|
|
// is empty path really correct? Other tests think so
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.path.path, "".to_string());
|
|
|
|
|
2014-05-13 17:51:05 -07:00
|
|
|
let urlstr = "scheme://host:1234/";
|
2014-06-20 01:11:32 +01:00
|
|
|
let url = from_str::<Url>(urlstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.scheme, "scheme".to_string());
|
|
|
|
assert_eq!(url.host, "host".to_string());
|
|
|
|
assert_eq!(url.port, Some(1234));
|
|
|
|
assert_eq!(url.path.path, "/".to_string());
|
2013-08-19 02:27:57 -07:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_url_with_underscores() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let urlstr = "http://dotcom.com/file_name.html";
|
2014-06-20 01:11:32 +01:00
|
|
|
let url = from_str::<Url>(urlstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.path.path, "/file_name.html".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
#[test]
|
|
|
|
fn test_path_with_underscores() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let pathstr = "/file_name.html";
|
2014-06-20 01:30:12 +01:00
|
|
|
let path = from_str::<Path>(pathstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(path.path, "/file_name.html".to_string());
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_url_with_dashes() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let urlstr = "http://dotcom.com/file-name.html";
|
2014-06-20 01:11:32 +01:00
|
|
|
let url = from_str::<Url>(urlstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(url.path.path, "/file-name.html".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
#[test]
|
|
|
|
fn test_path_with_dashes() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let pathstr = "/file-name.html";
|
2014-06-20 01:30:12 +01:00
|
|
|
let path = from_str::<Path>(pathstr).unwrap();
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(path.path, "/file-name.html".to_string());
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_no_scheme() {
|
|
|
|
assert!(get_scheme("noschemehere.html").is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid_scheme_errors() {
|
2014-06-20 01:11:32 +01:00
|
|
|
assert!(Url::parse("99://something").is_err());
|
|
|
|
assert!(Url::parse("://something").is_err());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_full_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org/doc?s=v#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_userless_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org/doc?s=v#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_queryless_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org/doc#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_empty_query_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org/doc?#something";
|
|
|
|
let should_be = "http://user:pass@rust-lang.org/doc#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), should_be);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_fragmentless_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org/doc?q=v";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_minimal_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org/doc";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2013-10-19 00:25:03 +11:00
|
|
|
#[test]
|
|
|
|
fn test_url_with_port_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org:80/doc";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-10-19 00:25:03 +11:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_scheme_host_only_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pathless_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://user:pass@rust-lang.org?q=v#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scheme_host_fragment_only_url_parse_and_format() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org#something";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_url_component_encoding() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "http://rust-lang.org/doc%20uments?ba%25d%20=%23%26%2B";
|
2014-06-20 01:11:32 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
2014-06-21 00:05:06 +01:00
|
|
|
assert!(u.path.path == "/doc uments".to_string());
|
|
|
|
assert!(u.path.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
2014-02-12 21:28:58 -08:00
|
|
|
#[test]
|
|
|
|
fn test_path_component_encoding() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let path = "/doc%20uments?ba%25d%20=%23%26%2B";
|
2014-06-20 01:30:12 +01:00
|
|
|
let p = from_str::<Path>(path).unwrap();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert!(p.path == "/doc uments".to_string());
|
|
|
|
assert!(p.query == vec!(("ba%d ".to_string(), "#&+".to_string())));
|
2014-02-12 21:28:58 -08:00
|
|
|
}
|
|
|
|
|
2013-07-17 17:47:20 -07:00
|
|
|
#[test]
|
|
|
|
fn test_url_without_authority() {
|
2014-05-13 17:51:05 -07:00
|
|
|
let url = "mailto:test@email.com";
|
2014-06-25 23:51:36 +01:00
|
|
|
let u = from_str::<Url>(url).unwrap();
|
|
|
|
assert_eq!(format!("{}", u).as_slice(), url);
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_encode() {
|
2014-06-26 16:43:32 +01:00
|
|
|
fn t<T: BytesContainer>(input: T, expected: &str) {
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(encode(input), expected.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
t("", "");
|
|
|
|
t("http://example.com", "http://example.com");
|
|
|
|
t("foo bar% baz", "foo%20bar%25%20baz");
|
|
|
|
t(" ", "%20");
|
|
|
|
t("!", "!");
|
|
|
|
t("\"", "\"");
|
|
|
|
t("#", "#");
|
|
|
|
t("$", "$");
|
|
|
|
t("%", "%25");
|
|
|
|
t("&", "&");
|
|
|
|
t("'", "%27");
|
|
|
|
t("(", "(");
|
|
|
|
t(")", ")");
|
|
|
|
t("*", "*");
|
|
|
|
t("+", "+");
|
|
|
|
t(",", ",");
|
|
|
|
t("/", "/");
|
|
|
|
t(":", ":");
|
|
|
|
t(";", ";");
|
|
|
|
t("=", "=");
|
|
|
|
t("?", "?");
|
|
|
|
t("@", "@");
|
|
|
|
t("[", "[");
|
|
|
|
t("]", "]");
|
|
|
|
t("\0", "%00");
|
|
|
|
t("\n", "%0A");
|
2014-06-26 16:43:32 +01:00
|
|
|
|
|
|
|
t(&[0u8, 10, 37], "%00%0A%25");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_encode_component() {
|
2014-06-26 16:43:32 +01:00
|
|
|
fn t<T: BytesContainer>(input: T, expected: &str) {
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(encode_component(input), expected.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
t("", "");
|
|
|
|
t("http://example.com", "http%3A%2F%2Fexample.com");
|
|
|
|
t("foo bar% baz", "foo%20bar%25%20baz");
|
|
|
|
t(" ", "%20");
|
|
|
|
t("!", "%21");
|
|
|
|
t("#", "%23");
|
|
|
|
t("$", "%24");
|
|
|
|
t("%", "%25");
|
|
|
|
t("&", "%26");
|
|
|
|
t("'", "%27");
|
|
|
|
t("(", "%28");
|
|
|
|
t(")", "%29");
|
|
|
|
t("*", "%2A");
|
|
|
|
t("+", "%2B");
|
|
|
|
t(",", "%2C");
|
|
|
|
t("/", "%2F");
|
|
|
|
t(":", "%3A");
|
|
|
|
t(";", "%3B");
|
|
|
|
t("=", "%3D");
|
|
|
|
t("?", "%3F");
|
|
|
|
t("@", "%40");
|
|
|
|
t("[", "%5B");
|
|
|
|
t("]", "%5D");
|
|
|
|
t("\0", "%00");
|
|
|
|
t("\n", "%0A");
|
2014-06-26 16:43:32 +01:00
|
|
|
|
|
|
|
t(&[0u8, 10, 37], "%00%0A%25");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_decode() {
|
2014-06-26 16:43:32 +01:00
|
|
|
fn t<T: BytesContainer>(input: T, expected: &str) {
|
2014-06-21 00:42:21 +01:00
|
|
|
assert_eq!(decode(input), Ok(expected.to_string()))
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(decode("sadsadsda%").is_err());
|
|
|
|
assert!(decode("waeasd%4").is_err());
|
|
|
|
t("", "");
|
|
|
|
t("abc/def 123", "abc/def 123");
|
|
|
|
t("abc%2Fdef%20123", "abc%2Fdef 123");
|
|
|
|
t("%20", " ");
|
|
|
|
t("%21", "%21");
|
|
|
|
t("%22", "%22");
|
|
|
|
t("%23", "%23");
|
|
|
|
t("%24", "%24");
|
|
|
|
t("%25", "%");
|
|
|
|
t("%26", "%26");
|
|
|
|
t("%27", "'");
|
|
|
|
t("%28", "%28");
|
|
|
|
t("%29", "%29");
|
|
|
|
t("%2A", "%2A");
|
|
|
|
t("%2B", "%2B");
|
|
|
|
t("%2C", "%2C");
|
|
|
|
t("%2F", "%2F");
|
|
|
|
t("%3A", "%3A");
|
|
|
|
t("%3B", "%3B");
|
|
|
|
t("%3D", "%3D");
|
|
|
|
t("%3F", "%3F");
|
|
|
|
t("%40", "%40");
|
|
|
|
t("%5B", "%5B");
|
|
|
|
t("%5D", "%5D");
|
2014-06-26 16:43:32 +01:00
|
|
|
|
|
|
|
t("%00%0A%25".as_bytes(), "\0\n%");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_decode_component() {
|
2014-06-26 16:43:32 +01:00
|
|
|
fn t<T: BytesContainer>(input: T, expected: &str) {
|
2014-06-21 00:42:21 +01:00
|
|
|
assert_eq!(decode_component(input), Ok(expected.to_string()))
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(decode_component("asacsa%").is_err());
|
|
|
|
assert!(decode_component("acsas%4").is_err());
|
|
|
|
t("", "");
|
|
|
|
t("abc/def 123", "abc/def 123");
|
|
|
|
t("abc%2Fdef%20123", "abc/def 123");
|
|
|
|
t("%20", " ");
|
|
|
|
t("%21", "!");
|
|
|
|
t("%22", "\"");
|
|
|
|
t("%23", "#");
|
|
|
|
t("%24", "$");
|
|
|
|
t("%25", "%");
|
|
|
|
t("%26", "&");
|
|
|
|
t("%27", "'");
|
|
|
|
t("%28", "(");
|
|
|
|
t("%29", ")");
|
|
|
|
t("%2A", "*");
|
|
|
|
t("%2B", "+");
|
|
|
|
t("%2C", ",");
|
|
|
|
t("%2F", "/");
|
|
|
|
t("%3A", ":");
|
|
|
|
t("%3B", ";");
|
|
|
|
t("%3D", "=");
|
|
|
|
t("%3F", "?");
|
|
|
|
t("%40", "@");
|
|
|
|
t("%5B", "[");
|
|
|
|
t("%5D", "]");
|
2014-06-26 16:43:32 +01:00
|
|
|
|
|
|
|
t("%00%0A%25".as_bytes(), "\0\n%");
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_encode_form_urlencoded() {
|
|
|
|
let mut m = HashMap::new();
|
2014-05-25 03:17:19 -07:00
|
|
|
assert_eq!(encode_form_urlencoded(&m), "".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
|
2014-05-25 03:17:19 -07:00
|
|
|
m.insert("".to_string(), vec!());
|
|
|
|
m.insert("foo".to_string(), vec!());
|
|
|
|
assert_eq!(encode_form_urlencoded(&m), "".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let mut m = HashMap::new();
|
2014-05-25 03:17:19 -07:00
|
|
|
m.insert("foo".to_string(), vec!("bar".to_string(), "123".to_string()));
|
|
|
|
assert_eq!(encode_form_urlencoded(&m), "foo=bar&foo=123".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let mut m = HashMap::new();
|
2014-05-25 03:17:19 -07:00
|
|
|
m.insert("foo bar".to_string(), vec!("abc".to_string(), "12 = 34".to_string()));
|
2014-06-25 23:51:36 +01:00
|
|
|
assert_eq!(encode_form_urlencoded(&m),
|
|
|
|
"foo+bar=abc&foo+bar=12+%3D+34".to_string());
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_decode_form_urlencoded() {
|
2014-06-21 00:42:21 +01:00
|
|
|
assert_eq!(decode_form_urlencoded([]).unwrap().len(), 0);
|
2013-07-17 17:47:20 -07:00
|
|
|
|
|
|
|
let s = "a=1&foo+bar=abc&foo+bar=12+%3D+34".as_bytes();
|
2014-06-21 00:42:21 +01:00
|
|
|
let form = decode_form_urlencoded(s).unwrap();
|
2013-07-17 17:47:20 -07:00
|
|
|
assert_eq!(form.len(), 2);
|
2014-05-25 03:17:19 -07:00
|
|
|
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()));
|
2013-07-17 17:47:20 -07:00
|
|
|
}
|
|
|
|
}
|