dpi-detector/vendor/hyper/PATCH.diff
Kirill Minovsky 1e66cf3ab1
Some checks are pending
Check / Test & lint (linux) (push) Waiting to run
Check / Artifact round-trip (push) Waiting to run
Check / Test & lint (windows) (push) Waiting to run
Check / Dependency policy (push) Waiting to run
vendor: the hyper patch's comments are the contract, not the story
The fork's branch was trimmed to what the file's own style asks for, and this
copy follows it byte for byte.

* `append`'s documentation was two paragraphs in eleven lines; it is one
  paragraph in seven. Every fact stays — the lowercased `HeaderName` it is
  looked up by, the bytes going to the wire verbatim and having to spell that
  name, the pairing of several spellings with that header's values in order —
  but the file's methods document themselves in a line or three (`get_all`,
  `get_all_internal`), and the type above carries the story.
* The four comments inside the two tests are gone. The test names are
  sentences and the assertions are the whole argument; the neighbouring
  `test_client_request_encode_orig_and_title_case` has no comment either, and
  the file's test module runs at about 3% comment lines.

The type-level section and its runnable example stay: hyper denies
`missing_docs`, `ext/mod.rs` documents its types at length with a `# Example`
(`Protocol`), and the example is what a docs.rs reader uses.

`PATCH.diff` regenerated: 164 lines, 120 added, two files.

Verified: `cargo test --features full` in `vendor/hyper` — 117 unit tests and 15
doctests pass, 0 failed; `rustfmt --check` on both patched files is clean (the
gate the PR cannot run itself, since GitHub shows no checks on it); our workspace
239 + 79 + 2 and clippy 0 warnings on the same patch.
2026-09-25 04:48:57 +03:00

164 lines
6 KiB
Diff

