This allows servers to check if an incoming URI is in absolute form. Especially useful for proxies to determine if the request was meant to be proxied or aimed directly at the proxy server.
		
			
				
	
	
		
			573 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			573 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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<usize>,
 | |
|     authority_end: Option<usize>,
 | |
|     query_start: Option<usize>,
 | |
|     fragment_start: Option<usize>,
 | |
| }
 | |
| 
 | |
| impl Uri {
 | |
|     /// Parse a string into a `Uri`.
 | |
|     fn new(s: ByteStr) -> Result<Uri, UriError> {
 | |
|         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<u16> {
 | |
|         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<usize> {
 | |
|     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<usize> {
 | |
|     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<usize> {
 | |
|     s.find('#')
 | |
| }
 | |
| 
 | |
| impl FromStr for Uri {
 | |
|     type Err = UriError;
 | |
| 
 | |
|     fn from_str(s: &str) -> Result<Uri, UriError> {
 | |
|         //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<Uri> for &'a str{
 | |
|     fn eq(&self, other: &Uri) -> bool {
 | |
|         *self == other.source.as_str()
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Eq for Uri {}
 | |
| 
 | |
| impl AsRef<str> 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, UriError> {
 | |
|     Uri::new(s)
 | |
| }
 | |
| 
 | |
| pub fn scheme_and_authority(uri: &Uri) -> Option<Uri> {
 | |
|     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);
 | |
|     }
 | |
| }
 |