From a92c25004ebbdeb70da6a84ce9c812e3ab84dc07 Mon Sep 17 00:00:00 2001 From: Kot Date: Tue, 28 Oct 2025 13:59:52 -0500 Subject: [PATCH] feat(ext): introduce header casing public API --- src/ext/mod.rs | 27 +++++++++++++++------------ src/ffi/http_types.rs | 4 ++-- src/proto/h1/role.rs | 40 ++++++++++++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/ext/mod.rs b/src/ext/mod.rs index b59d809dea..55930472ba 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -43,6 +43,8 @@ use bytes::Bytes; ))] use http::header::HeaderName; #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] +use http::header::InvalidHeaderName; +#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] use http::header::{HeaderMap, IntoHeaderName, ValueIter}; #[cfg(feature = "ffi")] use std::collections::HashMap; @@ -157,15 +159,15 @@ impl fmt::Debug for Protocol { /// /// [`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); +#[derive(Clone, Debug, Default)] +pub struct HeaderCaseMap(HeaderMap); #[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>( + pub fn get_all<'a>( &'a self, name: &HeaderName, ) -> impl Iterator + 'a> + 'a { @@ -179,22 +181,23 @@ impl HeaderCaseMap { 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) { + /// Inserts a header spelling, replacing any existing ones associated with that header name. + #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] + pub fn insert(&mut self, name: HeaderName, orig: Bytes) -> Result<(), InvalidHeaderName> { + HeaderName::from_bytes(&orig)?; self.0.insert(name, orig); + Ok(()) } - #[cfg(any(feature = "client", feature = "server"))] - pub(crate) fn append(&mut self, name: N, orig: Bytes) + /// Inserts a header spelling in addition to any existing ones associated with that header name. + #[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))] + pub fn append(&mut self, name: N, orig: Bytes) -> Result<(), InvalidHeaderName> where N: IntoHeaderName, { + HeaderName::from_bytes(&orig)?; self.0.append(name, orig); + Ok(()) } } diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 3dc4a2549d..d7601c35cc 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -513,7 +513,7 @@ ffi_fn! { match unsafe { raw_name_value(name, name_len, value, value_len) } { Ok((name, value, orig_name)) => { headers.headers.insert(&name, value); - headers.orig_casing.insert(name.clone(), orig_name.clone()); + headers.orig_casing.insert(name.clone(), orig_name.clone()).unwrap(); headers.orig_order.insert(name); hyper_code::HYPERE_OK } @@ -533,7 +533,7 @@ ffi_fn! { match unsafe { raw_name_value(name, name_len, value, value_len) } { Ok((name, value, orig_name)) => { headers.headers.append(&name, value); - headers.orig_casing.append(&name, orig_name.clone()); + headers.orig_casing.append(&name, orig_name.clone()).unwrap(); headers.orig_order.append(name); hyper_code::HYPERE_OK } diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 1674e26bc6..f342ca115b 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -315,7 +315,9 @@ impl Http1Transaction for Server { } if let Some(ref mut header_case_map) = header_case_map { - header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); + header_case_map + .append(&name, slice.slice(header.name.0..header.name.1)) + .unwrap(); } #[cfg(feature = "ffi")] @@ -1106,7 +1108,9 @@ impl Http1Transaction for Client { } if let Some(ref mut header_case_map) = header_case_map { - header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); + header_case_map + .append(&name, slice.slice(header.name.0..header.name.1)) + .unwrap(); } #[cfg(feature = "ffi")] @@ -2487,7 +2491,9 @@ mod tests { .insert("content-type", HeaderValue::from_static("application/json")); let mut orig_headers = HeaderCaseMap::default(); - orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()); + orig_headers + .insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()) + .unwrap(); head.extensions.insert(orig_headers); let mut vec = Vec::new(); @@ -2524,7 +2530,9 @@ mod tests { .insert("content-type", HeaderValue::from_static("application/json")); let mut orig_headers = HeaderCaseMap::default(); - orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()); + orig_headers + .insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()) + .unwrap(); head.extensions.insert(orig_headers); let mut vec = Vec::new(); @@ -2619,7 +2627,9 @@ mod tests { .insert("content-type", HeaderValue::from_static("application/json")); let mut orig_headers = HeaderCaseMap::default(); - orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()); + orig_headers + .insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()) + .unwrap(); head.extensions.insert(orig_headers); let mut vec = Vec::new(); @@ -2655,7 +2665,9 @@ mod tests { .insert("content-type", HeaderValue::from_static("application/json")); let mut orig_headers = HeaderCaseMap::default(); - orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()); + orig_headers + .insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()) + .unwrap(); head.extensions.insert(orig_headers); let mut vec = Vec::new(); @@ -2692,7 +2704,9 @@ mod tests { .insert("content-type", HeaderValue::from_static("application/json")); let mut orig_headers = HeaderCaseMap::default(); - orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()); + orig_headers + .insert(CONTENT_LENGTH, "CONTENT-LENGTH".into()) + .unwrap(); head.extensions.insert(orig_headers); let mut vec = Vec::new(); @@ -2897,7 +2911,9 @@ mod tests { let name = http::header::HeaderName::from_static("x-empty"); headers.insert(&name, "".parse().expect("parse empty")); let mut orig_cases = HeaderCaseMap::default(); - orig_cases.insert(name, Bytes::from_static(b"X-EmptY")); + orig_cases + .insert(name, Bytes::from_static(b"X-EmptY")) + .unwrap(); let mut dst = Vec::new(); super::write_headers_original_case(&headers, &orig_cases, &mut dst, false); @@ -2916,8 +2932,12 @@ mod tests { headers.append(&name, "b".parse().unwrap()); let mut orig_cases = HeaderCaseMap::default(); - orig_cases.insert(name.clone(), Bytes::from_static(b"X-Empty")); - orig_cases.append(name, Bytes::from_static(b"X-EMPTY")); + orig_cases + .insert(name.clone(), Bytes::from_static(b"X-Empty")) + .unwrap(); + orig_cases + .append(name, Bytes::from_static(b"X-EMPTY")) + .unwrap(); let mut dst = Vec::new(); super::write_headers_original_case(&headers, &orig_cases, &mut dst, false);