diff -ruN --strip-trailing-cr -x .cargo-ok -x .cargo_vcs_info.json -x Cargo.lock -x Cargo.toml.orig a/src/ext/mod.rs b/src/ext/mod.rs
--- a/src/ext/mod.rs 2026-09-25 04:47:59.362510500 +0300
+++ b/src/ext/mod.rs 2026-09-25 04:47:59.940102200 +0300
@@ -156,9 +156,47 @@
/// ```
///
/// [`preserve_header_case`]: /client/struct.Client.html#method.preserve_header_case
+///
+/// # Writing a message with a chosen casing
+///
+/// A client that has to write a header name the way some other program writes
+/// it, because a server compares the bytes it receives. For example, `TE`
+/// rather than the lowercased `te`, or `sec-ch-ua` lowercased right next to a
+/// title-cased `Sec-Fetch-Site`. Insert a map like that into the message's
+/// extensions, and the HTTP/1 encoder writes each spelling it finds; a name the
+/// map does not mention is written as the lowercased [`HeaderName`].
+///
+/// ```rust
+/// use http::header::{HeaderName, HeaderValue, TE};
+/// use hyper::body::Bytes;
+/// use hyper::ext::HeaderCaseMap;
+///
+/// let sec_fetch_site = HeaderName::from_static("sec-fetch-site");
+///
+/// let mut req = http::Request::new(());
+/// req.headers_mut()
+/// .insert(TE, HeaderValue::from_static("trailers"));
+/// req.headers_mut()
+/// .insert(sec_fetch_site.clone(), HeaderValue::from_static("cross-site"));
+///
+/// let mut case_map = HeaderCaseMap::default();
+/// case_map.append(TE, Bytes::from_static(b"TE"));
+/// case_map.append(sec_fetch_site, Bytes::from_static(b"Sec-Fetch-Site"));
+///
+/// req.extensions_mut().insert(case_map);
+/// ```
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
#[derive(Clone, Debug)]
-pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
+pub struct HeaderCaseMap(HeaderMap<Bytes>);
+
+#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
+impl Default for HeaderCaseMap {
+ /// An empty map: every header name is written as the lowercased
+ /// [`HeaderName`] it is.
+ fn default() -> Self {
+ Self(HeaderMap::default())
+ }
+}
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
impl HeaderCaseMap {
@@ -179,18 +217,20 @@
self.0.get_all(name).into_iter()
}
- #[cfg(any(feature = "client", feature = "server"))]
- pub(crate) fn default() -> Self {
- Self(HeaderMap::default())
- }
-
#[cfg(any(test, feature = "ffi"))]
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
self.0.insert(name, orig);
}
+ /// Records that `name` was written as `orig`, so that the encoder writes
+ /// `orig` when this map is in a message's extensions and the message goes
+ /// out as HTTP/1. `name` is the lowercased [`HeaderName`] the spelling is
+ /// looked up by; `orig` goes to the wire verbatim and must be a legal header
+ /// name equal to `name` ignoring case. Several spellings for one name are
+ /// kept in the order appended and paired with that header's values in the
+ /// same order, the lowercased name standing in for a value left over.
#[cfg(any(feature = "client", feature = "server"))]
- pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
+ pub fn append<N>(&mut self, name: N, orig: Bytes)
where
N: IntoHeaderName,
{
@@ -296,3 +336,35 @@
self.entry_order.iter()
}
}
+
+#[cfg(all(test, any(feature = "client", feature = "server"), feature = "http1"))]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_appended_spellings_that_differ_only_in_case_are_distinct() {
+ let mut case_map = HeaderCaseMap::default();
+
+ assert_eq!(
+ case_map
+ .get_all_internal(&HeaderName::from_static("x-bread"))
+ .count(),
+ 0
+ );
+
+ case_map.append(
+ HeaderName::from_bytes(b"x-Bread").expect("valid header name"),
+ Bytes::from_static(b"x-Bread"),
+ );
+ case_map.append(
+ HeaderName::from_bytes(b"X-BREAD").expect("valid header name"),
+ Bytes::from_static(b"X-BREAD"),
+ );
+
+ let spellings: Vec<&[u8]> = case_map
+ .get_all_internal(&HeaderName::from_static("x-bread"))
+ .map(AsRef::as_ref)
+ .collect();
+ assert_eq!(spellings, [b"x-Bread".as_slice(), b"X-BREAD".as_slice()]);
+ }
+}
diff -ruN --strip-trailing-cr -x .cargo-ok -x .cargo_vcs_info.json -x Cargo.lock -x Cargo.toml.orig a/src/proto/h1/role.rs b/src/proto/h1/role.rs
--- a/src/proto/h1/role.rs 2026-09-25 04:47:59.450514000 +0300
+++ b/src/proto/h1/role.rs 2026-09-25 04:48:00.100821200 +0300
@@ -2614,6 +2614,45 @@
);
}
#[test]
+ fn test_client_request_encode_public_case_map() {
+ let sec_fetch_site = HeaderName::from_static("sec-fetch-site");
+
+ let mut head = MessageHead::default();
+ head.headers
+ .insert(&sec_fetch_site, HeaderValue::from_static("cross-site"));
+ head.headers.insert(
+ &HeaderName::from_static("te"),
+ HeaderValue::from_static("trailers"),
+ );
+
+ let mut case_map = HeaderCaseMap::default();
+ case_map.append(&sec_fetch_site, Bytes::from_static(b"Sec-Fetch-Site"));
+ case_map.append("te", Bytes::from_static(b"TE"));
+ head.extensions.insert(case_map);
+
+ let mut vec = Vec::new();
+ Client::encode(
+ Encode {
+ head: &mut head,
+ body: None,
+ #[cfg(feature = "server")]
+ keep_alive: true,
+ req_method: &mut None,
+ title_case_headers: false,
+ #[cfg(feature = "server")]
+ date_header: true,
+ },
+ &mut vec,
+ )
+ .unwrap();
+
+ assert_eq!(
+ &*vec,
+ b"GET / HTTP/1.1\r\nSec-Fetch-Site: cross-site\r\nTE: trailers\r\n\r\n".as_ref(),
+ );
+ }
+
+ #[test]
fn test_client_request_encode_orig_and_title_case() {
use crate::proto::BodyLength;
use http::header::{HeaderValue, CONTENT_LENGTH};