Skip to content

Commit e339223

Browse files
committed
feat(http1): decouple preserving header case from FFI (fixes hyperium#2313)
1 parent 98e7e0b commit e339223

File tree

11 files changed

+95
-86
lines changed

11 files changed

+95
-86
lines changed

src/client/client.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -964,14 +964,27 @@ impl Builder {
964964
/// Set whether HTTP/1 connections will write header names as title case at
965965
/// the socket level.
966966
///
967-
/// Note that this setting does not affect HTTP/2.
967+
/// Note that this setting does not affect HTTP/2 and supersedes the
968+
/// `http1_preserve_header_case` setting.
968969
///
969970
/// Default is false.
970971
pub fn http1_title_case_headers(&mut self, val: bool) -> &mut Self {
971972
self.conn_builder.h1_title_case_headers(val);
972973
self
973974
}
974975

976+
/// Set whether HTTP/1 connections will write header names as provided
977+
/// at the socket level.
978+
///
979+
/// Note that this setting does not affect HTTP/2 and is superseded
980+
/// by the `http1_title_case_headers` setting.
981+
///
982+
/// Default is false.
983+
pub fn http1_preserve_header_case(&mut self, val: bool) -> &mut Self {
984+
self.conn_builder.h1_preserve_header_case(val);
985+
self
986+
}
987+
975988
/// Set whether HTTP/0.9 responses should be tolerated.
976989
///
977990
/// Default is false.

src/client/conn.rs

+10
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub struct Builder {
124124
pub(super) exec: Exec,
125125
h09_responses: bool,
126126
h1_title_case_headers: bool,
127+
h1_preserve_header_case: bool,
127128
h1_read_buf_exact_size: Option<usize>,
128129
h1_max_buf_size: Option<usize>,
129130
#[cfg(feature = "http2")]
@@ -497,6 +498,7 @@ impl Builder {
497498
h09_responses: false,
498499
h1_read_buf_exact_size: None,
499500
h1_title_case_headers: false,
501+
h1_preserve_header_case: false,
500502
h1_max_buf_size: None,
501503
#[cfg(feature = "http2")]
502504
h2_builder: Default::default(),
@@ -526,6 +528,11 @@ impl Builder {
526528
self
527529
}
528530

531+
pub(crate) fn h1_preserve_header_case(&mut self, enabled: bool) -> &mut Builder {
532+
self.h1_preserve_header_case = enabled;
533+
self
534+
}
535+
529536
pub(super) fn h1_read_buf_exact_size(&mut self, sz: Option<usize>) -> &mut Builder {
530537
self.h1_read_buf_exact_size = sz;
531538
self.h1_max_buf_size = None;
@@ -707,6 +714,9 @@ impl Builder {
707714
if opts.h1_title_case_headers {
708715
conn.set_title_case_headers();
709716
}
717+
if opts.h1_preserve_header_case {
718+
conn.set_preserve_header_case();
719+
}
710720
if opts.h09_responses {
711721
conn.set_h09_responses();
712722
}

src/ext.rs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//! HTTP extensions
2+
3+
use bytes::Bytes;
4+
use http::header::{GetAll, HeaderName, IntoHeaderName};
5+
use http::HeaderMap;
6+
7+
/// A map from header names to their original casing as received in an HTTP response.
8+
///
9+
/// If an HTTP/1 response `res` is parsed on a connection whose option
10+
/// [`http1_preserve_header_case`] was set to true and the response included
11+
/// the following headers:
12+
///
13+
/// ```ignore
14+
/// x-Bread: Baguette
15+
/// X-BREAD: Pain
16+
/// x-bread: Ficelle
17+
/// ```
18+
///
19+
/// Then `res.extensions().get::<HeaderCaseMap>()` will return a map with:
20+
///
21+
/// ```ignore
22+
/// HeaderCaseMap({
23+
/// "x-bread": ["x-Bread", "X-BREAD", "x-bread"],
24+
/// })
25+
/// ```
26+
///
27+
/// [`http1_preserve_header_case`]: /client/struct.Client.html#method.http1_preserve_header_case
28+
#[derive(Debug, Default)]
29+
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
30+
31+
impl HeaderCaseMap {
32+
pub(crate) fn get_all(&self, name: &HeaderName) -> GetAll<'_, Bytes> {
33+
self.0.get_all(name)
34+
}
35+
36+
#[cfg(any(test, feature = "ffi"))]
37+
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
38+
self.0.insert(name, orig);
39+
}
40+
41+
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
42+
where
43+
N: IntoHeaderName,
44+
{
45+
self.0.append(name, orig);
46+
}
47+
}

src/ffi/client.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,11 @@ unsafe impl AsTaskType for hyper_clientconn {
106106
ffi_fn! {
107107
/// Creates a new set of HTTP clientconn options to be used in a handshake.
108108
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
109+
let mut builder = conn::Builder::new();
110+
builder.h1_preserve_header_case(true);
111+
109112
Box::into_raw(Box::new(hyper_clientconn_options {
110-
builder: conn::Builder::new(),
113+
builder,
111114
exec: WeakExec::new(),
112115
}))
113116
} ?= std::ptr::null_mut()

src/ffi/http_types.rs

+1-23
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::error::hyper_code;
77
use super::task::{hyper_task_return_type, AsTaskType};
88
use super::HYPER_ITER_CONTINUE;
99
use crate::header::{HeaderName, HeaderValue};
10+
use crate::proto::h1::HeaderCaseMap;
1011
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
1112

1213
/// An HTTP request.
@@ -24,10 +25,6 @@ pub struct hyper_headers {
2425
orig_casing: HeaderCaseMap,
2526
}
2627

27-
// Will probably be moved to `hyper::ext::http1`
28-
#[derive(Debug, Default)]
29-
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
30-
3128
#[derive(Debug)]
3229
pub(crate) struct ReasonPhrase(pub(crate) Bytes);
3330

@@ -370,25 +367,6 @@ unsafe fn raw_name_value(
370367
Ok((name, value, orig_name))
371368
}
372369

373-
// ===== impl HeaderCaseMap =====
374-
375-
impl HeaderCaseMap {
376-
pub(crate) fn get_all(&self, name: &HeaderName) -> http::header::GetAll<'_, Bytes> {
377-
self.0.get_all(name)
378-
}
379-
380-
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
381-
self.0.insert(name, orig);
382-
}
383-
384-
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
385-
where
386-
N: http::header::IntoHeaderName,
387-
{
388-
self.0.append(name, orig);
389-
}
390-
}
391-
392370
#[cfg(test)]
393371
mod tests {
394372
use super::*;

src/ffi/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub use self::io::*;
6262
pub use self::task::*;
6363

6464
pub(crate) use self::body::UserBody;
65-
pub(crate) use self::http_types::{HeaderCaseMap, ReasonPhrase};
65+
pub(crate) use self::http_types::ReasonPhrase;
6666

6767
/// Return in iter functions to continue iterating.
6868
pub const HYPER_ITER_CONTINUE: libc::c_int = 0;

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ mod cfg;
8080
mod common;
8181
pub mod body;
8282
mod error;
83+
pub mod ext;
8384
#[cfg(test)]
8485
mod mock;
8586
#[cfg(any(feature = "http1", feature = "http2",))]

src/proto/h1/conn.rs

+5-13
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ where
4444
error: None,
4545
keep_alive: KA::Busy,
4646
method: None,
47-
#[cfg(feature = "ffi")]
4847
preserve_header_case: false,
4948
title_case_headers: false,
5049
h09_responses: false,
@@ -79,6 +78,11 @@ where
7978
self.state.title_case_headers = true;
8079
}
8180

81+
#[cfg(feature = "client")]
82+
pub(crate) fn set_preserve_header_case(&mut self) {
83+
self.state.preserve_header_case = true;
84+
}
85+
8286
#[cfg(feature = "client")]
8387
pub(crate) fn set_h09_responses(&mut self) {
8488
self.state.h09_responses = true;
@@ -150,7 +154,6 @@ where
150154
ParseContext {
151155
cached_headers: &mut self.state.cached_headers,
152156
req_method: &mut self.state.method,
153-
#[cfg(feature = "ffi")]
154157
preserve_header_case: self.state.preserve_header_case,
155158
h09_responses: self.state.h09_responses,
156159
}
@@ -488,16 +491,6 @@ where
488491

489492
self.enforce_version(&mut head);
490493

491-
// Maybe check if we should preserve header casing on received
492-
// message headers...
493-
#[cfg(feature = "ffi")]
494-
{
495-
if T::is_client() && !self.state.preserve_header_case {
496-
self.state.preserve_header_case =
497-
head.extensions.get::<crate::ffi::HeaderCaseMap>().is_some();
498-
}
499-
}
500-
501494
let buf = self.io.headers_buf();
502495
match super::role::encode_headers::<T>(
503496
Encode {
@@ -760,7 +753,6 @@ struct State {
760753
/// This is used to know things such as if the message can include
761754
/// a body or not.
762755
method: Option<Method>,
763-
#[cfg(feature = "ffi")]
764756
preserve_header_case: bool,
765757
title_case_headers: bool,
766758
h09_responses: bool,

src/proto/h1/io.rs

-2
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ where
159159
ParseContext {
160160
cached_headers: parse_ctx.cached_headers,
161161
req_method: parse_ctx.req_method,
162-
#[cfg(feature = "ffi")]
163162
preserve_header_case: parse_ctx.preserve_header_case,
164163
h09_responses: parse_ctx.h09_responses,
165164
},
@@ -639,7 +638,6 @@ mod tests {
639638
let parse_ctx = ParseContext {
640639
cached_headers: &mut None,
641640
req_method: &mut None,
642-
#[cfg(feature = "ffi")]
643641
preserve_header_case: false,
644642
h09_responses: false,
645643
};

src/proto/h1/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ pub(crate) struct ParsedMessage<T> {
7070
pub(crate) struct ParseContext<'a> {
7171
cached_headers: &'a mut Option<HeaderMap>,
7272
req_method: &'a mut Option<Method>,
73-
#[cfg(feature = "ffi")]
7473
preserve_header_case: bool,
7574
h09_responses: bool,
7675
}

0 commit comments

Comments
 (0)