use std::error::Error as StdError; use std::fmt::{Display, self}; use std::str::{self, FromStr}; use http::ByteStr; use bytes::{BufMut, BytesMut}; /// 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 /// ```notrust /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 /// |-| |-------------------------------||--------| |-------------------| |-----| /// | | | | | /// scheme authority path query fragment /// ``` #[derive(Clone, Hash)] pub struct Uri { source: ByteStr, scheme_end: Option, authority_end: Option, query_start: Option, fragment_start: Option, } impl Uri { /// Parse a string into a `Uri`. fn new(s: ByteStr) -> Result { if s.len() == 0 { Err(UriError(ErrorKind::Empty)) } else if s.as_bytes() == b"*" { // asterisk-form Ok(asterisk_form()) } else if s.as_bytes() == b"/" { // shortcut for '/' Ok(Uri::default()) } else if s.as_bytes()[0] == b'/' { // origin-form let query = parse_query(&s); let fragment = parse_fragment(&s); Ok(Uri { source: s, scheme_end: None, authority_end: None, query_start: query, fragment_start: fragment, }) } else if s.contains("://") { // absolute-form let scheme = parse_scheme(&s); let auth = Some(parse_authority(&s)); let scheme_end = scheme.expect("just checked for ':' above"); let auth_end = auth.expect("just checked for ://"); if scheme_end + 3 == auth_end { // authority was empty return Err(UriError(ErrorKind::MissingAuthority)); } let query = parse_query(&s); let fragment = parse_fragment(&s); Ok(Uri { source: s, scheme_end: scheme, authority_end: auth, query_start: query, fragment_start: fragment, }) } else if (s.contains("/") || s.contains("?")) && !s.contains("://") { // last possibility is authority-form, above are illegal characters return Err(UriError(ErrorKind::Malformed)) } else { // authority-form let len = s.len(); Ok(Uri { source: s, scheme_end: None, authority_end: Some(len), query_start: None, fragment_start: None, }) } } /// Get the path of this `Uri`. #[inline] pub fn path(&self) -> &str { let index = self.path_start(); let end = self.path_end(); if index >= end { if self.scheme().is_some() { "/" // absolute-form MUST have path } else { "" } } else { &self.source[index..end] } } #[inline] fn path_start(&self) -> usize { self.authority_end.unwrap_or(self.scheme_end.unwrap_or(0)) } #[inline] fn path_end(&self) -> usize { if let Some(query) = self.query_start { query } else if let Some(fragment) = self.fragment_start { fragment } else { self.source.len() } } #[inline] fn origin_form_end(&self) -> usize { if let Some(fragment) = self.fragment_start { fragment } else { self.source.len() } } /// Get the scheme of this `Uri`. #[inline] 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`. #[inline] 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`. #[inline] pub fn host(&self) -> Option<&str> { if let Some(auth) = self.authority() { auth.split(":").next() } else { None } } /// Get the port of this `Uri`. #[inline] pub fn port(&self) -> Option { match self.authority() { Some(auth) => auth.find(":").and_then(|i| u16::from_str(&auth[i+1..]).ok()), None => None, } } /// Get the query string of this `Uri`, starting after the `?`. #[inline] pub fn query(&self) -> Option<&str> { self.query_start.map(|start| { // +1 to remove '?' let start = start + 1; if let Some(end) = self.fragment_start { &self.source[start..end] } else { &self.source[start..] } }) } /// Returns whether this URI is in `absolute-form`. /// /// An example of absolute form is `https://hyper.rs`. #[inline] pub fn is_absolute(&self) -> bool { self.scheme_end.is_some() } #[cfg(test)] fn fragment(&self) -> Option<&str> { self.fragment_start.map(|start| { // +1 to remove the '#' &self.source[start + 1..] }) } } fn parse_scheme(s: &str) -> Option { s.find(':') } fn parse_authority(s: &str) -> usize { let i = s.find("://").map(|p| p + 3).unwrap_or(0); s[i..].find('/') .or_else(|| s[i..].find('?')) .or_else(|| s[i..].find('#')) .map(|end| end + i) .unwrap_or(s.len()) } fn parse_query(s: &str) -> Option { s.find('?').and_then(|i| { if let Some(frag) = s.find('#') { if frag < i { None } else { Some(i) } } else { Some(i) } }) } fn parse_fragment(s: &str) -> Option { s.find('#') } impl FromStr for Uri { type Err = UriError; fn from_str(s: &str) -> Result { //TODO: refactor such that the to_owned() is only required at the end //of successful parsing, so an Err doesn't needlessly clone the string. Uri::new(ByteStr::from(s)) } } impl PartialEq for Uri { fn eq(&self, other: &Uri) -> bool { self.source.as_str() == other.source.as_str() } } impl<'a> PartialEq<&'a str> for Uri { fn eq(&self, other: & &'a str) -> bool { self.source.as_str() == *other } } impl<'a> PartialEq for &'a str{ fn eq(&self, other: &Uri) -> bool { *self == other.source.as_str() } } impl Eq for Uri {} impl AsRef for Uri { fn as_ref(&self) -> &str { self.source.as_str() } } impl Default for Uri { fn default() -> Uri { Uri { source: ByteStr::from_static("/"), scheme_end: None, authority_end: None, query_start: None, fragment_start: None, } } } impl fmt::Debug for Uri { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self.as_ref(), f) } } impl Display for Uri { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_ref()) } } pub fn from_byte_str(s: ByteStr) -> Result { Uri::new(s) } pub fn scheme_and_authority(uri: &Uri) -> Option { if uri.scheme_end.is_some() { Some(Uri { source: uri.source.slice_to(uri.authority_end.expect("scheme without authority")), scheme_end: uri.scheme_end, authority_end: uri.authority_end, query_start: None, fragment_start: None, }) } else { None } } #[inline] fn asterisk_form() -> Uri { Uri { source: ByteStr::from_static("*"), scheme_end: None, authority_end: None, query_start: None, fragment_start: None, } } pub fn origin_form(uri: &Uri) -> Uri { let range = Range(uri.path_start(), uri.origin_form_end()); let clone = if range.len() == 0 { ByteStr::from_static("/") } else if uri.source.as_bytes()[range.0] == b'*' { return asterisk_form(); } else if uri.source.as_bytes()[range.0] != b'/' { let mut new = BytesMut::with_capacity(range.1 - range.0 + 1); new.put_u8(b'/'); new.put_slice(&uri.source.as_bytes()[range.0..range.1]); // safety: the bytes are '/' + previous utf8 str unsafe { ByteStr::from_utf8_unchecked(new.freeze()) } } else if range.0 == 0 && range.1 == uri.source.len() { uri.source.clone() } else { uri.source.slice(range.0, range.1) }; Uri { source: clone, scheme_end: None, authority_end: None, query_start: uri.query_start, fragment_start: None, } } struct Range(usize, usize); impl Range { fn len(&self) -> usize { self.1 - self.0 } } /// An error parsing a `Uri`. #[derive(Clone, Debug)] pub struct UriError(ErrorKind); #[derive(Clone, Debug)] enum ErrorKind { Empty, Malformed, MissingAuthority, } impl fmt::Display for UriError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad(self.description()) } } impl StdError for UriError { fn description(&self) -> &str { match self.0 { ErrorKind::Empty => "empty Uri string", ErrorKind::Malformed => "invalid character in Uri authority", ErrorKind::MissingAuthority => "absolute Uri missing authority segment", } } } macro_rules! test_parse { ( $test_name:ident, $str:expr, $($method:ident = $value:expr,)* ) => ( #[test] fn $test_name() { let uri = Uri::from_str($str).unwrap(); println!("{:?} = {:#?}", $str, uri); $( 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, port = Some(61761), } 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, port = Some(61761), } test_parse! { test_uri_parse_asterisk_form, "*", scheme = None, authority = None, path = "*", query = None, fragment = None, } test_parse! { test_uri_parse_authority_no_port, "localhost", scheme = None, authority = Some("localhost"), path = "", query = None, fragment = None, port = None, } test_parse! { test_uri_parse_authority_form, "localhost:3000", scheme = None, authority = Some("localhost:3000"), path = "", query = None, fragment = None, port = Some(3000), } 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:80"), path = "/", query = None, fragment = None, port = Some(80), } 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:443"), path = "/", query = None, fragment = None, port = Some(443), } test_parse! { test_uri_parse_fragment_questionmark, "http://127.0.0.1/#?", scheme = Some("http"), authority = Some("127.0.0.1"), path = "/", query = None, fragment = Some("?"), port = None, } test_parse! { test_uri_parse_path_with_terminating_questionmark, "http://127.0.0.1/path?", scheme = Some("http"), authority = Some("127.0.0.1"), path = "/path", query = Some(""), fragment = None, port = None, } test_parse! { test_uri_parse_absolute_form_with_empty_path_and_nonempty_query, "http://127.0.0.1?foo=bar", scheme = Some("http"), authority = Some("127.0.0.1"), path = "/", query = Some("foo=bar"), fragment = None, port = None, } #[test] fn test_uri_parse_error() { fn err(s: &str) { Uri::from_str(s).unwrap_err(); } err("http://"); err("htt:p//host"); err("hyper.rs/"); err("hyper.rs?key=val"); err("?key=val"); err("localhost/"); err("localhost?key=val"); } #[test] fn test_uri_to_origin_form() { let cases = vec![ ("/", "/"), ("/foo?bar", "/foo?bar"), ("/foo?bar#nope", "/foo?bar"), ("http://hyper.rs", "/"), ("http://hyper.rs/", "/"), ("http://hyper.rs/path", "/path"), ("http://hyper.rs?query", "/?query"), ("*", "*"), ]; for case in cases { let uri = Uri::from_str(case.0).unwrap(); assert_eq!(origin_form(&uri), case.1); //, "{:?}", case); } }