Files
hyper/src/uri.rs
Florent Monjalet 594ea65420 fix(uri): fix panic when uri contain default port
Parsing "http://host:80" caused panic (index out of bound) because the
authority end index was computed with the original uri but the uri
stored for later used was sanitized by `Url::parse()` to "http://host"

The fix computes the autority end index with the actual uri used (the
one from `Url::parse()`). Two tests have been added.
2017-01-20 17:17:33 +01:00

347 lines
9.2 KiB
Rust

use std::borrow::Cow;
use std::fmt::{Display, self};
use std::str::FromStr;
use url::{self, Url};
use url::ParseError as UrlError;
use Error;
/// The Request-URI of a Request's StartLine.
///
/// From Section 5.3, Request Target:
/// > Once an inbound connection is obtained, the client sends an HTTP
/// > request message (Section 3) with a request-target derived from the
/// > target URI. There are four distinct formats for the request-target,
/// > depending on both the method being requested and whether the request
/// > is to a proxy.
/// >
/// > ```notrust
/// > request-target = origin-form
/// > / absolute-form
/// > / authority-form
/// > / asterisk-form
/// > ```
///
/// # Uri explanations
///
/// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
/// |-| |-------------------------------||--------| |-------------------| |-----|
/// | | | | |
/// scheme authority path query fragment
#[derive(Clone)]
pub struct Uri {
source: Cow<'static, str>,
scheme_end: Option<usize>,
authority_end: Option<usize>,
query: Option<usize>,
fragment: Option<usize>,
}
impl Uri {
/// Parse a string into a `Uri`.
pub fn new(s: &str) -> Result<Uri, Error> {
let bytes = s.as_bytes();
if bytes.len() == 0 {
Err(Error::Uri(UrlError::RelativeUrlWithoutBase))
} else if bytes == b"*" {
Ok(Uri {
source: "*".into(),
scheme_end: None,
authority_end: None,
query: None,
fragment: None,
})
} else if bytes == b"/" {
Ok(Uri::default())
} else if bytes.starts_with(b"/") {
let mut temp = "http://example.com".to_owned();
temp.push_str(s);
let url = try!(Url::parse(&temp));
let query_len = url.query().unwrap_or("").len();
let fragment_len = url.fragment().unwrap_or("").len();
Ok(Uri {
source: s.to_owned().into(),
scheme_end: None,
authority_end: None,
query: if query_len > 0 { Some(query_len) } else { None },
fragment: if fragment_len > 0 { Some(fragment_len) } else { None },
})
} else if s.contains("://") {
let url = try!(Url::parse(s));
let query_len = url.query().unwrap_or("").len();
let new_s = url.to_string();
let authority_end = {
let v: Vec<&str> = new_s.split("://").collect();
v.last().unwrap()
.split(url.path())
.next()
.unwrap_or(&new_s)
.len() + if v.len() == 2 { v[0].len() + 3 } else { 0 }
};
let fragment_len = url.fragment().unwrap_or("").len();
match url.origin() {
url::Origin::Opaque(_) => Err(Error::Method),
url::Origin::Tuple(scheme, _, _) => {
Ok(Uri {
source: new_s.into(),
scheme_end: Some(scheme.len()),
authority_end: if authority_end > 0 { Some(authority_end) } else { None },
query: if query_len > 0 { Some(query_len) } else { None },
fragment: if fragment_len > 0 { Some(fragment_len) } else { None },
})
}
}
} else {
Ok(Uri {
source: s.to_owned().into(),
scheme_end: None,
authority_end: Some(s.len()),
query: None,
fragment: None,
})
}
}
/// Get the path of this `Uri`.
pub fn path(&self) -> &str {
let index = self.authority_end.unwrap_or(self.scheme_end.unwrap_or(0));
let query_len = self.query.unwrap_or(0);
let fragment_len = self.fragment.unwrap_or(0);
let end = self.source.len() - if query_len > 0 { query_len + 1 } else { 0 } -
if fragment_len > 0 { fragment_len + 1 } else { 0 };
if index >= end {
""
} else {
&self.source[index..end]
}
}
/// Get the scheme of this `Uri`.
pub fn scheme(&self) -> Option<&str> {
if let Some(end) = self.scheme_end {
Some(&self.source[..end])
} else {
None
}
}
/// Get the authority of this `Uri`.
pub fn authority(&self) -> Option<&str> {
if let Some(end) = self.authority_end {
let index = self.scheme_end.map(|i| i + 3).unwrap_or(0);
Some(&self.source[index..end])
} else {
None
}
}
/// Get the host of this `Uri`.
pub fn host(&self) -> Option<&str> {
if let Some(auth) = self.authority() {
auth.split(":").next()
} else {
None
}
}
/// Get the port of this `Uri.
pub fn port(&self) -> Option<u16> {
if let Some(auth) = self.authority() {
let v: Vec<&str> = auth.split(":").collect();
if v.len() == 2 {
u16::from_str(v[1]).ok()
} else {
None
}
} else {
None
}
}
/// Get the query string of this `Uri`, starting after the `?`.
pub fn query(&self) -> Option<&str> {
let fragment_len = self.fragment.unwrap_or(0);
let fragment_len = if fragment_len > 0 { fragment_len + 1 } else { 0 };
if let Some(len) = self.query {
Some(&self.source[self.source.len() - len - fragment_len..self.source.len() - fragment_len])
} else {
None
}
}
#[cfg(test)]
fn fragment(&self) -> Option<&str> {
if let Some(len) = self.fragment {
Some(&self.source[self.source.len() - len..])
} else {
None
}
}
}
impl FromStr for Uri {
type Err = Error;
fn from_str(s: &str) -> Result<Uri, Error> {
Uri::new(s)
}
}
impl From<Url> for Uri {
fn from(url: Url) -> Uri {
Uri::new(url.as_str()).expect("Uri::From<Url> failed")
}
}
impl PartialEq for Uri {
fn eq(&self, other: &Uri) -> bool {
self.source == other.source
}
}
impl AsRef<str> for Uri {
fn as_ref(&self) -> &str {
&self.source
}
}
impl Default for Uri {
fn default() -> Uri {
Uri {
source: "/".into(),
scheme_end: None,
authority_end: None,
query: None,
fragment: None,
}
}
}
impl fmt::Debug for Uri {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.source.as_ref(), f)
}
}
impl Display for Uri {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.source)
}
}
macro_rules! test_parse {
(
$test_name:ident,
$str:expr,
$($method:ident = $value:expr,)*
) => (
#[test]
fn $test_name() {
let uri = Uri::new($str).unwrap();
$(
assert_eq!(uri.$method(), $value);
)+
}
);
}
test_parse! {
test_uri_parse_origin_form,
"/some/path/here?and=then&hello#and-bye",
scheme = None,
authority = None,
path = "/some/path/here",
query = Some("and=then&hello"),
fragment = Some("and-bye"),
}
test_parse! {
test_uri_parse_absolute_form,
"http://127.0.0.1:61761/chunks",
scheme = Some("http"),
authority = Some("127.0.0.1:61761"),
path = "/chunks",
query = None,
fragment = None,
}
test_parse! {
test_uri_parse_absolute_form_without_path,
"https://127.0.0.1:61761",
scheme = Some("https"),
authority = Some("127.0.0.1:61761"),
path = "/",
query = None,
fragment = None,
}
test_parse! {
test_uri_parse_asterisk_form,
"*",
scheme = None,
authority = None,
path = "*",
query = None,
fragment = None,
}
test_parse! {
test_uri_parse_authority_form,
"localhost:3000",
scheme = None,
authority = Some("localhost:3000"),
path = "",
query = None,
fragment = None,
}
test_parse! {
test_uri_parse_absolute_with_default_port_http,
"http://127.0.0.1:80",
scheme = Some("http"),
authority = Some("127.0.0.1"),
path = "/",
query = None,
fragment = None,
}
test_parse! {
test_uri_parse_absolute_with_default_port_https,
"https://127.0.0.1:443",
scheme = Some("https"),
authority = Some("127.0.0.1"),
path = "/",
query = None,
fragment = None,
}
#[test]
fn test_uri_parse_error() {
fn err(s: &str) {
Uri::new(s).unwrap_err();
}
err("http://");
//TODO: these should error
//err("htt:p//host");
//err("hyper.rs/");
//err("hyper.rs?key=val");
}
#[test]
fn test_uri_from_url() {
let uri = Uri::from(Url::parse("http://test.com/nazghul?test=3").unwrap());
assert_eq!(uri.path(), "/nazghul");
assert_eq!(uri.authority(), Some("test.com"));
assert_eq!(uri.scheme(), Some("http"));
assert_eq!(uri.query(), Some("test=3"));
assert_eq!(uri.as_ref(), "http://test.com/nazghul?test=3");
}