mirror of
https://github.com/Runnin4ik/dpi-detector.git
synced 2026-10-19 09:23:17 +00:00
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.
164 lines
6 KiB
Diff
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};
|