use std::convert::TryFrom; use std::hash::{Hash, Hasher}; use std::str::FromStr; use std::{cmp, fmt, str}; use bytes::Bytes; use super::{ErrorKind, InvalidUri, Port, URI_CHARS}; use crate::byte_str::ByteStr; /// Represents the authority component of a URI. #[derive(Clone)] pub struct Authority { pub(super) data: ByteStr, } impl Authority { pub(super) fn empty() -> Self { Authority { data: ByteStr::new(), } } // Not public while `bytes` is unstable. pub(super) fn from_shared(s: Bytes) -> Result { // Precondition on create_authority: trivially satisfied by the // identity clousre create_authority(s, |s| s) } /// Attempt to convert an `Authority` from a static string. /// /// This function will not perform any copying, and the string will be /// checked if it is empty or contains an invalid character. /// /// # Panics /// /// This function panics if the argument contains invalid characters or /// is empty. /// /// # Examples /// /// ``` /// # use http::uri::Authority; /// let authority = Authority::from_static("example.com"); /// assert_eq!(authority.host(), "example.com"); /// ``` pub fn from_static(src: &'static str) -> Self { Authority::from_shared(Bytes::from_static(src.as_bytes())) .expect("static str is not valid authority") } /// Attempt to convert a `Bytes` buffer to a `Authority`. /// /// This will try to prevent a copy if the type passed is the type used /// internally, and will copy the data if it is not. pub fn from_maybe_shared(src: T) -> Result where T: AsRef<[u8]> + 'static, { if_downcast_into!(T, Bytes, src, { return Authority::from_shared(src); }); Authority::try_from(src.as_ref()) } // Note: this may return an *empty* Authority. You might want `parse_non_empty`. // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where // ret is the return value. pub(super) fn parse(s: &[u8]) -> Result { let mut colon_cnt = 0; let mut start_bracket = false; let mut end_bracket = false; let mut has_percent = false; let mut end = s.len(); let mut at_sign_pos = None; // Among other things, this loop checks that every byte in s up to the // first '/', '?', or '#' is a valid URI character (or in some contexts, // a '%'). This means that each such byte is a valid single-byte UTF-8 // code point. for (i, &b) in s.iter().enumerate() { match URI_CHARS[b as usize] { b'/' | b'?' | b'#' => { end = i; break; } b':' => { colon_cnt += 1; } b'[' => { if has_percent || start_bracket { // Something other than the userinfo has a `%`, so reject it. return Err(ErrorKind::InvalidAuthority.into()); } start_bracket = true; } b']' => { if end_bracket { return Err(ErrorKind::InvalidAuthority.into()); } end_bracket = true; // Those were part of an IPv6 hostname, so forget them... colon_cnt = 0; has_percent = false; } b'@' => { at_sign_pos = Some(i); // Those weren't a port colon, but part of the // userinfo, so it needs to be forgotten. colon_cnt = 0; has_percent = false; } 0 if b == b'%' => { // Per https://tools.ietf.org/html/rfc3986#section-3.2.1 and // https://url.spec.whatwg.org/#authority-state // the userinfo can have a percent-encoded username and password, // so record that a `%` was found. If this turns out to be // part of the userinfo, this flag will be cleared. // Also per https://tools.ietf.org/html/rfc6874, percent-encoding can // be used to indicate a zone identifier. // If the flag hasn't been cleared at the end, that means this // was part of the hostname (and not part of an IPv6 address), and // will fail with an error. has_percent = true; } 0 => { return Err(ErrorKind::InvalidUriChar.into()); } _ => {} } } if start_bracket ^ end_bracket { return Err(ErrorKind::InvalidAuthority.into()); } if colon_cnt > 1 { // Things like 'localhost:8080:3030' are rejected. return Err(ErrorKind::InvalidAuthority.into()); } if end > 0 && at_sign_pos == Some(end - 1) { // If there's nothing after an `@`, this is bonkers. return Err(ErrorKind::InvalidAuthority.into()); } if has_percent { // Something after the userinfo has a `%`, so reject it. return Err(ErrorKind::InvalidAuthority.into()); } Ok(end) } // Parse bytes as an Authority, not allowing an empty string. // // This should be used by functions that allow a user to parse // an `Authority` by itself. // // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where // ret is the return value. fn parse_non_empty(s: &[u8]) -> Result { if s.is_empty() { return Err(ErrorKind::Empty.into()); } Authority::parse(s) } /// Get the host of this `Authority`. /// /// The host subcomponent of authority is identified by an IP literal /// encapsulated within square brackets, an IPv4 address in dotted- decimal /// form, or a registered name. The host subcomponent is **case-insensitive**. /// /// ```notrust /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 /// |---------| /// | /// host /// ``` /// /// # Examples /// /// ``` /// # use http::uri::*; /// let authority: Authority = "example.org:80".parse().unwrap(); /// /// assert_eq!(authority.host(), "example.org"); /// ``` #[inline] pub fn host(&self) -> &str { host(self.as_str()) } /// Get the port part of this `Authority`. /// /// The port subcomponent of authority is designated by an optional port /// number following the host and delimited from it by a single colon (":") /// character. It can be turned into a decimal port number with the `as_u16` /// method or as a `str` with the `as_str` method. /// /// ```notrust /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 /// |-| /// | /// port /// ``` /// /// # Examples /// /// Authority with port /// /// ``` /// # use http::uri::Authority; /// let authority: Authority = "example.org:80".parse().unwrap(); /// /// let port = authority.port().unwrap(); /// assert_eq!(port.as_u16(), 80); /// assert_eq!(port.as_str(), "80"); /// ``` /// /// Authority without port /// /// ``` /// # use http::uri::Authority; /// let authority: Authority = "example.org".parse().unwrap(); /// /// assert!(authority.port().is_none()); /// ``` pub fn port(&self) -> Option> { let bytes = self.as_str(); bytes .rfind(":") .and_then(|i| Port::from_str(&bytes[i + 1..]).ok()) } /// Get the port of this `Authority` as a `u16`. /// /// # Example /// /// ``` /// # use http::uri::Authority; /// let authority: Authority = "example.org:80".parse().unwrap(); /// /// assert_eq!(authority.port_u16(), Some(80)); /// ``` pub fn port_u16(&self) -> Option { self.port().and_then(|p| Some(p.as_u16())) } /// Return a str representation of the authority #[inline] pub fn as_str(&self) -> &str { &self.data[..] } } // Purposefully not public while `bytes` is unstable. // impl TryFrom for Authority impl AsRef for Authority { fn as_ref(&self) -> &str { self.as_str() } } impl PartialEq for Authority { fn eq(&self, other: &Authority) -> bool { self.data.eq_ignore_ascii_case(&other.data) } } impl Eq for Authority {} /// Case-insensitive equality /// /// # Examples /// /// ``` /// # use http::uri::Authority; /// let authority: Authority = "HELLO.com".parse().unwrap(); /// assert_eq!(authority, "hello.coM"); /// assert_eq!("hello.com", authority); /// ``` impl PartialEq for Authority { fn eq(&self, other: &str) -> bool { self.data.eq_ignore_ascii_case(other) } } impl PartialEq for str { fn eq(&self, other: &Authority) -> bool { self.eq_ignore_ascii_case(other.as_str()) } } impl<'a> PartialEq for &'a str { fn eq(&self, other: &Authority) -> bool { self.eq_ignore_ascii_case(other.as_str()) } } impl<'a> PartialEq<&'a str> for Authority { fn eq(&self, other: &&'a str) -> bool { self.data.eq_ignore_ascii_case(other) } } impl PartialEq for Authority { fn eq(&self, other: &String) -> bool { self.data.eq_ignore_ascii_case(other.as_str()) } } impl PartialEq for String { fn eq(&self, other: &Authority) -> bool { self.as_str().eq_ignore_ascii_case(other.as_str()) } } /// Case-insensitive ordering /// /// # Examples /// /// ``` /// # use http::uri::Authority; /// let authority: Authority = "DEF.com".parse().unwrap(); /// assert!(authority < "ghi.com"); /// assert!(authority > "abc.com"); /// ``` impl PartialOrd for Authority { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl PartialOrd for Authority { fn partial_cmp(&self, other: &str) -> Option { let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl PartialOrd for str { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl<'a> PartialOrd for &'a str { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl<'a> PartialOrd<&'a str> for Authority { fn partial_cmp(&self, other: &&'a str) -> Option { let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl PartialOrd for Authority { fn partial_cmp(&self, other: &String) -> Option { let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } impl PartialOrd for String { fn partial_cmp(&self, other: &Authority) -> Option { let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); left.partial_cmp(right) } } /// Case-insensitive hashing /// /// # Examples /// /// ``` /// # use http::uri::Authority; /// # use std::hash::{Hash, Hasher}; /// # use std::collections::hash_map::DefaultHasher; /// /// let a: Authority = "HELLO.com".parse().unwrap(); /// let b: Authority = "hello.coM".parse().unwrap(); /// /// let mut s = DefaultHasher::new(); /// a.hash(&mut s); /// let a = s.finish(); /// /// let mut s = DefaultHasher::new(); /// b.hash(&mut s); /// let b = s.finish(); /// /// assert_eq!(a, b); /// ``` impl Hash for Authority { fn hash(&self, state: &mut H) where H: Hasher, { self.data.len().hash(state); for &b in self.data.as_bytes() { state.write_u8(b.to_ascii_lowercase()); } } } impl<'a> TryFrom<&'a [u8]> for Authority { type Error = InvalidUri; #[inline] fn try_from(s: &'a [u8]) -> Result { // parse first, and only turn into Bytes if valid // Preconditon on create_authority: copy_from_slice() copies all of // bytes from the [u8] parameter into a new Bytes create_authority(s, |s| Bytes::copy_from_slice(s)) } } impl<'a> TryFrom<&'a str> for Authority { type Error = InvalidUri; #[inline] fn try_from(s: &'a str) -> Result { TryFrom::try_from(s.as_bytes()) } } impl TryFrom> for Authority { type Error = InvalidUri; #[inline] fn try_from(vec: Vec) -> Result { Authority::from_shared(vec.into()) } } impl TryFrom for Authority { type Error = InvalidUri; #[inline] fn try_from(t: String) -> Result { Authority::from_shared(t.into()) } } impl FromStr for Authority { type Err = InvalidUri; fn from_str(s: &str) -> Result { TryFrom::try_from(s) } } impl fmt::Debug for Authority { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl fmt::Display for Authority { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } fn host(auth: &str) -> &str { let host_port = auth .rsplitn(2, '@') .next() .expect("split always has at least 1 item"); if host_port.as_bytes()[0] == b'[' { let i = host_port .find(']') .expect("parsing should validate brackets"); // ..= ranges aren't available in 1.20, our minimum Rust version... &host_port[0..i + 1] } else { host_port .split(':') .next() .expect("split always has at least 1 item") } } // Precondition: f converts all of the bytes in the passed in B into the // returned Bytes. fn create_authority(b: B, f: F) -> Result where B: AsRef<[u8]>, F: FnOnce(B) -> Bytes, { let s = b.as_ref(); let authority_end = Authority::parse_non_empty(s)?; if authority_end != s.len() { return Err(ErrorKind::InvalidUriChar.into()); } let bytes = f(b); Ok(Authority { // Safety: the postcondition on parse_non_empty() and the check against // s.len() ensure that b is valid UTF-8. The precondition on f ensures // that this is carried through to bytes. data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, }) } #[cfg(test)] mod tests { use super::*; #[test] fn parse_empty_string_is_error() { let err = Authority::parse_non_empty(b"").unwrap_err(); assert_eq!(err.0, ErrorKind::Empty); } #[test] fn equal_to_self_of_same_authority() { let authority1: Authority = "example.com".parse().unwrap(); let authority2: Authority = "EXAMPLE.COM".parse().unwrap(); assert_eq!(authority1, authority2); assert_eq!(authority2, authority1); } #[test] fn not_equal_to_self_of_different_authority() { let authority1: Authority = "example.com".parse().unwrap(); let authority2: Authority = "test.com".parse().unwrap(); assert_ne!(authority1, authority2); assert_ne!(authority2, authority1); } #[test] fn equates_with_a_str() { let authority: Authority = "example.com".parse().unwrap(); assert_eq!(&authority, "EXAMPLE.com"); assert_eq!("EXAMPLE.com", &authority); assert_eq!(authority, "EXAMPLE.com"); assert_eq!("EXAMPLE.com", authority); } #[test] fn from_static_equates_with_a_str() { let authority = Authority::from_static("example.com"); assert_eq!(authority, "example.com"); } #[test] fn not_equal_with_a_str_of_a_different_authority() { let authority: Authority = "example.com".parse().unwrap(); assert_ne!(&authority, "test.com"); assert_ne!("test.com", &authority); assert_ne!(authority, "test.com"); assert_ne!("test.com", authority); } #[test] fn equates_with_a_string() { let authority: Authority = "example.com".parse().unwrap(); assert_eq!(authority, "EXAMPLE.com".to_string()); assert_eq!("EXAMPLE.com".to_string(), authority); } #[test] fn equates_with_a_string_of_a_different_authority() { let authority: Authority = "example.com".parse().unwrap(); assert_ne!(authority, "test.com".to_string()); assert_ne!("test.com".to_string(), authority); } #[test] fn compares_to_self() { let authority1: Authority = "abc.com".parse().unwrap(); let authority2: Authority = "def.com".parse().unwrap(); assert!(authority1 < authority2); assert!(authority2 > authority1); } #[test] fn compares_with_a_str() { let authority: Authority = "def.com".parse().unwrap(); // with ref assert!(&authority < "ghi.com"); assert!("ghi.com" > &authority); assert!(&authority > "abc.com"); assert!("abc.com" < &authority); // no ref assert!(authority < "ghi.com"); assert!("ghi.com" > authority); assert!(authority > "abc.com"); assert!("abc.com" < authority); } #[test] fn compares_with_a_string() { let authority: Authority = "def.com".parse().unwrap(); assert!(authority < "ghi.com".to_string()); assert!("ghi.com".to_string() > authority); assert!(authority > "abc.com".to_string()); assert!("abc.com".to_string() < authority); } #[test] fn allows_percent_in_userinfo() { let authority_str = "a%2f:b%2f@example.com"; let authority: Authority = authority_str.parse().unwrap(); assert_eq!(authority, authority_str); } #[test] fn rejects_percent_in_hostname() { let err = Authority::parse_non_empty(b"example%2f.com").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); let err = Authority::parse_non_empty(b"a%2f:b%2f@example%2f.com").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); } #[test] fn allows_percent_in_ipv6_address() { let authority_str = "[fe80::1:2:3:4%25eth0]"; let result: Authority = authority_str.parse().unwrap(); assert_eq!(result, authority_str); } #[test] fn rejects_percent_outside_ipv6_address() { let err = Authority::parse_non_empty(b"1234%20[fe80::1:2:3:4]").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); } #[test] fn rejects_invalid_utf8() { let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidUriChar); let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())) .unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidUriChar); } #[test] fn rejects_invalid_use_of_brackets() { let err = Authority::parse_non_empty(b"[]@[").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); } }