diff options
Diffstat (limited to 'vendor/hyper/src/ext')
| -rw-r--r-- | vendor/hyper/src/ext/h1_reason_phrase.rs | 221 | ||||
| -rw-r--r-- | vendor/hyper/src/ext/informational.rs | 86 | ||||
| -rw-r--r-- | vendor/hyper/src/ext/mod.rs | 246 |
3 files changed, 553 insertions, 0 deletions
diff --git a/vendor/hyper/src/ext/h1_reason_phrase.rs b/vendor/hyper/src/ext/h1_reason_phrase.rs new file mode 100644 index 00000000..adb43636 --- /dev/null +++ b/vendor/hyper/src/ext/h1_reason_phrase.rs @@ -0,0 +1,221 @@ +use bytes::Bytes; + +/// A reason phrase in an HTTP/1 response. +/// +/// # Clients +/// +/// For clients, a `ReasonPhrase` will be present in the extensions of the `http::Response` returned +/// for a request if the reason phrase is different from the canonical reason phrase for the +/// response's status code. For example, if a server returns `HTTP/1.1 200 Awesome`, the +/// `ReasonPhrase` will be present and contain `Awesome`, but if a server returns `HTTP/1.1 200 OK`, +/// the response will not contain a `ReasonPhrase`. +/// +/// ```no_run +/// # #[cfg(all(feature = "tcp", feature = "client", feature = "http1"))] +/// # async fn fake_fetch() -> hyper::Result<()> { +/// use hyper::{Client, Uri}; +/// use hyper::ext::ReasonPhrase; +/// +/// let res = Client::new().get(Uri::from_static("http://example.com/non_canonical_reason")).await?; +/// +/// // Print out the non-canonical reason phrase, if it has one... +/// if let Some(reason) = res.extensions().get::<ReasonPhrase>() { +/// println!("non-canonical reason: {}", std::str::from_utf8(reason.as_bytes()).unwrap()); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// # Servers +/// +/// When a `ReasonPhrase` is present in the extensions of the `http::Response` written by a server, +/// its contents will be written in place of the canonical reason phrase when responding via HTTP/1. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ReasonPhrase(Bytes); + +impl ReasonPhrase { + /// Gets the reason phrase as bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Converts a static byte slice to a reason phrase. + pub const fn from_static(reason: &'static [u8]) -> Self { + // TODO: this can be made const once MSRV is >= 1.57.0 + if find_invalid_byte(reason).is_some() { + panic!("invalid byte in static reason phrase"); + } + Self(Bytes::from_static(reason)) + } + + // Not public on purpose. + /// Converts a `Bytes` directly into a `ReasonPhrase` without validating. + /// + /// Use with care; invalid bytes in a reason phrase can cause serious security problems if + /// emitted in a response. + #[cfg(feature = "client")] + pub(crate) fn from_bytes_unchecked(reason: Bytes) -> Self { + Self(reason) + } +} + +impl TryFrom<&[u8]> for ReasonPhrase { + type Error = InvalidReasonPhrase; + + fn try_from(reason: &[u8]) -> Result<Self, Self::Error> { + if let Some(bad_byte) = find_invalid_byte(reason) { + Err(InvalidReasonPhrase { bad_byte }) + } else { + Ok(Self(Bytes::copy_from_slice(reason))) + } + } +} + +impl TryFrom<Vec<u8>> for ReasonPhrase { + type Error = InvalidReasonPhrase; + + fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> { + if let Some(bad_byte) = find_invalid_byte(&reason) { + Err(InvalidReasonPhrase { bad_byte }) + } else { + Ok(Self(Bytes::from(reason))) + } + } +} + +impl TryFrom<String> for ReasonPhrase { + type Error = InvalidReasonPhrase; + + fn try_from(reason: String) -> Result<Self, Self::Error> { + if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) { + Err(InvalidReasonPhrase { bad_byte }) + } else { + Ok(Self(Bytes::from(reason))) + } + } +} + +impl TryFrom<Bytes> for ReasonPhrase { + type Error = InvalidReasonPhrase; + + fn try_from(reason: Bytes) -> Result<Self, Self::Error> { + if let Some(bad_byte) = find_invalid_byte(&reason) { + Err(InvalidReasonPhrase { bad_byte }) + } else { + Ok(Self(reason)) + } + } +} + +impl From<ReasonPhrase> for Bytes { + fn from(reason: ReasonPhrase) -> Self { + reason.0 + } +} + +impl AsRef<[u8]> for ReasonPhrase { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Error indicating an invalid byte when constructing a `ReasonPhrase`. +/// +/// See [the spec][spec] for details on allowed bytes. +/// +/// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7 +#[derive(Debug)] +pub struct InvalidReasonPhrase { + bad_byte: u8, +} + +impl std::fmt::Display for InvalidReasonPhrase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Invalid byte in reason phrase: {}", self.bad_byte) + } +} + +impl std::error::Error for InvalidReasonPhrase {} + +const fn is_valid_byte(b: u8) -> bool { + // See https://www.rfc-editor.org/rfc/rfc5234.html#appendix-B.1 + const fn is_vchar(b: u8) -> bool { + 0x21 <= b && b <= 0x7E + } + + // See https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#fields.values + // + // The 0xFF comparison is technically redundant, but it matches the text of the spec more + // clearly and will be optimized away. + #[allow(unused_comparisons, clippy::absurd_extreme_comparisons)] + const fn is_obs_text(b: u8) -> bool { + 0x80 <= b && b <= 0xFF + } + + // See https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7 + b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b) +} + +const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> { + let mut i = 0; + while i < bytes.len() { + let b = bytes[i]; + if !is_valid_byte(b) { + return Some(b); + } + i += 1; + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_valid() { + const PHRASE: &[u8] = b"OK"; + assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); + assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); + } + + #[test] + fn empty_valid() { + const PHRASE: &[u8] = b""; + assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); + assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); + } + + #[test] + fn obs_text_valid() { + const PHRASE: &[u8] = b"hyp\xe9r"; + assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); + assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); + } + + const NEWLINE_PHRASE: &[u8] = b"hyp\ner"; + + #[test] + #[should_panic] + fn newline_invalid_panic() { + ReasonPhrase::from_static(NEWLINE_PHRASE); + } + + #[test] + fn newline_invalid_err() { + assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err()); + } + + const CR_PHRASE: &[u8] = b"hyp\rer"; + + #[test] + #[should_panic] + fn cr_invalid_panic() { + ReasonPhrase::from_static(CR_PHRASE); + } + + #[test] + fn cr_invalid_err() { + assert!(ReasonPhrase::try_from(CR_PHRASE).is_err()); + } +} diff --git a/vendor/hyper/src/ext/informational.rs b/vendor/hyper/src/ext/informational.rs new file mode 100644 index 00000000..e728580f --- /dev/null +++ b/vendor/hyper/src/ext/informational.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; + +#[derive(Clone)] +pub(crate) struct OnInformational(Arc<dyn OnInformationalCallback + Send + Sync>); + +/// Add a callback for 1xx informational responses. +/// +/// # Example +/// +/// ``` +/// # let some_body = (); +/// let mut req = hyper::Request::new(some_body); +/// +/// hyper::ext::on_informational(&mut req, |res| { +/// println!("informational: {:?}", res.status()); +/// }); +/// +/// // send request on a client connection... +/// ``` +pub fn on_informational<B, F>(req: &mut http::Request<B>, callback: F) +where + F: Fn(Response<'_>) + Send + Sync + 'static, +{ + on_informational_raw(req, OnInformationalClosure(callback)); +} + +pub(crate) fn on_informational_raw<B, C>(req: &mut http::Request<B>, callback: C) +where + C: OnInformationalCallback + Send + Sync + 'static, +{ + req.extensions_mut() + .insert(OnInformational(Arc::new(callback))); +} + +// Sealed, not actually nameable bounds +pub(crate) trait OnInformationalCallback { + fn on_informational(&self, res: http::Response<()>); +} + +impl OnInformational { + pub(crate) fn call(&self, res: http::Response<()>) { + self.0.on_informational(res); + } +} + +struct OnInformationalClosure<F>(F); + +impl<F> OnInformationalCallback for OnInformationalClosure<F> +where + F: Fn(Response<'_>) + Send + Sync + 'static, +{ + fn on_informational(&self, res: http::Response<()>) { + let res = Response(&res); + (self.0)(res); + } +} + +// A facade over http::Response. +// +// It purposefully hides being able to move the response out of the closure, +// while also not being able to expect it to be a reference `&Response`. +// (Otherwise, a closure can be written as `|res: &_|`, and then be broken if +// we make the closure take ownership.) +// +// With the type not being nameable, we could change from being a facade to +// being either a real reference, or moving the http::Response into the closure, +// in a backwards-compatible change in the future. +#[derive(Debug)] +pub struct Response<'a>(&'a http::Response<()>); + +impl Response<'_> { + #[inline] + pub fn status(&self) -> http::StatusCode { + self.0.status() + } + + #[inline] + pub fn version(&self) -> http::Version { + self.0.version() + } + + #[inline] + pub fn headers(&self) -> &http::HeaderMap { + self.0.headers() + } +} diff --git a/vendor/hyper/src/ext/mod.rs b/vendor/hyper/src/ext/mod.rs new file mode 100644 index 00000000..da28da64 --- /dev/null +++ b/vendor/hyper/src/ext/mod.rs @@ -0,0 +1,246 @@ +//! HTTP extensions. + +#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] +use bytes::Bytes; +#[cfg(any( + all(any(feature = "client", feature = "server"), feature = "http1"), + feature = "ffi" +))] +use http::header::HeaderName; +#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] +use http::header::{HeaderMap, IntoHeaderName, ValueIter}; +#[cfg(feature = "ffi")] +use std::collections::HashMap; +#[cfg(feature = "http2")] +use std::fmt; + +#[cfg(any(feature = "http1", feature = "ffi"))] +mod h1_reason_phrase; +#[cfg(any(feature = "http1", feature = "ffi"))] +pub use h1_reason_phrase::ReasonPhrase; + +#[cfg(all(feature = "http1", feature = "client"))] +mod informational; +#[cfg(all(feature = "http1", feature = "client"))] +pub use informational::on_informational; +#[cfg(all(feature = "http1", feature = "client"))] +pub(crate) use informational::OnInformational; +#[cfg(all(feature = "http1", feature = "client", feature = "ffi"))] +pub(crate) use informational::{on_informational_raw, OnInformationalCallback}; + +#[cfg(feature = "http2")] +/// Represents the `:protocol` pseudo-header used by +/// the [Extended CONNECT Protocol]. +/// +/// [Extended CONNECT Protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4 +#[derive(Clone, Eq, PartialEq)] +pub struct Protocol { + inner: h2::ext::Protocol, +} + +#[cfg(feature = "http2")] +impl Protocol { + /// Converts a static string to a protocol name. + pub const fn from_static(value: &'static str) -> Self { + Self { + inner: h2::ext::Protocol::from_static(value), + } + } + + /// Returns a str representation of the header. + pub fn as_str(&self) -> &str { + self.inner.as_str() + } + + #[cfg(feature = "server")] + pub(crate) fn from_inner(inner: h2::ext::Protocol) -> Self { + Self { inner } + } + + #[cfg(all(feature = "client", feature = "http2"))] + pub(crate) fn into_inner(self) -> h2::ext::Protocol { + self.inner + } +} + +#[cfg(feature = "http2")] +impl<'a> From<&'a str> for Protocol { + fn from(value: &'a str) -> Self { + Self { + inner: h2::ext::Protocol::from(value), + } + } +} + +#[cfg(feature = "http2")] +impl AsRef<[u8]> for Protocol { + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } +} + +#[cfg(feature = "http2")] +impl fmt::Debug for Protocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// A map from header names to their original casing as received in an HTTP message. +/// +/// If an HTTP/1 response `res` is parsed on a connection whose option +/// [`preserve_header_case`] was set to true and the response included +/// the following headers: +/// +/// ```ignore +/// x-Bread: Baguette +/// X-BREAD: Pain +/// x-bread: Ficelle +/// ``` +/// +/// Then `res.extensions().get::<HeaderCaseMap>()` will return a map with: +/// +/// ```ignore +/// HeaderCaseMap({ +/// "x-bread": ["x-Bread", "X-BREAD", "x-bread"], +/// }) +/// ``` +/// +/// [`preserve_header_case`]: /client/struct.Client.html#method.preserve_header_case +#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] +#[derive(Clone, Debug)] +pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>); + +#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] +impl HeaderCaseMap { + /// Returns a view of all spellings associated with that header name, + /// in the order they were found. + #[cfg(feature = "client")] + pub(crate) fn get_all<'a>( + &'a self, + name: &HeaderName, + ) -> impl Iterator<Item = impl AsRef<[u8]> + 'a> + 'a { + self.get_all_internal(name) + } + + /// Returns a view of all spellings associated with that header name, + /// in the order they were found. + #[cfg(any(feature = "client", feature = "server"))] + pub(crate) fn get_all_internal(&self, name: &HeaderName) -> ValueIter<'_, Bytes> { + self.0.get_all(name).into_iter() + } + + #[cfg(any(feature = "client", feature = "server"))] + pub(crate) fn default() -> Self { + Self(Default::default()) + } + + #[cfg(any(test, feature = "ffi"))] + pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) { + self.0.insert(name, orig); + } + + #[cfg(any(feature = "client", feature = "server"))] + pub(crate) fn append<N>(&mut self, name: N, orig: Bytes) + where + N: IntoHeaderName, + { + self.0.append(name, orig); + } +} + +#[cfg(feature = "ffi")] +#[derive(Clone, Debug)] +/// Hashmap<Headername, numheaders with that name> +pub(crate) struct OriginalHeaderOrder { + /// Stores how many entries a Headername maps to. This is used + /// for accounting. + num_entries: HashMap<HeaderName, usize>, + /// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`, + /// The vector is ordered such that the ith element + /// represents the ith header that came in off the line. + /// The `HeaderName` and `idx` are then used elsewhere to index into + /// the multi map that stores the header values. + entry_order: Vec<(HeaderName, usize)>, +} + +#[cfg(all(feature = "http1", feature = "ffi"))] +impl OriginalHeaderOrder { + pub(crate) fn default() -> Self { + OriginalHeaderOrder { + num_entries: HashMap::new(), + entry_order: Vec::new(), + } + } + + pub(crate) fn insert(&mut self, name: HeaderName) { + if !self.num_entries.contains_key(&name) { + let idx = 0; + self.num_entries.insert(name.clone(), 1); + self.entry_order.push((name, idx)); + } + // Replacing an already existing element does not + // change ordering, so we only care if its the first + // header name encountered + } + + pub(crate) fn append<N>(&mut self, name: N) + where + N: IntoHeaderName + Into<HeaderName> + Clone, + { + let name: HeaderName = name.into(); + let idx; + if self.num_entries.contains_key(&name) { + idx = self.num_entries[&name]; + *self.num_entries.get_mut(&name).unwrap() += 1; + } else { + idx = 0; + self.num_entries.insert(name.clone(), 1); + } + self.entry_order.push((name, idx)); + } + + // No doc test is run here because `RUSTFLAGS='--cfg hyper_unstable_ffi'` + // is needed to compile. Once ffi is stabilized `no_run` should be removed + // here. + /// This returns an iterator that provides header names and indexes + /// in the original order received. + /// + /// # Examples + /// ```no_run + /// use hyper::ext::OriginalHeaderOrder; + /// use hyper::header::{HeaderName, HeaderValue, HeaderMap}; + /// + /// let mut h_order = OriginalHeaderOrder::default(); + /// let mut h_map = Headermap::new(); + /// + /// let name1 = b"Set-CookiE"; + /// let value1 = b"a=b"; + /// h_map.append(name1); + /// h_order.append(name1); + /// + /// let name2 = b"Content-Encoding"; + /// let value2 = b"gzip"; + /// h_map.append(name2, value2); + /// h_order.append(name2); + /// + /// let name3 = b"SET-COOKIE"; + /// let value3 = b"c=d"; + /// h_map.append(name3, value3); + /// h_order.append(name3) + /// + /// let mut iter = h_order.get_in_order() + /// + /// let (name, idx) = iter.next(); + /// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap()); + /// + /// let (name, idx) = iter.next(); + /// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap()); + /// + /// let (name, idx) = iter.next(); + /// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap()); + /// ``` + pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> { + self.entry_order.iter() + } +} |
