summaryrefslogtreecommitdiff
path: root/vendor/hyper/src/ext
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
committermo khan <mo@mokhan.ca>2025-07-02 18:36:06 -0600
commit8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch)
tree22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/hyper/src/ext
parent4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff)
chore: add vendor directory
Diffstat (limited to 'vendor/hyper/src/ext')
-rw-r--r--vendor/hyper/src/ext/h1_reason_phrase.rs221
-rw-r--r--vendor/hyper/src/ext/informational.rs86
-rw-r--r--vendor/hyper/src/ext/mod.rs246
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()
+ }
+}