diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/http/src/uri | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/http/src/uri')
-rw-r--r-- | third_party/rust/http/src/uri/authority.rs | 671 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/builder.rs | 197 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/mod.rs | 1118 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/path.rs | 564 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/port.rs | 151 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/scheme.rs | 363 | ||||
-rw-r--r-- | third_party/rust/http/src/uri/tests.rs | 519 |
7 files changed, 3583 insertions, 0 deletions
diff --git a/third_party/rust/http/src/uri/authority.rs b/third_party/rust/http/src/uri/authority.rs new file mode 100644 index 0000000000..7a43bc10ff --- /dev/null +++ b/third_party/rust/http/src/uri/authority.rs @@ -0,0 +1,671 @@ +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<Self, InvalidUri> { + // 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<T>(src: T) -> Result<Self, InvalidUri> + 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<usize, InvalidUri> { + 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<usize, InvalidUri> { + 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<Port<&str>> { + 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<u16> { + 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<Bytes> for Authority + +impl AsRef<str> 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<str> for Authority { + fn eq(&self, other: &str) -> bool { + self.data.eq_ignore_ascii_case(other) + } +} + +impl PartialEq<Authority> for str { + fn eq(&self, other: &Authority) -> bool { + self.eq_ignore_ascii_case(other.as_str()) + } +} + +impl<'a> PartialEq<Authority> 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<String> for Authority { + fn eq(&self, other: &String) -> bool { + self.data.eq_ignore_ascii_case(other.as_str()) + } +} + +impl PartialEq<Authority> 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<cmp::Ordering> { + 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<str> for Authority { + fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { + 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<Authority> for str { + fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { + 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<Authority> for &'a str { + fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { + 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<cmp::Ordering> { + 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<String> for Authority { + fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { + 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<Authority> for String { + fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { + 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<H>(&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<Self, Self::Error> { + // 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<Self, Self::Error> { + TryFrom::try_from(s.as_bytes()) + } +} + +impl TryFrom<Vec<u8>> for Authority { + type Error = InvalidUri; + + #[inline] + fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { + Authority::from_shared(vec.into()) + } +} + +impl TryFrom<String> for Authority { + type Error = InvalidUri; + + #[inline] + fn try_from(t: String) -> Result<Self, Self::Error> { + Authority::from_shared(t.into()) + } +} + +impl FromStr for Authority { + type Err = InvalidUri; + + fn from_str(s: &str) -> Result<Self, InvalidUri> { + 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, F>(b: B, f: F) -> Result<Authority, InvalidUri> +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); + } +} diff --git a/third_party/rust/http/src/uri/builder.rs b/third_party/rust/http/src/uri/builder.rs new file mode 100644 index 0000000000..825c0fafcc --- /dev/null +++ b/third_party/rust/http/src/uri/builder.rs @@ -0,0 +1,197 @@ +use std::convert::{TryFrom, TryInto}; + +use super::{Authority, Parts, PathAndQuery, Scheme}; +use crate::Uri; + +/// A builder for `Uri`s. +/// +/// This type can be used to construct an instance of `Uri` +/// through a builder pattern. +#[derive(Debug)] +pub struct Builder { + parts: Result<Parts, crate::Error>, +} + +impl Builder { + /// Creates a new default instance of `Builder` to construct a `Uri`. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let uri = uri::Builder::new() + /// .scheme("https") + /// .authority("hyper.rs") + /// .path_and_query("/") + /// .build() + /// .unwrap(); + /// ``` + #[inline] + pub fn new() -> Builder { + Builder::default() + } + + /// Set the `Scheme` for this URI. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let mut builder = uri::Builder::new(); + /// builder.scheme("https"); + /// ``` + pub fn scheme<T>(self, scheme: T) -> Self + where + Scheme: TryFrom<T>, + <Scheme as TryFrom<T>>::Error: Into<crate::Error>, + { + self.map(move |mut parts| { + let scheme = scheme.try_into().map_err(Into::into)?; + parts.scheme = Some(scheme); + Ok(parts) + }) + } + + /// Set the `Authority` for this URI. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let uri = uri::Builder::new() + /// .authority("tokio.rs") + /// .build() + /// .unwrap(); + /// ``` + pub fn authority<T>(self, auth: T) -> Self + where + Authority: TryFrom<T>, + <Authority as TryFrom<T>>::Error: Into<crate::Error>, + { + self.map(move |mut parts| { + let auth = auth.try_into().map_err(Into::into)?; + parts.authority = Some(auth); + Ok(parts) + }) + } + + /// Set the `PathAndQuery` for this URI. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let uri = uri::Builder::new() + /// .path_and_query("/hello?foo=bar") + /// .build() + /// .unwrap(); + /// ``` + pub fn path_and_query<T>(self, p_and_q: T) -> Self + where + PathAndQuery: TryFrom<T>, + <PathAndQuery as TryFrom<T>>::Error: Into<crate::Error>, + { + self.map(move |mut parts| { + let p_and_q = p_and_q.try_into().map_err(Into::into)?; + parts.path_and_query = Some(p_and_q); + Ok(parts) + }) + } + + /// Consumes this builder, and tries to construct a valid `Uri` from + /// the configured pieces. + /// + /// # Errors + /// + /// This function may return an error if any previously configured argument + /// failed to parse or get converted to the internal representation. For + /// example if an invalid `scheme` was specified via `scheme("!@#%/^")` + /// the error will be returned when this function is called rather than + /// when `scheme` was called. + /// + /// Additionally, the various forms of URI require certain combinations of + /// parts to be set to be valid. If the parts don't fit into any of the + /// valid forms of URI, a new error is returned. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let uri = Uri::builder() + /// .build() + /// .unwrap(); + /// ``` + pub fn build(self) -> Result<Uri, crate::Error> { + let parts = self.parts?; + Uri::from_parts(parts).map_err(Into::into) + } + + // private + + fn map<F>(self, func: F) -> Self + where + F: FnOnce(Parts) -> Result<Parts, crate::Error>, + { + + Builder { + parts: self.parts.and_then(func), + } + } +} + +impl Default for Builder { + #[inline] + fn default() -> Builder { + Builder { + parts: Ok(Parts::default()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_from_str() { + let uri = Builder::new() + .scheme(Scheme::HTTP) + .authority("hyper.rs") + .path_and_query("/foo?a=1") + .build() + .unwrap(); + assert_eq!(uri.scheme_str(), Some("http")); + assert_eq!(uri.authority().unwrap().host(), "hyper.rs"); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some("a=1")); + } + + #[test] + fn build_from_string() { + for i in 1..10 { + let uri = Builder::new() + .path_and_query(format!("/foo?a={}", i)) + .build() + .unwrap(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + } + + #[test] + fn build_from_string_ref() { + for i in 1..10 { + let p_a_q = format!("/foo?a={}", i); + let uri = Builder::new().path_and_query(&p_a_q).build().unwrap(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + } +} diff --git a/third_party/rust/http/src/uri/mod.rs b/third_party/rust/http/src/uri/mod.rs new file mode 100644 index 0000000000..30be83b570 --- /dev/null +++ b/third_party/rust/http/src/uri/mod.rs @@ -0,0 +1,1118 @@ +//! URI component of request and response lines +//! +//! This module primarily contains the `Uri` type which is a component of all +//! HTTP requests and also reexports this type at the root of the crate. A URI +//! is not always a "full URL" in the sense of something you'd type into a web +//! browser, but HTTP requests may only have paths on servers but may have full +//! schemes and hostnames on clients. +//! +//! # Examples +//! +//! ``` +//! use http::Uri; +//! +//! let uri = "/foo/bar?baz".parse::<Uri>().unwrap(); +//! assert_eq!(uri.path(), "/foo/bar"); +//! assert_eq!(uri.query(), Some("baz")); +//! assert_eq!(uri.host(), None); +//! +//! let uri = "https://www.rust-lang.org/install.html".parse::<Uri>().unwrap(); +//! assert_eq!(uri.scheme_str(), Some("https")); +//! assert_eq!(uri.host(), Some("www.rust-lang.org")); +//! assert_eq!(uri.path(), "/install.html"); +//! ``` + +use crate::byte_str::ByteStr; +use std::convert::TryFrom; + +use bytes::Bytes; + +use std::error::Error; +use std::hash::{Hash, Hasher}; +use std::str::{self, FromStr}; +use std::{fmt, u16, u8}; + +use self::scheme::Scheme2; + +pub use self::authority::Authority; +pub use self::builder::Builder; +pub use self::path::PathAndQuery; +pub use self::port::Port; +pub use self::scheme::Scheme; + +mod authority; +mod builder; +mod path; +mod port; +mod scheme; +#[cfg(test)] +mod tests; + +/// The URI component of a request. +/// +/// For HTTP 1, this is included as part of the request line. 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 +/// > ``` +/// +/// The URI is structured as follows: +/// +/// ```notrust +/// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 +/// |-| |-------------------------------||--------| |-------------------| |-----| +/// | | | | | +/// scheme authority path query fragment +/// ``` +/// +/// For HTTP 2.0, the URI is encoded using pseudoheaders. +/// +/// # Examples +/// +/// ``` +/// use http::Uri; +/// +/// let uri = "/foo/bar?baz".parse::<Uri>().unwrap(); +/// assert_eq!(uri.path(), "/foo/bar"); +/// assert_eq!(uri.query(), Some("baz")); +/// assert_eq!(uri.host(), None); +/// +/// let uri = "https://www.rust-lang.org/install.html".parse::<Uri>().unwrap(); +/// assert_eq!(uri.scheme_str(), Some("https")); +/// assert_eq!(uri.host(), Some("www.rust-lang.org")); +/// assert_eq!(uri.path(), "/install.html"); +/// ``` +#[derive(Clone)] +pub struct Uri { + scheme: Scheme, + authority: Authority, + path_and_query: PathAndQuery, +} + +/// The various parts of a URI. +/// +/// This struct is used to provide to and retrieve from a URI. +#[derive(Debug, Default)] +pub struct Parts { + /// The scheme component of a URI + pub scheme: Option<Scheme>, + + /// The authority component of a URI + pub authority: Option<Authority>, + + /// The origin-form component of a URI + pub path_and_query: Option<PathAndQuery>, + + /// Allow extending in the future + _priv: (), +} + +/// An error resulting from a failed attempt to construct a URI. +#[derive(Debug)] +pub struct InvalidUri(ErrorKind); + +/// An error resulting from a failed attempt to construct a URI. +#[derive(Debug)] +pub struct InvalidUriParts(InvalidUri); + +#[derive(Debug, Eq, PartialEq)] +enum ErrorKind { + InvalidUriChar, + InvalidScheme, + InvalidAuthority, + InvalidPort, + InvalidFormat, + SchemeMissing, + AuthorityMissing, + PathAndQueryMissing, + TooLong, + Empty, + SchemeTooLong, +} + +// u16::MAX is reserved for None +const MAX_LEN: usize = (u16::MAX - 1) as usize; + +// URI_CHARS is a table of valid characters in a URI. An entry in the table is +// 0 for invalid characters. For valid characters the entry is itself (i.e. +// the entry for 33 is b'!' because b'!' == 33u8). An important characteristic +// of this table is that all entries above 127 are invalid. This makes all of the +// valid entries a valid single-byte UTF-8 code point. This means that a slice +// of such valid entries is valid UTF-8. +const URI_CHARS: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x + 0, 0, 0, b'!', 0, b'#', b'$', 0, b'&', b'\'', // 3x + b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', // 4x + b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', // 5x + 0, b'=', 0, b'?', b'@', b'A', b'B', b'C', b'D', b'E', // 6x + b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x + b'Z', b'[', 0, b']', 0, b'_', 0, b'a', b'b', b'c', // 9x + b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x + b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x + b'x', b'y', b'z', 0, 0, 0, b'~', 0, 0, 0, // 12x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x + 0, 0, 0, 0, 0, 0 // 25x +]; + +impl Uri { + /// Creates a new builder-style object to manufacture a `Uri`. + /// + /// This method returns an instance of `Builder` which can be usd to + /// create a `Uri`. + /// + /// # Examples + /// + /// ``` + /// use http::Uri; + /// + /// let uri = Uri::builder() + /// .scheme("https") + /// .authority("hyper.rs") + /// .path_and_query("/") + /// .build() + /// .unwrap(); + /// ``` + pub fn builder() -> Builder { + Builder::new() + } + + /// Attempt to convert a `Parts` into a `Uri`. + /// + /// # Examples + /// + /// Relative URI + /// + /// ``` + /// # use http::uri::*; + /// let mut parts = Parts::default(); + /// parts.path_and_query = Some("/foo".parse().unwrap()); + /// + /// let uri = Uri::from_parts(parts).unwrap(); + /// + /// assert_eq!(uri.path(), "/foo"); + /// + /// assert!(uri.scheme().is_none()); + /// assert!(uri.authority().is_none()); + /// ``` + /// + /// Absolute URI + /// + /// ``` + /// # use http::uri::*; + /// let mut parts = Parts::default(); + /// parts.scheme = Some("http".parse().unwrap()); + /// parts.authority = Some("foo.com".parse().unwrap()); + /// parts.path_and_query = Some("/foo".parse().unwrap()); + /// + /// let uri = Uri::from_parts(parts).unwrap(); + /// + /// assert_eq!(uri.scheme().unwrap().as_str(), "http"); + /// assert_eq!(uri.authority().unwrap(), "foo.com"); + /// assert_eq!(uri.path(), "/foo"); + /// ``` + pub fn from_parts(src: Parts) -> Result<Uri, InvalidUriParts> { + if src.scheme.is_some() { + if src.authority.is_none() { + return Err(ErrorKind::AuthorityMissing.into()); + } + + if src.path_and_query.is_none() { + return Err(ErrorKind::PathAndQueryMissing.into()); + } + } else { + if src.authority.is_some() && src.path_and_query.is_some() { + return Err(ErrorKind::SchemeMissing.into()); + } + } + + let scheme = match src.scheme { + Some(scheme) => scheme, + None => Scheme { + inner: Scheme2::None, + }, + }; + + let authority = match src.authority { + Some(authority) => authority, + None => Authority::empty(), + }; + + let path_and_query = match src.path_and_query { + Some(path_and_query) => path_and_query, + None => PathAndQuery::empty(), + }; + + Ok(Uri { + scheme: scheme, + authority: authority, + path_and_query: path_and_query, + }) + } + + /// Attempt to convert a `Bytes` buffer to a `Uri`. + /// + /// 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<T>(src: T) -> Result<Self, InvalidUri> + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return Uri::from_shared(src); + }); + + Uri::try_from(src.as_ref()) + } + + // Not public while `bytes` is unstable. + fn from_shared(s: Bytes) -> Result<Uri, InvalidUri> { + use self::ErrorKind::*; + + if s.len() > MAX_LEN { + return Err(TooLong.into()); + } + + match s.len() { + 0 => { + return Err(Empty.into()); + } + 1 => match s[0] { + b'/' => { + return Ok(Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::slash(), + }); + } + b'*' => { + return Ok(Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::star(), + }); + } + _ => { + let authority = Authority::from_shared(s)?; + + return Ok(Uri { + scheme: Scheme::empty(), + authority: authority, + path_and_query: PathAndQuery::empty(), + }); + } + }, + _ => {} + } + + if s[0] == b'/' { + return Ok(Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::from_shared(s)?, + }); + } + + parse_full(s) + } + + /// Convert a `Uri` from a static string. + /// + /// This function will not perform any copying, however the string is + /// checked to ensure that it is valid. + /// + /// # Panics + /// + /// This function panics if the argument is an invalid URI. + /// + /// # Examples + /// + /// ``` + /// # use http::uri::Uri; + /// let uri = Uri::from_static("http://example.com/foo"); + /// + /// assert_eq!(uri.host().unwrap(), "example.com"); + /// assert_eq!(uri.path(), "/foo"); + /// ``` + pub fn from_static(src: &'static str) -> Self { + let s = Bytes::from_static(src.as_bytes()); + match Uri::from_shared(s) { + Ok(uri) => uri, + Err(e) => panic!("static str is not valid URI: {}", e), + } + } + + /// Convert a `Uri` into `Parts`. + /// + /// # Note + /// + /// This is just an inherent method providing the same functionality as + /// `let parts: Parts = uri.into()` + /// + /// # Examples + /// + /// ``` + /// # use http::uri::*; + /// let uri: Uri = "/foo".parse().unwrap(); + /// + /// let parts = uri.into_parts(); + /// + /// assert_eq!(parts.path_and_query.unwrap(), "/foo"); + /// + /// assert!(parts.scheme.is_none()); + /// assert!(parts.authority.is_none()); + /// ``` + #[inline] + pub fn into_parts(self) -> Parts { + self.into() + } + + /// Returns the path & query components of the Uri + #[inline] + pub fn path_and_query(&self) -> Option<&PathAndQuery> { + if !self.scheme.inner.is_none() || self.authority.data.is_empty() { + Some(&self.path_and_query) + } else { + None + } + } + + /// Get the path of this `Uri`. + /// + /// Both relative and absolute URIs contain a path component, though it + /// might be the empty string. The path component is **case sensitive**. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |--------| + /// | + /// path + /// ``` + /// + /// If the URI is `*` then the path component is equal to `*`. + /// + /// # Examples + /// + /// A relative URI + /// + /// ``` + /// # use http::Uri; + /// + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.path(), "/hello/world"); + /// ``` + /// + /// An absolute URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.path(), "/hello/world"); + /// ``` + #[inline] + pub fn path(&self) -> &str { + if self.has_path() { + self.path_and_query.path() + } else { + "" + } + } + + /// Get the scheme of this `Uri`. + /// + /// The URI scheme refers to a specification for assigning identifiers + /// within that scheme. Only absolute URIs contain a scheme component, but + /// not all absolute URIs will contain a scheme component. Although scheme + /// names are case-insensitive, the canonical form is lowercase. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |-| + /// | + /// scheme + /// ``` + /// + /// # Examples + /// + /// Absolute URI + /// + /// ``` + /// use http::uri::{Scheme, Uri}; + /// + /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.scheme(), Some(&Scheme::HTTP)); + /// ``` + /// + /// + /// Relative URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert!(uri.scheme().is_none()); + /// ``` + #[inline] + pub fn scheme(&self) -> Option<&Scheme> { + if self.scheme.inner.is_none() { + None + } else { + Some(&self.scheme) + } + } + + /// Get the scheme of this `Uri` as a `&str`. + /// + /// # Example + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.scheme_str(), Some("http")); + /// ``` + #[inline] + pub fn scheme_str(&self) -> Option<&str> { + if self.scheme.inner.is_none() { + None + } else { + Some(self.scheme.as_str()) + } + } + + /// Get the authority of this `Uri`. + /// + /// The authority is a hierarchical element for naming authority such that + /// the remainder of the URI is delegated to that authority. For HTTP, the + /// authority consists of the host and port. The host portion of the + /// authority is **case-insensitive**. + /// + /// The authority also includes a `username:password` component, however + /// the use of this is deprecated and should be avoided. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |-------------------------------| + /// | + /// authority + /// ``` + /// + /// # Examples + /// + /// Absolute URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.authority().map(|a| a.as_str()), Some("example.org:80")); + /// ``` + /// + /// + /// Relative URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert!(uri.authority().is_none()); + /// ``` + #[inline] + pub fn authority(&self) -> Option<&Authority> { + if self.authority.data.is_empty() { + None + } else { + Some(&self.authority) + } + } + + /// Get the host of this `Uri`. + /// + /// 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 + /// + /// Absolute URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.host(), Some("example.org")); + /// ``` + /// + /// + /// Relative URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert!(uri.host().is_none()); + /// ``` + #[inline] + pub fn host(&self) -> Option<&str> { + self.authority().map(|a| a.host()) + } + + /// Get the port part of this `Uri`. + /// + /// 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 + /// + /// Absolute URI with port + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); + /// + /// let port = uri.port().unwrap(); + /// assert_eq!(port.as_u16(), 80); + /// ``` + /// + /// Absolute URI without port + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); + /// + /// assert!(uri.port().is_none()); + /// ``` + /// + /// Relative URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert!(uri.port().is_none()); + /// ``` + pub fn port(&self) -> Option<Port<&str>> { + self.authority().and_then(|a| a.port()) + } + + /// Get the port of this `Uri` as a `u16`. + /// + /// + /// # Example + /// + /// ``` + /// # use http::{Uri, uri::Port}; + /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); + /// + /// assert_eq!(uri.port_u16(), Some(80)); + /// ``` + pub fn port_u16(&self) -> Option<u16> { + self.port().and_then(|p| Some(p.as_u16())) + } + + /// Get the query string of this `Uri`, starting after the `?`. + /// + /// The query component contains non-hierarchical data that, along with data + /// in the path component, serves to identify a resource within the scope of + /// the URI's scheme and naming authority (if any). The query component is + /// indicated by the first question mark ("?") character and terminated by a + /// number sign ("#") character or by the end of the URI. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |-------------------| + /// | + /// query + /// ``` + /// + /// # Examples + /// + /// Absolute URI + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "http://example.org/hello/world?key=value".parse().unwrap(); + /// + /// assert_eq!(uri.query(), Some("key=value")); + /// ``` + /// + /// Relative URI with a query string component + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world?key=value&foo=bar".parse().unwrap(); + /// + /// assert_eq!(uri.query(), Some("key=value&foo=bar")); + /// ``` + /// + /// Relative URI without a query string component + /// + /// ``` + /// # use http::Uri; + /// let uri: Uri = "/hello/world".parse().unwrap(); + /// + /// assert!(uri.query().is_none()); + /// ``` + #[inline] + pub fn query(&self) -> Option<&str> { + self.path_and_query.query() + } + + fn has_path(&self) -> bool { + !self.path_and_query.data.is_empty() || !self.scheme.inner.is_none() + } +} + +impl<'a> TryFrom<&'a [u8]> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> { + Uri::from_shared(Bytes::copy_from_slice(t)) + } +} + +impl<'a> TryFrom<&'a str> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(t: &'a str) -> Result<Self, Self::Error> { + t.parse() + } +} + +impl<'a> TryFrom<&'a String> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(t: &'a String) -> Result<Self, Self::Error> { + t.parse() + } +} + +impl TryFrom<String> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(t: String) -> Result<Self, Self::Error> { + Uri::from_shared(Bytes::from(t)) + } +} + +impl<'a> TryFrom<Vec<u8>> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { + Uri::from_shared(Bytes::from(vec)) + } +} + +impl TryFrom<Parts> for Uri { + type Error = InvalidUriParts; + + #[inline] + fn try_from(src: Parts) -> Result<Self, Self::Error> { + Uri::from_parts(src) + } +} + +impl<'a> TryFrom<&'a Uri> for Uri { + type Error = crate::Error; + + #[inline] + fn try_from(src: &'a Uri) -> Result<Self, Self::Error> { + Ok(src.clone()) + } +} + +/// Convert an `Authority` into a `Uri`. +impl From<Authority> for Uri { + fn from(authority: Authority) -> Self { + Self { + scheme: Scheme::empty(), + authority, + path_and_query: PathAndQuery::empty(), + } + } +} + +/// Convert a `PathAndQuery` into a `Uri`. +impl From<PathAndQuery> for Uri { + fn from(path_and_query: PathAndQuery) -> Self { + Self { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query, + } + } +} + +/// Convert a `Uri` into `Parts` +impl From<Uri> for Parts { + fn from(src: Uri) -> Self { + let path_and_query = if src.has_path() { + Some(src.path_and_query) + } else { + None + }; + + let scheme = match src.scheme.inner { + Scheme2::None => None, + _ => Some(src.scheme), + }; + + let authority = if src.authority.data.is_empty() { + None + } else { + Some(src.authority) + }; + + Parts { + scheme: scheme, + authority: authority, + path_and_query: path_and_query, + _priv: (), + } + } +} + +fn parse_full(mut s: Bytes) -> Result<Uri, InvalidUri> { + // Parse the scheme + let scheme = match Scheme2::parse(&s[..])? { + Scheme2::None => Scheme2::None, + Scheme2::Standard(p) => { + // TODO: use truncate + let _ = s.split_to(p.len() + 3); + Scheme2::Standard(p) + } + Scheme2::Other(n) => { + // Grab the protocol + let mut scheme = s.split_to(n + 3); + + // Strip ://, TODO: truncate + let _ = scheme.split_off(n); + + // Allocate the ByteStr + let val = unsafe { ByteStr::from_utf8_unchecked(scheme) }; + + Scheme2::Other(Box::new(val)) + } + }; + + // Find the end of the authority. The scheme will already have been + // extracted. + let authority_end = Authority::parse(&s[..])?; + + if scheme.is_none() { + if authority_end != s.len() { + return Err(ErrorKind::InvalidFormat.into()); + } + + let authority = Authority { + data: unsafe { ByteStr::from_utf8_unchecked(s) }, + }; + + return Ok(Uri { + scheme: scheme.into(), + authority: authority, + path_and_query: PathAndQuery::empty(), + }); + } + + // Authority is required when absolute + if authority_end == 0 { + return Err(ErrorKind::InvalidFormat.into()); + } + + let authority = s.split_to(authority_end); + let authority = Authority { + data: unsafe { ByteStr::from_utf8_unchecked(authority) }, + }; + + Ok(Uri { + scheme: scheme.into(), + authority: authority, + path_and_query: PathAndQuery::from_shared(s)?, + }) +} + +impl FromStr for Uri { + type Err = InvalidUri; + + #[inline] + fn from_str(s: &str) -> Result<Uri, InvalidUri> { + Uri::try_from(s.as_bytes()) + } +} + +impl PartialEq for Uri { + fn eq(&self, other: &Uri) -> bool { + if self.scheme() != other.scheme() { + return false; + } + + if self.authority() != other.authority() { + return false; + } + + if self.path() != other.path() { + return false; + } + + if self.query() != other.query() { + return false; + } + + true + } +} + +impl PartialEq<str> for Uri { + fn eq(&self, other: &str) -> bool { + let mut other = other.as_bytes(); + let mut absolute = false; + + if let Some(scheme) = self.scheme() { + let scheme = scheme.as_str().as_bytes(); + absolute = true; + + if other.len() < scheme.len() + 3 { + return false; + } + + if !scheme.eq_ignore_ascii_case(&other[..scheme.len()]) { + return false; + } + + other = &other[scheme.len()..]; + + if &other[..3] != b"://" { + return false; + } + + other = &other[3..]; + } + + if let Some(auth) = self.authority() { + let len = auth.data.len(); + absolute = true; + + if other.len() < len { + return false; + } + + if !auth.data.as_bytes().eq_ignore_ascii_case(&other[..len]) { + return false; + } + + other = &other[len..]; + } + + let path = self.path(); + + if other.len() < path.len() || path.as_bytes() != &other[..path.len()] { + if absolute && path == "/" { + // PathAndQuery can be ommitted, fall through + } else { + return false; + } + } else { + other = &other[path.len()..]; + } + + if let Some(query) = self.query() { + if other.len() == 0 { + return query.len() == 0; + } + + if other[0] != b'?' { + return false; + } + + other = &other[1..]; + + if other.len() < query.len() { + return false; + } + + if query.as_bytes() != &other[..query.len()] { + return false; + } + + other = &other[query.len()..]; + } + + other.is_empty() || other[0] == b'#' + } +} + +impl PartialEq<Uri> for str { + fn eq(&self, uri: &Uri) -> bool { + uri == self + } +} + +impl<'a> PartialEq<&'a str> for Uri { + fn eq(&self, other: &&'a str) -> bool { + self == *other + } +} + +impl<'a> PartialEq<Uri> for &'a str { + fn eq(&self, uri: &Uri) -> bool { + uri == *self + } +} + +impl Eq for Uri {} + +/// Returns a `Uri` representing `/` +impl Default for Uri { + #[inline] + fn default() -> Uri { + Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::slash(), + } + } +} + +impl fmt::Display for Uri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(scheme) = self.scheme() { + write!(f, "{}://", scheme)?; + } + + if let Some(authority) = self.authority() { + write!(f, "{}", authority)?; + } + + write!(f, "{}", self.path())?; + + if let Some(query) = self.query() { + write!(f, "?{}", query)?; + } + + Ok(()) + } +} + +impl fmt::Debug for Uri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl From<ErrorKind> for InvalidUri { + fn from(src: ErrorKind) -> InvalidUri { + InvalidUri(src) + } +} + +impl From<ErrorKind> for InvalidUriParts { + fn from(src: ErrorKind) -> InvalidUriParts { + InvalidUriParts(src.into()) + } +} + +impl InvalidUri { + fn s(&self) -> &str { + match self.0 { + ErrorKind::InvalidUriChar => "invalid uri character", + ErrorKind::InvalidScheme => "invalid scheme", + ErrorKind::InvalidAuthority => "invalid authority", + ErrorKind::InvalidPort => "invalid port", + ErrorKind::InvalidFormat => "invalid format", + ErrorKind::SchemeMissing => "scheme missing", + ErrorKind::AuthorityMissing => "authority missing", + ErrorKind::PathAndQueryMissing => "path missing", + ErrorKind::TooLong => "uri too long", + ErrorKind::Empty => "empty string", + ErrorKind::SchemeTooLong => "scheme too long", + } + } +} + +impl fmt::Display for InvalidUri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.s().fmt(f) + } +} + +impl Error for InvalidUri {} + +impl fmt::Display for InvalidUriParts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Error for InvalidUriParts {} + +impl Hash for Uri { + fn hash<H>(&self, state: &mut H) + where + H: Hasher, + { + if !self.scheme.inner.is_none() { + self.scheme.hash(state); + state.write_u8(0xff); + } + + if let Some(auth) = self.authority() { + auth.hash(state); + } + + Hash::hash_slice(self.path().as_bytes(), state); + + if let Some(query) = self.query() { + b'?'.hash(state); + Hash::hash_slice(query.as_bytes(), state); + } + } +} diff --git a/third_party/rust/http/src/uri/path.rs b/third_party/rust/http/src/uri/path.rs new file mode 100644 index 0000000000..be2cb65c1b --- /dev/null +++ b/third_party/rust/http/src/uri/path.rs @@ -0,0 +1,564 @@ +use std::convert::TryFrom; +use std::str::FromStr; +use std::{cmp, fmt, hash, str}; + +use bytes::Bytes; + +use super::{ErrorKind, InvalidUri}; +use crate::byte_str::ByteStr; + +/// Represents the path component of a URI +#[derive(Clone)] +pub struct PathAndQuery { + pub(super) data: ByteStr, + pub(super) query: u16, +} + +const NONE: u16 = ::std::u16::MAX; + +impl PathAndQuery { + // Not public while `bytes` is unstable. + pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> { + let mut query = NONE; + let mut fragment = None; + + // block for iterator borrow + { + let mut iter = src.as_ref().iter().enumerate(); + + // path ... + for (i, &b) in &mut iter { + // See https://url.spec.whatwg.org/#path-state + match b { + b'?' => { + debug_assert_eq!(query, NONE); + query = i as u16; + break; + } + b'#' => { + fragment = Some(i); + break; + } + + // This is the range of bytes that don't need to be + // percent-encoded in the path. If it should have been + // percent-encoded, then error. + 0x21 | + 0x24..=0x3B | + 0x3D | + 0x40..=0x5F | + 0x61..=0x7A | + 0x7C | + 0x7E => {}, + + // These are code points that are supposed to be + // percent-encoded in the path but there are clients + // out there sending them as is and httparse accepts + // to parse those requests, so they are allowed here + // for parity. + // + // For reference, those are code points that are used + // to send requests with JSON directly embedded in + // the URI path. Yes, those things happen for real. + b'"' | + b'{' | b'}' => {}, + + _ => return Err(ErrorKind::InvalidUriChar.into()), + } + } + + // query ... + if query != NONE { + for (i, &b) in iter { + match b { + // While queries *should* be percent-encoded, most + // bytes are actually allowed... + // See https://url.spec.whatwg.org/#query-state + // + // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E + 0x21 | + 0x24..=0x3B | + 0x3D | + 0x3F..=0x7E => {}, + + b'#' => { + fragment = Some(i); + break; + } + + _ => return Err(ErrorKind::InvalidUriChar.into()), + } + } + } + } + + if let Some(i) = fragment { + src.truncate(i); + } + + Ok(PathAndQuery { + data: unsafe { ByteStr::from_utf8_unchecked(src) }, + query: query, + }) + } + + /// Convert a `PathAndQuery` from a static string. + /// + /// This function will not perform any copying, however the string is + /// checked to ensure that it is valid. + /// + /// # Panics + /// + /// This function panics if the argument is an invalid path and query. + /// + /// # Examples + /// + /// ``` + /// # use http::uri::*; + /// let v = PathAndQuery::from_static("/hello?world"); + /// + /// assert_eq!(v.path(), "/hello"); + /// assert_eq!(v.query(), Some("world")); + /// ``` + #[inline] + pub fn from_static(src: &'static str) -> Self { + let src = Bytes::from_static(src.as_bytes()); + + PathAndQuery::from_shared(src).unwrap() + } + + /// Attempt to convert a `Bytes` buffer to a `PathAndQuery`. + /// + /// 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<T>(src: T) -> Result<Self, InvalidUri> + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return PathAndQuery::from_shared(src); + }); + + PathAndQuery::try_from(src.as_ref()) + } + + pub(super) fn empty() -> Self { + PathAndQuery { + data: ByteStr::new(), + query: NONE, + } + } + + pub(super) fn slash() -> Self { + PathAndQuery { + data: ByteStr::from_static("/"), + query: NONE, + } + } + + pub(super) fn star() -> Self { + PathAndQuery { + data: ByteStr::from_static("*"), + query: NONE, + } + } + + /// Returns the path component + /// + /// The path component is **case sensitive**. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |--------| + /// | + /// path + /// ``` + /// + /// If the URI is `*` then the path component is equal to `*`. + /// + /// # Examples + /// + /// ``` + /// # use http::uri::*; + /// + /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap(); + /// + /// assert_eq!(path_and_query.path(), "/hello/world"); + /// ``` + #[inline] + pub fn path(&self) -> &str { + let ret = if self.query == NONE { + &self.data[..] + } else { + &self.data[..self.query as usize] + }; + + if ret.is_empty() { + return "/"; + } + + ret + } + + /// Returns the query string component + /// + /// The query component contains non-hierarchical data that, along with data + /// in the path component, serves to identify a resource within the scope of + /// the URI's scheme and naming authority (if any). The query component is + /// indicated by the first question mark ("?") character and terminated by a + /// number sign ("#") character or by the end of the URI. + /// + /// ```notrust + /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 + /// |-------------------| + /// | + /// query + /// ``` + /// + /// # Examples + /// + /// With a query string component + /// + /// ``` + /// # use http::uri::*; + /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap(); + /// + /// assert_eq!(path_and_query.query(), Some("key=value&foo=bar")); + /// ``` + /// + /// Without a query string component + /// + /// ``` + /// # use http::uri::*; + /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap(); + /// + /// assert!(path_and_query.query().is_none()); + /// ``` + #[inline] + pub fn query(&self) -> Option<&str> { + if self.query == NONE { + None + } else { + let i = self.query + 1; + Some(&self.data[i as usize..]) + } + } + + /// Returns the path and query as a string component. + /// + /// # Examples + /// + /// With a query string component + /// + /// ``` + /// # use http::uri::*; + /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap(); + /// + /// assert_eq!(path_and_query.as_str(), "/hello/world?key=value&foo=bar"); + /// ``` + /// + /// Without a query string component + /// + /// ``` + /// # use http::uri::*; + /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap(); + /// + /// assert_eq!(path_and_query.as_str(), "/hello/world"); + /// ``` + #[inline] + pub fn as_str(&self) -> &str { + let ret = &self.data[..]; + if ret.is_empty() { + return "/"; + } + ret + } +} + +impl<'a> TryFrom<&'a [u8]> for PathAndQuery { + type Error = InvalidUri; + #[inline] + fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> { + PathAndQuery::from_shared(Bytes::copy_from_slice(s)) + } +} + +impl<'a> TryFrom<&'a str> for PathAndQuery { + type Error = InvalidUri; + #[inline] + fn try_from(s: &'a str) -> Result<Self, Self::Error> { + TryFrom::try_from(s.as_bytes()) + } +} + +impl<'a> TryFrom<Vec<u8>> for PathAndQuery { + type Error = InvalidUri; + #[inline] + fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { + PathAndQuery::from_shared(vec.into()) + } +} + +impl TryFrom<String> for PathAndQuery { + type Error = InvalidUri; + #[inline] + fn try_from(s: String) -> Result<Self, Self::Error> { + PathAndQuery::from_shared(s.into()) + } +} + +impl TryFrom<&String> for PathAndQuery { + type Error = InvalidUri; + #[inline] + fn try_from(s: &String) -> Result<Self, Self::Error> { + TryFrom::try_from(s.as_bytes()) + } +} + +impl FromStr for PathAndQuery { + type Err = InvalidUri; + #[inline] + fn from_str(s: &str) -> Result<Self, InvalidUri> { + TryFrom::try_from(s) + } +} + +impl fmt::Debug for PathAndQuery { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for PathAndQuery { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.data.is_empty() { + match self.data.as_bytes()[0] { + b'/' | b'*' => write!(fmt, "{}", &self.data[..]), + _ => write!(fmt, "/{}", &self.data[..]), + } + } else { + write!(fmt, "/") + } + } +} + +impl hash::Hash for PathAndQuery { + fn hash<H: hash::Hasher>(&self, state: &mut H) { + self.data.hash(state); + } +} + +// ===== PartialEq / PartialOrd ===== + +impl PartialEq for PathAndQuery { + #[inline] + fn eq(&self, other: &PathAndQuery) -> bool { + self.data == other.data + } +} + +impl Eq for PathAndQuery {} + +impl PartialEq<str> for PathAndQuery { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl<'a> PartialEq<PathAndQuery> for &'a str { + #[inline] + fn eq(&self, other: &PathAndQuery) -> bool { + self == &other.as_str() + } +} + +impl<'a> PartialEq<&'a str> for PathAndQuery { + #[inline] + fn eq(&self, other: &&'a str) -> bool { + self.as_str() == *other + } +} + +impl PartialEq<PathAndQuery> for str { + #[inline] + fn eq(&self, other: &PathAndQuery) -> bool { + self == other.as_str() + } +} + +impl PartialEq<String> for PathAndQuery { + #[inline] + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq<PathAndQuery> for String { + #[inline] + fn eq(&self, other: &PathAndQuery) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialOrd for PathAndQuery { + #[inline] + fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl PartialOrd<str> for PathAndQuery { + #[inline] + fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { + self.as_str().partial_cmp(other) + } +} + +impl PartialOrd<PathAndQuery> for str { + #[inline] + fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { + self.partial_cmp(other.as_str()) + } +} + +impl<'a> PartialOrd<&'a str> for PathAndQuery { + #[inline] + fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> { + self.as_str().partial_cmp(*other) + } +} + +impl<'a> PartialOrd<PathAndQuery> for &'a str { + #[inline] + fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { + self.partial_cmp(&other.as_str()) + } +} + +impl PartialOrd<String> for PathAndQuery { + #[inline] + fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl PartialOrd<PathAndQuery> for String { + #[inline] + fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { + self.as_str().partial_cmp(other.as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn equal_to_self_of_same_path() { + let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + assert_eq!(p1, p2); + assert_eq!(p2, p1); + } + + #[test] + fn not_equal_to_self_of_different_path() { + let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + let p2: PathAndQuery = "/world&foo=bar".parse().unwrap(); + assert_ne!(p1, p2); + assert_ne!(p2, p1); + } + + #[test] + fn equates_with_a_str() { + let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + assert_eq!(&path_and_query, "/hello/world&foo=bar"); + assert_eq!("/hello/world&foo=bar", &path_and_query); + assert_eq!(path_and_query, "/hello/world&foo=bar"); + assert_eq!("/hello/world&foo=bar", path_and_query); + } + + #[test] + fn not_equal_with_a_str_of_a_different_path() { + let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + // as a reference + assert_ne!(&path_and_query, "/hello&foo=bar"); + assert_ne!("/hello&foo=bar", &path_and_query); + // without reference + assert_ne!(path_and_query, "/hello&foo=bar"); + assert_ne!("/hello&foo=bar", path_and_query); + } + + #[test] + fn equates_with_a_string() { + let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + assert_eq!(path_and_query, "/hello/world&foo=bar".to_string()); + assert_eq!("/hello/world&foo=bar".to_string(), path_and_query); + } + + #[test] + fn not_equal_with_a_string_of_a_different_path() { + let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap(); + assert_ne!(path_and_query, "/hello&foo=bar".to_string()); + assert_ne!("/hello&foo=bar".to_string(), path_and_query); + } + + #[test] + fn compares_to_self() { + let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap(); + let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap(); + assert!(p1 < p2); + assert!(p2 > p1); + } + + #[test] + fn compares_with_a_str() { + let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap(); + // by ref + assert!(&path_and_query < "/c/world&foo=bar"); + assert!("/c/world&foo=bar" > &path_and_query); + assert!(&path_and_query > "/a/world&foo=bar"); + assert!("/a/world&foo=bar" < &path_and_query); + + // by val + assert!(path_and_query < "/c/world&foo=bar"); + assert!("/c/world&foo=bar" > path_and_query); + assert!(path_and_query > "/a/world&foo=bar"); + assert!("/a/world&foo=bar" < path_and_query); + } + + #[test] + fn compares_with_a_string() { + let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap(); + assert!(path_and_query < "/c/world&foo=bar".to_string()); + assert!("/c/world&foo=bar".to_string() > path_and_query); + assert!(path_and_query > "/a/world&foo=bar".to_string()); + assert!("/a/world&foo=bar".to_string() < path_and_query); + } + + #[test] + fn ignores_valid_percent_encodings() { + assert_eq!("/a%20b", pq("/a%20b?r=1").path()); + assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap()); + } + + #[test] + fn ignores_invalid_percent_encodings() { + assert_eq!("/a%%b", pq("/a%%b?r=1").path()); + assert_eq!("/aaa%", pq("/aaa%").path()); + assert_eq!("/aaa%", pq("/aaa%?r=1").path()); + assert_eq!("/aa%2", pq("/aa%2").path()); + assert_eq!("/aa%2", pq("/aa%2?r=1").path()); + assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap()); + } + + #[test] + fn json_is_fine() { + assert_eq!(r#"/{"bread":"baguette"}"#, pq(r#"/{"bread":"baguette"}"#).path()); + } + + fn pq(s: &str) -> PathAndQuery { + s.parse().expect(&format!("parsing {}", s)) + } +} diff --git a/third_party/rust/http/src/uri/port.rs b/third_party/rust/http/src/uri/port.rs new file mode 100644 index 0000000000..8f5c5f3f7d --- /dev/null +++ b/third_party/rust/http/src/uri/port.rs @@ -0,0 +1,151 @@ +use std::fmt; + +use super::{ErrorKind, InvalidUri}; + +/// The port component of a URI. +pub struct Port<T> { + port: u16, + repr: T, +} + +impl<T> Port<T> { + /// Returns the port number as a `u16`. + /// + /// # Examples + /// + /// Port as `u16`. + /// + /// ``` + /// # use http::uri::Authority; + /// let authority: Authority = "example.org:80".parse().unwrap(); + /// + /// let port = authority.port().unwrap(); + /// assert_eq!(port.as_u16(), 80); + /// ``` + pub fn as_u16(&self) -> u16 { + self.port + } +} + +impl<T> Port<T> +where + T: AsRef<str>, +{ + /// Converts a `str` to a port number. + /// + /// The supplied `str` must be a valid u16. + pub(crate) fn from_str(bytes: T) -> Result<Self, InvalidUri> { + bytes + .as_ref() + .parse::<u16>() + .map(|port| Port { port, repr: bytes }) + .map_err(|_| ErrorKind::InvalidPort.into()) + } + + /// Returns the port number as a `str`. + /// + /// # Examples + /// + /// Port as `str`. + /// + /// ``` + /// # use http::uri::Authority; + /// let authority: Authority = "example.org:80".parse().unwrap(); + /// + /// let port = authority.port().unwrap(); + /// assert_eq!(port.as_str(), "80"); + /// ``` + pub fn as_str(&self) -> &str { + self.repr.as_ref() + } +} + +impl<T> fmt::Debug for Port<T> +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Port").field(&self.port).finish() + } +} + +impl<T> fmt::Display for Port<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Use `u16::fmt` so that it respects any formatting flags that + // may have been set (like padding, align, etc). + fmt::Display::fmt(&self.port, f) + } +} + +impl<T> From<Port<T>> for u16 { + fn from(port: Port<T>) -> Self { + port.as_u16() + } +} + +impl<T> AsRef<str> for Port<T> +where + T: AsRef<str>, +{ + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<T, U> PartialEq<Port<U>> for Port<T> { + fn eq(&self, other: &Port<U>) -> bool { + self.port == other.port + } +} + +impl<T> PartialEq<u16> for Port<T> { + fn eq(&self, other: &u16) -> bool { + self.port == *other + } +} + +impl<T> PartialEq<Port<T>> for u16 { + fn eq(&self, other: &Port<T>) -> bool { + other.port == *self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn partialeq_port() { + let port_a = Port::from_str("8080").unwrap(); + let port_b = Port::from_str("8080").unwrap(); + assert_eq!(port_a, port_b); + } + + #[test] + fn partialeq_port_different_reprs() { + let port_a = Port { + repr: "8081", + port: 8081, + }; + let port_b = Port { + repr: String::from("8081"), + port: 8081, + }; + assert_eq!(port_a, port_b); + assert_eq!(port_b, port_a); + } + + #[test] + fn partialeq_u16() { + let port = Port::from_str("8080").unwrap(); + // test equals in both directions + assert_eq!(port, 8080); + assert_eq!(8080, port); + } + + #[test] + fn u16_from_port() { + let port = Port::from_str("8080").unwrap(); + assert_eq!(8080, u16::from(port)); + } +} diff --git a/third_party/rust/http/src/uri/scheme.rs b/third_party/rust/http/src/uri/scheme.rs new file mode 100644 index 0000000000..682b11eeea --- /dev/null +++ b/third_party/rust/http/src/uri/scheme.rs @@ -0,0 +1,363 @@ +use std::convert::TryFrom; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; + +use bytes::Bytes; + +use super::{ErrorKind, InvalidUri}; +use crate::byte_str::ByteStr; + +/// Represents the scheme component of a URI +#[derive(Clone)] +pub struct Scheme { + pub(super) inner: Scheme2, +} + +#[derive(Clone, Debug)] +pub(super) enum Scheme2<T = Box<ByteStr>> { + None, + Standard(Protocol), + Other(T), +} + +#[derive(Copy, Clone, Debug)] +pub(super) enum Protocol { + Http, + Https, +} + +impl Scheme { + /// HTTP protocol scheme + pub const HTTP: Scheme = Scheme { + inner: Scheme2::Standard(Protocol::Http), + }; + + /// HTTP protocol over TLS. + pub const HTTPS: Scheme = Scheme { + inner: Scheme2::Standard(Protocol::Https), + }; + + pub(super) fn empty() -> Self { + Scheme { + inner: Scheme2::None, + } + } + + /// Return a str representation of the scheme + /// + /// # Examples + /// + /// ``` + /// # use http::uri::*; + /// let scheme: Scheme = "http".parse().unwrap(); + /// assert_eq!(scheme.as_str(), "http"); + /// ``` + #[inline] + pub fn as_str(&self) -> &str { + use self::Protocol::*; + use self::Scheme2::*; + + match self.inner { + Standard(Http) => "http", + Standard(Https) => "https", + Other(ref v) => &v[..], + None => unreachable!(), + } + } +} + +impl<'a> TryFrom<&'a [u8]> for Scheme { + type Error = InvalidUri; + #[inline] + fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> { + use self::Scheme2::*; + + match Scheme2::parse_exact(s)? { + None => Err(ErrorKind::InvalidScheme.into()), + Standard(p) => Ok(Standard(p).into()), + Other(_) => { + let bytes = Bytes::copy_from_slice(s); + + // Safety: postcondition on parse_exact() means that s and + // hence bytes are valid UTF-8. + let string = unsafe { ByteStr::from_utf8_unchecked(bytes) }; + + Ok(Other(Box::new(string)).into()) + } + } + } +} + +impl<'a> TryFrom<&'a str> for Scheme { + type Error = InvalidUri; + #[inline] + fn try_from(s: &'a str) -> Result<Self, Self::Error> { + TryFrom::try_from(s.as_bytes()) + } +} + +impl FromStr for Scheme { + type Err = InvalidUri; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + TryFrom::try_from(s) + } +} + +impl fmt::Debug for Scheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_str(), f) + } +} + +impl fmt::Display for Scheme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl AsRef<str> for Scheme { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl PartialEq for Scheme { + fn eq(&self, other: &Scheme) -> bool { + use self::Protocol::*; + use self::Scheme2::*; + + match (&self.inner, &other.inner) { + (&Standard(Http), &Standard(Http)) => true, + (&Standard(Https), &Standard(Https)) => true, + (&Other(ref a), &Other(ref b)) => a.eq_ignore_ascii_case(b), + (&None, _) | (_, &None) => unreachable!(), + _ => false, + } + } +} + +impl Eq for Scheme {} + +/// Case-insensitive equality +/// +/// # Examples +/// +/// ``` +/// # use http::uri::Scheme; +/// let scheme: Scheme = "HTTP".parse().unwrap(); +/// assert_eq!(scheme, *"http"); +/// ``` +impl PartialEq<str> for Scheme { + fn eq(&self, other: &str) -> bool { + self.as_str().eq_ignore_ascii_case(other) + } +} + +/// Case-insensitive equality +impl PartialEq<Scheme> for str { + fn eq(&self, other: &Scheme) -> bool { + other == self + } +} + +/// Case-insensitive hashing +impl Hash for Scheme { + fn hash<H>(&self, state: &mut H) + where + H: Hasher, + { + match self.inner { + Scheme2::None => (), + Scheme2::Standard(Protocol::Http) => state.write_u8(1), + Scheme2::Standard(Protocol::Https) => state.write_u8(2), + Scheme2::Other(ref other) => { + other.len().hash(state); + for &b in other.as_bytes() { + state.write_u8(b.to_ascii_lowercase()); + } + } + } + } +} + +impl<T> Scheme2<T> { + pub(super) fn is_none(&self) -> bool { + match *self { + Scheme2::None => true, + _ => false, + } + } +} + +// Require the scheme to not be too long in order to enable further +// optimizations later. +const MAX_SCHEME_LEN: usize = 64; + +// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +// +// SCHEME_CHARS is a table of valid characters in the scheme part of a URI. An +// entry in the table is 0 for invalid characters. For valid characters the +// entry is itself (i.e. the entry for 43 is b'+' because b'+' == 43u8). An +// important characteristic of this table is that all entries above 127 are +// invalid. This makes all of the valid entries a valid single-byte UTF-8 code +// point. This means that a slice of such valid entries is valid UTF-8. +const SCHEME_CHARS: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3x + 0, 0, 0, b'+', 0, b'-', b'.', 0, b'0', b'1', // 4x + b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', 0, // 5x + 0, 0, 0, 0, 0, b'A', b'B', b'C', b'D', b'E', // 6x + b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x + b'Z', 0, 0, 0, 0, 0, 0, b'a', b'b', b'c', // 9x + b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x + b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x + b'x', b'y', b'z', 0, 0, 0, b'~', 0, 0, 0, // 12x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 13x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 14x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 15x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 17x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 18x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 19x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 21x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 22x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 23x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 24x + 0, 0, 0, 0, 0, 0 // 25x +]; + +impl Scheme2<usize> { + // Postcondition: On all Ok() returns, s is valid UTF-8 + fn parse_exact(s: &[u8]) -> Result<Scheme2<()>, InvalidUri> { + match s { + b"http" => Ok(Protocol::Http.into()), + b"https" => Ok(Protocol::Https.into()), + _ => { + if s.len() > MAX_SCHEME_LEN { + return Err(ErrorKind::SchemeTooLong.into()); + } + + // check that each byte in s is a SCHEME_CHARS which implies + // that it is a valid single byte UTF-8 code point. + for &b in s { + match SCHEME_CHARS[b as usize] { + b':' => { + // Don't want :// here + return Err(ErrorKind::InvalidScheme.into()); + } + 0 => { + return Err(ErrorKind::InvalidScheme.into()); + } + _ => {} + } + } + + Ok(Scheme2::Other(())) + } + } + } + + pub(super) fn parse(s: &[u8]) -> Result<Scheme2<usize>, InvalidUri> { + if s.len() >= 7 { + // Check for HTTP + if s[..7].eq_ignore_ascii_case(b"http://") { + // Prefix will be striped + return Ok(Protocol::Http.into()); + } + } + + if s.len() >= 8 { + // Check for HTTPs + if s[..8].eq_ignore_ascii_case(b"https://") { + return Ok(Protocol::Https.into()); + } + } + + if s.len() > 3 { + for i in 0..s.len() { + let b = s[i]; + + match SCHEME_CHARS[b as usize] { + b':' => { + // Not enough data remaining + if s.len() < i + 3 { + break; + } + + // Not a scheme + if &s[i + 1..i + 3] != b"//" { + break; + } + + if i > MAX_SCHEME_LEN { + return Err(ErrorKind::SchemeTooLong.into()); + } + + // Return scheme + return Ok(Scheme2::Other(i)); + } + // Invald scheme character, abort + 0 => break, + _ => {} + } + } + } + + Ok(Scheme2::None) + } +} + +impl Protocol { + pub(super) fn len(&self) -> usize { + match *self { + Protocol::Http => 4, + Protocol::Https => 5, + } + } +} + +impl<T> From<Protocol> for Scheme2<T> { + fn from(src: Protocol) -> Self { + Scheme2::Standard(src) + } +} + +#[doc(hidden)] +impl From<Scheme2> for Scheme { + fn from(src: Scheme2) -> Self { + Scheme { inner: src } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn scheme_eq_to_str() { + assert_eq!(&scheme("http"), "http"); + assert_eq!(&scheme("https"), "https"); + assert_eq!(&scheme("ftp"), "ftp"); + assert_eq!(&scheme("my+funky+scheme"), "my+funky+scheme"); + } + + #[test] + fn invalid_scheme_is_error() { + Scheme::try_from("my_funky_scheme").expect_err("Unexpectly valid Scheme"); + + // Invalid UTF-8 + Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectly valid Scheme"); + } + + fn scheme(s: &str) -> Scheme { + s.parse().expect(&format!("Invalid scheme: {}", s)) + } +} diff --git a/third_party/rust/http/src/uri/tests.rs b/third_party/rust/http/src/uri/tests.rs new file mode 100644 index 0000000000..719cb94ee3 --- /dev/null +++ b/third_party/rust/http/src/uri/tests.rs @@ -0,0 +1,519 @@ +use std::str::FromStr; + +use super::{ErrorKind, InvalidUri, Port, Uri, URI_CHARS}; + +#[test] +fn test_char_table() { + for (i, &v) in URI_CHARS.iter().enumerate() { + if v != 0 { + assert_eq!(i, v as usize); + } + } +} + +macro_rules! part { + ($s:expr) => { + Some(&$s.parse().unwrap()) + }; +} + +macro_rules! test_parse { + ( + $test_name:ident, + $str:expr, + $alt:expr, + $($method:ident = $value:expr,)* + ) => ( + #[test] + fn $test_name() { + let orig_str = $str; + let uri = match Uri::from_str(orig_str) { + Ok(uri) => uri, + Err(err) => { + panic!("parse error {:?} from {:?}", err, orig_str); + }, + }; + $( + assert_eq!(uri.$method(), $value, "{}: uri = {:?}", stringify!($method), uri); + )+ + assert_eq!(uri, orig_str, "partial eq to original str"); + assert_eq!(uri, uri.clone(), "clones are equal"); + + let new_str = uri.to_string(); + let new_uri = Uri::from_str(&new_str).expect("to_string output parses again as a Uri"); + assert_eq!(new_uri, orig_str, "round trip still equals original str"); + + const ALT: &'static [&'static str] = &$alt; + + for &alt in ALT.iter() { + let other: Uri = alt.parse().unwrap(); + assert_eq!(uri, *alt); + assert_eq!(uri, other); + } + } + ); +} + +test_parse! { + test_uri_parse_path_and_query, + "/some/path/here?and=then&hello#and-bye", + [], + + scheme = None, + authority = None, + path = "/some/path/here", + query = Some("and=then&hello"), + host = None, +} + +test_parse! { + test_uri_parse_absolute_form, + "http://127.0.0.1:61761/chunks", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1:61761"), + path = "/chunks", + query = None, + host = Some("127.0.0.1"), + port = Port::from_str("61761").ok(), +} + +test_parse! { + test_uri_parse_absolute_form_without_path, + "https://127.0.0.1:61761", + ["https://127.0.0.1:61761/"], + + scheme = part!("https"), + authority = part!("127.0.0.1:61761"), + path = "/", + query = None, + host = Some("127.0.0.1"), + port = Port::from_str("61761").ok(), +} + +test_parse! { + test_uri_parse_asterisk_form, + "*", + [], + + scheme = None, + authority = None, + path = "*", + query = None, + host = None, +} + +test_parse! { + test_uri_parse_authority_no_port, + "localhost", + ["LOCALHOST", "LocaLHOSt"], + + scheme = None, + authority = part!("localhost"), + path = "", + query = None, + port = None, + host = Some("localhost"), +} + +test_parse! { + test_uri_authority_only_one_character_issue_197, + "S", + [], + + scheme = None, + authority = part!("S"), + path = "", + query = None, + port = None, + host = Some("S"), +} + +test_parse! { + test_uri_parse_authority_form, + "localhost:3000", + ["localhosT:3000"], + + scheme = None, + authority = part!("localhost:3000"), + path = "", + query = None, + host = Some("localhost"), + port = Port::from_str("3000").ok(), +} + +test_parse! { + test_uri_parse_absolute_with_default_port_http, + "http://127.0.0.1:80", + ["http://127.0.0.1:80/"], + + scheme = part!("http"), + authority = part!("127.0.0.1:80"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = Port::from_str("80").ok(), +} + +test_parse! { + test_uri_parse_absolute_with_default_port_https, + "https://127.0.0.1:443", + ["https://127.0.0.1:443/"], + + scheme = part!("https"), + authority = part!("127.0.0.1:443"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = Port::from_str("443").ok(), +} + +test_parse! { + test_uri_parse_fragment_questionmark, + "http://127.0.0.1/#?", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_uri_parse_path_with_terminating_questionmark, + "http://127.0.0.1/path?", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1"), + path = "/path", + query = Some(""), + port = None, +} + +test_parse! { + test_uri_parse_absolute_form_with_empty_path_and_nonempty_query, + "http://127.0.0.1?foo=bar", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1"), + path = "/", + query = Some("foo=bar"), + port = None, +} + +test_parse! { + test_uri_parse_absolute_form_with_empty_path_and_fragment_with_slash, + "http://127.0.0.1#foo/bar", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_uri_parse_absolute_form_with_empty_path_and_fragment_with_questionmark, + "http://127.0.0.1#foo?bar", + [], + + scheme = part!("http"), + authority = part!("127.0.0.1"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_uri_parse_long_host_with_no_scheme, + "thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost", + [], + + scheme = None, + authority = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost"), + path = "", + query = None, + port = None, +} + +test_parse! { + test_uri_parse_long_host_with_port_and_no_scheme, + "thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost:1234", + [], + + scheme = None, + authority = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost:1234"), + path = "", + query = None, + port = Port::from_str("1234").ok(), +} + +test_parse! { + test_userinfo1, + "http://a:b@127.0.0.1:1234/", + [], + + scheme = part!("http"), + authority = part!("a:b@127.0.0.1:1234"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = Port::from_str("1234").ok(), +} + +test_parse! { + test_userinfo2, + "http://a:b@127.0.0.1/", + [], + + scheme = part!("http"), + authority = part!("a:b@127.0.0.1"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_userinfo3, + "http://a@127.0.0.1/", + [], + + scheme = part!("http"), + authority = part!("a@127.0.0.1"), + host = Some("127.0.0.1"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_userinfo_with_port, + "user@localhost:3000", + [], + + scheme = None, + authority = part!("user@localhost:3000"), + path = "", + query = None, + host = Some("localhost"), + port = Port::from_str("3000").ok(), +} + +test_parse! { + test_userinfo_pass_with_port, + "user:pass@localhost:3000", + [], + + scheme = None, + authority = part!("user:pass@localhost:3000"), + path = "", + query = None, + host = Some("localhost"), + port = Port::from_str("3000").ok(), +} + +test_parse! { + test_ipv6, + "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/", + [], + + scheme = part!("http"), + authority = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), + host = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_ipv6_shorthand, + "http://[::1]/", + [], + + scheme = part!("http"), + authority = part!("[::1]"), + host = Some("[::1]"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_ipv6_shorthand2, + "http://[::]/", + [], + + scheme = part!("http"), + authority = part!("[::]"), + host = Some("[::]"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_ipv6_shorthand3, + "http://[2001:db8::2:1]/", + [], + + scheme = part!("http"), + authority = part!("[2001:db8::2:1]"), + host = Some("[2001:db8::2:1]"), + path = "/", + query = None, + port = None, +} + +test_parse! { + test_ipv6_with_port, + "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008/", + [], + + scheme = part!("http"), + authority = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008"), + host = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), + path = "/", + query = None, + port = Port::from_str("8008").ok(), +} + +test_parse! { + test_percentage_encoded_path, + "/echo/abcdefgh_i-j%20/abcdefg_i-j%20478", + [], + + scheme = None, + authority = None, + host = None, + path = "/echo/abcdefgh_i-j%20/abcdefg_i-j%20478", + query = None, + port = None, +} + +test_parse! { + test_path_permissive, + "/foo=bar|baz\\^~%", + [], + + path = "/foo=bar|baz\\^~%", +} + +test_parse! { + test_query_permissive, + "/?foo={bar|baz}\\^`", + [], + + query = Some("foo={bar|baz}\\^`"), +} + +#[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"); + err("\0"); + err("http://[::1"); + err("http://::1]"); + err("localhost:8080:3030"); + err("@"); + err("http://username:password@/wut"); + + // illegal queries + err("/?foo\rbar"); + err("/?foo\nbar"); + err("/?<"); + err("/?>"); +} + +#[test] +fn test_max_uri_len() { + let mut uri = vec![]; + uri.extend(b"http://localhost/"); + uri.extend(vec![b'a'; 70 * 1024]); + + let uri = String::from_utf8(uri).unwrap(); + let res: Result<Uri, InvalidUri> = uri.parse(); + + assert_eq!(res.unwrap_err().0, ErrorKind::TooLong); +} + +#[test] +fn test_overflowing_scheme() { + let mut uri = vec![]; + uri.extend(vec![b'a'; 256]); + uri.extend(b"://localhost/"); + + let uri = String::from_utf8(uri).unwrap(); + let res: Result<Uri, InvalidUri> = uri.parse(); + + assert_eq!(res.unwrap_err().0, ErrorKind::SchemeTooLong); +} + +#[test] +fn test_max_length_scheme() { + let mut uri = vec![]; + uri.extend(vec![b'a'; 64]); + uri.extend(b"://localhost/"); + + let uri = String::from_utf8(uri).unwrap(); + let uri: Uri = uri.parse().unwrap(); + + assert_eq!(uri.scheme_str().unwrap().len(), 64); +} + +#[test] +fn test_uri_to_path_and_query() { + 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(); + let s = uri.path_and_query().unwrap().to_string(); + + assert_eq!(s, case.1); + } +} + +#[test] +fn test_authority_uri_parts_round_trip() { + let s = "hyper.rs"; + let uri = Uri::from_str(s).expect("first parse"); + assert_eq!(uri, s); + assert_eq!(uri.to_string(), s); + + let parts = uri.into_parts(); + let uri2 = Uri::from_parts(parts).expect("from_parts"); + assert_eq!(uri2, s); + assert_eq!(uri2.to_string(), s); +} + +#[test] +fn test_partial_eq_path_with_terminating_questionmark() { + let a = "/path"; + let uri = Uri::from_str("/path?").expect("first parse"); + + assert_eq!(uri, a); +} |