feat(http1): add options to preserve header casing (#2480)

Decouple preserving header case from FFI:

The feature is now supported in both the server and the client
and can be combined with the title case feature, for headers
which don't have entries in the header case map.

Closes #2313
This commit is contained in:
Anthony Ramine
2021-04-21 18:50:35 +02:00
committed by GitHub
parent 117cc492a6
commit dbea7716f1
12 changed files with 656 additions and 181 deletions

View File

@@ -997,6 +997,17 @@ impl Builder {
self self
} }
/// Set whether HTTP/1 connections will write header names as provided
/// at the socket level.
///
/// Note that this setting does not affect HTTP/2.
///
/// Default is false.
pub fn http1_preserve_header_case(&mut self, val: bool) -> &mut Self {
self.conn_builder.h1_preserve_header_case(val);
self
}
/// Set whether HTTP/0.9 responses should be tolerated. /// Set whether HTTP/0.9 responses should be tolerated.
/// ///
/// Default is false. /// Default is false.

View File

@@ -126,6 +126,7 @@ pub struct Builder {
h09_responses: bool, h09_responses: bool,
h1_parser_config: ParserConfig, h1_parser_config: ParserConfig,
h1_title_case_headers: bool, h1_title_case_headers: bool,
h1_preserve_header_case: bool,
h1_read_buf_exact_size: Option<usize>, h1_read_buf_exact_size: Option<usize>,
h1_max_buf_size: Option<usize>, h1_max_buf_size: Option<usize>,
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
@@ -500,6 +501,7 @@ impl Builder {
h1_read_buf_exact_size: None, h1_read_buf_exact_size: None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
h1_title_case_headers: false, h1_title_case_headers: false,
h1_preserve_header_case: false,
h1_max_buf_size: None, h1_max_buf_size: None,
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
h2_builder: Default::default(), h2_builder: Default::default(),
@@ -537,6 +539,11 @@ impl Builder {
self self
} }
pub(crate) fn h1_preserve_header_case(&mut self, enabled: bool) -> &mut Builder {
self.h1_preserve_header_case = enabled;
self
}
pub(super) fn h1_read_buf_exact_size(&mut self, sz: Option<usize>) -> &mut Builder { pub(super) fn h1_read_buf_exact_size(&mut self, sz: Option<usize>) -> &mut Builder {
self.h1_read_buf_exact_size = sz; self.h1_read_buf_exact_size = sz;
self.h1_max_buf_size = None; self.h1_max_buf_size = None;
@@ -719,6 +726,9 @@ impl Builder {
if opts.h1_title_case_headers { if opts.h1_title_case_headers {
conn.set_title_case_headers(); conn.set_title_case_headers();
} }
if opts.h1_preserve_header_case {
conn.set_preserve_header_case();
}
if opts.h09_responses { if opts.h09_responses {
conn.set_h09_responses(); conn.set_h09_responses();
} }

64
src/ext.rs Normal file
View File

@@ -0,0 +1,64 @@
//! HTTP extensions
use bytes::Bytes;
#[cfg(feature = "http1")]
use http::header::{HeaderName, IntoHeaderName, ValueIter};
use http::HeaderMap;
/// 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
/// [`http1_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"],
/// })
/// ```
///
/// [`http1_preserve_header_case`]: /client/struct.Client.html#method.http1_preserve_header_case
#[derive(Clone, Debug)]
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
#[cfg(feature = "http1")]
impl HeaderCaseMap {
/// Returns a view of all spellings associated with that header name,
/// in the order they were found.
pub(crate) fn get_all<'a>(
&'a self,
name: &HeaderName,
) -> impl Iterator<Item = impl AsRef<[u8]> + 'a> + 'a {
self.get_all_internal(name).into_iter()
}
/// Returns a view of all spellings associated with that header name,
/// in the order they were found.
pub(crate) fn get_all_internal<'a>(&'a self, name: &HeaderName) -> ValueIter<'_, Bytes> {
self.0.get_all(name).into_iter()
}
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);
}
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
where
N: IntoHeaderName,
{
self.0.append(name, orig);
}
}

View File

@@ -106,8 +106,11 @@ unsafe impl AsTaskType for hyper_clientconn {
ffi_fn! { ffi_fn! {
/// Creates a new set of HTTP clientconn options to be used in a handshake. /// Creates a new set of HTTP clientconn options to be used in a handshake.
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options { fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
let mut builder = conn::Builder::new();
builder.h1_preserve_header_case(true);
Box::into_raw(Box::new(hyper_clientconn_options { Box::into_raw(Box::new(hyper_clientconn_options {
builder: conn::Builder::new(), builder,
exec: WeakExec::new(), exec: WeakExec::new(),
})) }))
} ?= std::ptr::null_mut() } ?= std::ptr::null_mut()

View File

@@ -6,6 +6,7 @@ use super::body::hyper_body;
use super::error::hyper_code; use super::error::hyper_code;
use super::task::{hyper_task_return_type, AsTaskType}; use super::task::{hyper_task_return_type, AsTaskType};
use super::HYPER_ITER_CONTINUE; use super::HYPER_ITER_CONTINUE;
use crate::ext::HeaderCaseMap;
use crate::header::{HeaderName, HeaderValue}; use crate::header::{HeaderName, HeaderValue};
use crate::{Body, HeaderMap, Method, Request, Response, Uri}; use crate::{Body, HeaderMap, Method, Request, Response, Uri};
@@ -18,16 +19,11 @@ pub struct hyper_response(pub(super) Response<Body>);
/// An HTTP header map. /// An HTTP header map.
/// ///
/// These can be part of a request or response. /// These can be part of a request or response.
#[derive(Default)]
pub struct hyper_headers { pub struct hyper_headers {
pub(super) headers: HeaderMap, pub(super) headers: HeaderMap,
orig_casing: HeaderCaseMap, orig_casing: HeaderCaseMap,
} }
// Will probably be moved to `hyper::ext::http1`
#[derive(Debug, Default)]
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ReasonPhrase(pub(crate) Bytes); pub(crate) struct ReasonPhrase(pub(crate) Bytes);
@@ -229,7 +225,7 @@ impl hyper_response {
let orig_casing = resp let orig_casing = resp
.extensions_mut() .extensions_mut()
.remove::<HeaderCaseMap>() .remove::<HeaderCaseMap>()
.unwrap_or_default(); .unwrap_or_else(HeaderCaseMap::default);
resp.extensions_mut().insert(hyper_headers { resp.extensions_mut().insert(hyper_headers {
headers, headers,
orig_casing, orig_casing,
@@ -265,10 +261,7 @@ type hyper_headers_foreach_callback =
impl hyper_headers { impl hyper_headers {
pub(super) fn get_or_default(ext: &mut http::Extensions) -> &mut hyper_headers { pub(super) fn get_or_default(ext: &mut http::Extensions) -> &mut hyper_headers {
if let None = ext.get_mut::<hyper_headers>() { if let None = ext.get_mut::<hyper_headers>() {
ext.insert(hyper_headers { ext.insert(hyper_headers::default());
headers: Default::default(),
orig_casing: Default::default(),
});
} }
ext.get_mut::<hyper_headers>().unwrap() ext.get_mut::<hyper_headers>().unwrap()
@@ -290,11 +283,11 @@ ffi_fn! {
// //
// TODO: consider adding http::HeaderMap::entries() iterator // TODO: consider adding http::HeaderMap::entries() iterator
for name in headers.headers.keys() { for name in headers.headers.keys() {
let mut names = headers.orig_casing.get_all(name).iter(); let mut names = headers.orig_casing.get_all(name);
for value in headers.headers.get_all(name) { for value in headers.headers.get_all(name) {
let (name_ptr, name_len) = if let Some(orig_name) = names.next() { let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
(orig_name.as_ptr(), orig_name.len()) (orig_name.as_ref().as_ptr(), orig_name.as_ref().len())
} else { } else {
( (
name.as_str().as_bytes().as_ptr(), name.as_str().as_bytes().as_ptr(),
@@ -349,6 +342,15 @@ ffi_fn! {
} }
} }
impl Default for hyper_headers {
fn default() -> Self {
Self {
headers: Default::default(),
orig_casing: HeaderCaseMap::default(),
}
}
}
unsafe fn raw_name_value( unsafe fn raw_name_value(
name: *const u8, name: *const u8,
name_len: size_t, name_len: size_t,
@@ -370,25 +372,6 @@ unsafe fn raw_name_value(
Ok((name, value, orig_name)) Ok((name, value, orig_name))
} }
// ===== impl HeaderCaseMap =====
impl HeaderCaseMap {
pub(crate) fn get_all(&self, name: &HeaderName) -> http::header::GetAll<'_, Bytes> {
self.0.get_all(name)
}
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
self.0.insert(name, orig);
}
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
where
N: http::header::IntoHeaderName,
{
self.0.append(name, orig);
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -80,6 +80,7 @@ mod cfg;
mod common; mod common;
pub mod body; pub mod body;
mod error; mod error;
mod ext;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
#[cfg(any(feature = "http1", feature = "http2",))] #[cfg(any(feature = "http1", feature = "http2",))]

View File

@@ -46,7 +46,6 @@ where
keep_alive: KA::Busy, keep_alive: KA::Busy,
method: None, method: None,
h1_parser_config: ParserConfig::default(), h1_parser_config: ParserConfig::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
title_case_headers: false, title_case_headers: false,
h09_responses: false, h09_responses: false,
@@ -77,13 +76,16 @@ where
} }
#[cfg(feature = "client")] #[cfg(feature = "client")]
pub(crate) fn set_h1_parser_config(&mut self, parser_config: ParserConfig) {
self.state.h1_parser_config = parser_config;
}
pub(crate) fn set_title_case_headers(&mut self) { pub(crate) fn set_title_case_headers(&mut self) {
self.state.title_case_headers = true; self.state.title_case_headers = true;
} }
#[cfg(feature = "client")] pub(crate) fn set_preserve_header_case(&mut self) {
pub(crate) fn set_h1_parser_config(&mut self, parser_config: ParserConfig) { self.state.preserve_header_case = true;
self.state.h1_parser_config = parser_config;
} }
#[cfg(feature = "client")] #[cfg(feature = "client")]
@@ -158,7 +160,6 @@ where
cached_headers: &mut self.state.cached_headers, cached_headers: &mut self.state.cached_headers,
req_method: &mut self.state.method, req_method: &mut self.state.method,
h1_parser_config: self.state.h1_parser_config.clone(), h1_parser_config: self.state.h1_parser_config.clone(),
#[cfg(feature = "ffi")]
preserve_header_case: self.state.preserve_header_case, preserve_header_case: self.state.preserve_header_case,
h09_responses: self.state.h09_responses, h09_responses: self.state.h09_responses,
} }
@@ -499,16 +500,6 @@ where
self.enforce_version(&mut head); self.enforce_version(&mut head);
// Maybe check if we should preserve header casing on received
// message headers...
#[cfg(feature = "ffi")]
{
if T::is_client() && !self.state.preserve_header_case {
self.state.preserve_header_case =
head.extensions.get::<crate::ffi::HeaderCaseMap>().is_some();
}
}
let buf = self.io.headers_buf(); let buf = self.io.headers_buf();
match super::role::encode_headers::<T>( match super::role::encode_headers::<T>(
Encode { Encode {
@@ -772,7 +763,6 @@ struct State {
/// a body or not. /// a body or not.
method: Option<Method>, method: Option<Method>,
h1_parser_config: ParserConfig, h1_parser_config: ParserConfig,
#[cfg(feature = "ffi")]
preserve_header_case: bool, preserve_header_case: bool,
title_case_headers: bool, title_case_headers: bool,
h09_responses: bool, h09_responses: bool,

View File

@@ -160,7 +160,6 @@ where
cached_headers: parse_ctx.cached_headers, cached_headers: parse_ctx.cached_headers,
req_method: parse_ctx.req_method, req_method: parse_ctx.req_method,
h1_parser_config: parse_ctx.h1_parser_config.clone(), h1_parser_config: parse_ctx.h1_parser_config.clone(),
#[cfg(feature = "ffi")]
preserve_header_case: parse_ctx.preserve_header_case, preserve_header_case: parse_ctx.preserve_header_case,
h09_responses: parse_ctx.h09_responses, h09_responses: parse_ctx.h09_responses,
}, },
@@ -644,7 +643,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };

View File

@@ -72,7 +72,6 @@ pub(crate) struct ParseContext<'a> {
cached_headers: &'a mut Option<HeaderMap>, cached_headers: &'a mut Option<HeaderMap>,
req_method: &'a mut Option<Method>, req_method: &'a mut Option<Method>,
h1_parser_config: ParserConfig, h1_parser_config: ParserConfig,
#[cfg(feature = "ffi")]
preserve_header_case: bool, preserve_header_case: bool,
h09_responses: bool, h09_responses: bool,
} }

View File

@@ -5,16 +5,19 @@
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::mem; use std::mem;
#[cfg(feature = "ffi")] #[cfg(any(test, feature = "server", feature = "ffi"))]
use bytes::Bytes; use bytes::Bytes;
use bytes::BytesMut; use bytes::BytesMut;
use http::header::{self, Entry, HeaderName, HeaderValue}; use http::header::{self, Entry, HeaderName, HeaderValue};
#[cfg(feature = "server")]
use http::header::ValueIter;
use http::{HeaderMap, Method, StatusCode, Version}; use http::{HeaderMap, Method, StatusCode, Version};
use crate::body::DecodedLength; use crate::body::DecodedLength;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::common::date; use crate::common::date;
use crate::error::Parse; use crate::error::Parse;
use crate::ext::HeaderCaseMap;
use crate::headers; use crate::headers;
use crate::proto::h1::{ use crate::proto::h1::{
Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage,
@@ -191,6 +194,12 @@ impl Http1Transaction for Server {
let mut is_te_chunked = false; let mut is_te_chunked = false;
let mut wants_upgrade = subject.0 == Method::CONNECT; let mut wants_upgrade = subject.0 == Method::CONNECT;
let mut header_case_map = if ctx.preserve_header_case {
Some(HeaderCaseMap::default())
} else {
None
};
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new); let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
headers.reserve(headers_len); headers.reserve(headers_len);
@@ -260,6 +269,10 @@ 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));
}
headers.append(name, value); headers.append(name, value);
} }
@@ -268,6 +281,12 @@ impl Http1Transaction for Server {
return Err(Parse::Header); return Err(Parse::Header);
} }
let mut extensions = http::Extensions::default();
if let Some(header_case_map) = header_case_map {
extensions.insert(header_case_map);
}
*ctx.req_method = Some(subject.0.clone()); *ctx.req_method = Some(subject.0.clone());
Ok(Some(ParsedMessage { Ok(Some(ParsedMessage {
@@ -275,7 +294,7 @@ impl Http1Transaction for Server {
version, version,
subject, subject,
headers, headers,
extensions: http::Extensions::default(), extensions,
}, },
decode: decoder, decode: decoder,
expect_continue, expect_continue,
@@ -284,20 +303,13 @@ impl Http1Transaction for Server {
})) }))
} }
fn encode( fn encode(mut msg: Encode<'_, Self::Outgoing>, dst: &mut Vec<u8>) -> crate::Result<Encoder> {
mut msg: Encode<'_, Self::Outgoing>,
mut dst: &mut Vec<u8>,
) -> crate::Result<Encoder> {
trace!( trace!(
"Server::encode status={:?}, body={:?}, req_method={:?}", "Server::encode status={:?}, body={:?}, req_method={:?}",
msg.head.subject, msg.head.subject,
msg.body, msg.body,
msg.req_method msg.req_method
); );
debug_assert!(
!msg.title_case_headers,
"no server config for title case headers"
);
let mut wrote_len = false; let mut wrote_len = false;
@@ -305,7 +317,7 @@ impl Http1Transaction for Server {
// This is because Service only allows returning a single Response, and // This is because Service only allows returning a single Response, and
// so if you try to reply with a e.g. 100 Continue, you have no way of // so if you try to reply with a e.g. 100 Continue, you have no way of
// replying with the latter status code response. // replying with the latter status code response.
let (ret, mut is_last) = if msg.head.subject == StatusCode::SWITCHING_PROTOCOLS { let (ret, is_last) = if msg.head.subject == StatusCode::SWITCHING_PROTOCOLS {
(Ok(()), true) (Ok(()), true)
} else if msg.req_method == &Some(Method::CONNECT) && msg.head.subject.is_success() { } else if msg.req_method == &Some(Method::CONNECT) && msg.head.subject.is_success() {
// Sending content-length or transfer-encoding header on 2xx response // Sending content-length or transfer-encoding header on 2xx response
@@ -326,9 +338,6 @@ impl Http1Transaction for Server {
// pushing some bytes onto the `dst`. In those cases, we don't want to send // pushing some bytes onto the `dst`. In those cases, we don't want to send
// the half-pushed message, so rewind to before. // the half-pushed message, so rewind to before.
let orig_len = dst.len(); let orig_len = dst.len();
let rewind = |dst: &mut Vec<u8>| {
dst.truncate(orig_len);
};
let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE; let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE;
dst.reserve(init_cap); dst.reserve(init_cap);
@@ -359,6 +368,217 @@ impl Http1Transaction for Server {
extend(dst, b"\r\n"); extend(dst, b"\r\n");
} }
let orig_headers;
let extensions = mem::take(&mut msg.head.extensions);
let orig_headers = match extensions.get::<HeaderCaseMap>() {
None if msg.title_case_headers => {
orig_headers = HeaderCaseMap::default();
Some(&orig_headers)
}
orig_headers => orig_headers,
};
let encoder = if let Some(orig_headers) = orig_headers {
Self::encode_headers_with_original_case(
msg,
dst,
is_last,
orig_len,
wrote_len,
orig_headers,
)?
} else {
Self::encode_headers_with_lower_case(msg, dst, is_last, orig_len, wrote_len)?
};
ret.map(|()| encoder)
}
fn on_error(err: &crate::Error) -> Option<MessageHead<Self::Outgoing>> {
use crate::error::Kind;
let status = match *err.kind() {
Kind::Parse(Parse::Method)
| Kind::Parse(Parse::Header)
| Kind::Parse(Parse::Uri)
| Kind::Parse(Parse::Version) => StatusCode::BAD_REQUEST,
Kind::Parse(Parse::TooLarge) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
_ => return None,
};
debug!("sending automatic response ({}) for parse error", status);
let mut msg = MessageHead::default();
msg.subject = status;
Some(msg)
}
fn is_server() -> bool {
true
}
fn update_date() {
date::update();
}
}
#[cfg(feature = "server")]
impl Server {
fn can_have_body(method: &Option<Method>, status: StatusCode) -> bool {
Server::can_chunked(method, status)
}
fn can_chunked(method: &Option<Method>, status: StatusCode) -> bool {
if method == &Some(Method::HEAD) || method == &Some(Method::CONNECT) && status.is_success()
{
false
} else if status.is_informational() {
false
} else {
match status {
StatusCode::NO_CONTENT | StatusCode::NOT_MODIFIED => false,
_ => true,
}
}
}
fn can_have_content_length(method: &Option<Method>, status: StatusCode) -> bool {
if status.is_informational() || method == &Some(Method::CONNECT) && status.is_success() {
false
} else {
match status {
StatusCode::NO_CONTENT | StatusCode::NOT_MODIFIED => false,
_ => true,
}
}
}
fn encode_headers_with_lower_case(
msg: Encode<'_, StatusCode>,
dst: &mut Vec<u8>,
is_last: bool,
orig_len: usize,
wrote_len: bool,
) -> crate::Result<Encoder> {
struct LowercaseWriter;
impl HeaderNameWriter for LowercaseWriter {
#[inline]
fn write_full_header_line(
&mut self,
dst: &mut Vec<u8>,
line: &str,
_: (HeaderName, &str),
) {
extend(dst, line.as_bytes())
}
#[inline]
fn write_header_name_with_colon(
&mut self,
dst: &mut Vec<u8>,
name_with_colon: &str,
_: HeaderName,
) {
extend(dst, name_with_colon.as_bytes())
}
#[inline]
fn write_header_name(&mut self, dst: &mut Vec<u8>, name: &HeaderName) {
extend(dst, name.as_str().as_bytes())
}
}
Self::encode_headers(msg, dst, is_last, orig_len, wrote_len, LowercaseWriter)
}
#[cold]
#[inline(never)]
fn encode_headers_with_original_case(
msg: Encode<'_, StatusCode>,
dst: &mut Vec<u8>,
is_last: bool,
orig_len: usize,
wrote_len: bool,
orig_headers: &HeaderCaseMap,
) -> crate::Result<Encoder> {
struct OrigCaseWriter<'map> {
map: &'map HeaderCaseMap,
current: Option<(HeaderName, ValueIter<'map, Bytes>)>,
title_case_headers: bool,
}
impl HeaderNameWriter for OrigCaseWriter<'_> {
#[inline]
fn write_full_header_line(
&mut self,
dst: &mut Vec<u8>,
_: &str,
(name, rest): (HeaderName, &str),
) {
self.write_header_name(dst, &name);
extend(dst, rest.as_bytes());
}
#[inline]
fn write_header_name_with_colon(
&mut self,
dst: &mut Vec<u8>,
_: &str,
name: HeaderName,
) {
self.write_header_name(dst, &name);
extend(dst, b": ");
}
#[inline]
fn write_header_name(&mut self, dst: &mut Vec<u8>, name: &HeaderName) {
let Self {
map,
ref mut current,
title_case_headers,
} = *self;
if current.as_ref().map_or(true, |(last, _)| last != name) {
*current = None;
}
let (_, values) =
current.get_or_insert_with(|| (name.clone(), map.get_all_internal(name)));
if let Some(orig_name) = values.next() {
extend(dst, orig_name);
} else if title_case_headers {
title_case(dst, name.as_str().as_bytes());
} else {
extend(dst, name.as_str().as_bytes());
}
}
}
let header_name_writer = OrigCaseWriter {
map: orig_headers,
current: None,
title_case_headers: msg.title_case_headers,
};
Self::encode_headers(msg, dst, is_last, orig_len, wrote_len, header_name_writer)
}
#[inline]
fn encode_headers<W>(
msg: Encode<'_, StatusCode>,
mut dst: &mut Vec<u8>,
mut is_last: bool,
orig_len: usize,
mut wrote_len: bool,
mut header_name_writer: W,
) -> crate::Result<Encoder>
where
W: HeaderNameWriter,
{
// In some error cases, we don't know about the invalid message until already
// pushing some bytes onto the `dst`. In those cases, we don't want to send
// the half-pushed message, so rewind to before.
let rewind = |dst: &mut Vec<u8>| {
dst.truncate(orig_len);
};
let mut encoder = Encoder::length(0); let mut encoder = Encoder::length(0);
let mut wrote_date = false; let mut wrote_date = false;
let mut cur_name = None; let mut cur_name = None;
@@ -422,7 +642,11 @@ impl Http1Transaction for Server {
if !is_name_written { if !is_name_written {
encoder = Encoder::length(known_len); encoder = Encoder::length(known_len);
extend(dst, b"content-length: "); header_name_writer.write_header_name_with_colon(
dst,
"content-length: ",
header::CONTENT_LENGTH,
);
extend(dst, value.as_bytes()); extend(dst, value.as_bytes());
wrote_len = true; wrote_len = true;
is_name_written = true; is_name_written = true;
@@ -450,7 +674,11 @@ impl Http1Transaction for Server {
} else { } else {
// we haven't written content-length yet! // we haven't written content-length yet!
encoder = Encoder::length(len); encoder = Encoder::length(len);
extend(dst, b"content-length: "); header_name_writer.write_header_name_with_colon(
dst,
"content-length: ",
header::CONTENT_LENGTH,
);
extend(dst, value.as_bytes()); extend(dst, value.as_bytes());
wrote_len = true; wrote_len = true;
is_name_written = true; is_name_written = true;
@@ -505,7 +733,11 @@ impl Http1Transaction for Server {
if !is_name_written { if !is_name_written {
encoder = Encoder::chunked(); encoder = Encoder::chunked();
is_name_written = true; is_name_written = true;
extend(dst, b"transfer-encoding: "); header_name_writer.write_header_name_with_colon(
dst,
"transfer-encoding: ",
header::TRANSFER_ENCODING,
);
extend(dst, value.as_bytes()); extend(dst, value.as_bytes());
} else { } else {
extend(dst, b", "); extend(dst, b", ");
@@ -519,7 +751,11 @@ impl Http1Transaction for Server {
} }
if !is_name_written { if !is_name_written {
is_name_written = true; is_name_written = true;
extend(dst, b"connection: "); header_name_writer.write_header_name_with_colon(
dst,
"connection: ",
header::CONNECTION,
);
extend(dst, value.as_bytes()); extend(dst, value.as_bytes());
} else { } else {
extend(dst, b", "); extend(dst, b", ");
@@ -541,7 +777,7 @@ impl Http1Transaction for Server {
"{:?} set is_name_written and didn't continue loop", "{:?} set is_name_written and didn't continue loop",
name, name,
); );
extend(dst, name.as_str().as_bytes()); header_name_writer.write_header_name(dst, name);
extend(dst, b": "); extend(dst, b": ");
extend(dst, value.as_bytes()); extend(dst, value.as_bytes());
extend(dst, b"\r\n"); extend(dst, b"\r\n");
@@ -557,13 +793,21 @@ impl Http1Transaction for Server {
{ {
Encoder::close_delimited() Encoder::close_delimited()
} else { } else {
extend(dst, b"transfer-encoding: chunked\r\n"); header_name_writer.write_full_header_line(
dst,
"transfer-encoding: chunked\r\n",
(header::TRANSFER_ENCODING, ": chunked\r\n"),
);
Encoder::chunked() Encoder::chunked()
} }
} }
None | Some(BodyLength::Known(0)) => { None | Some(BodyLength::Known(0)) => {
if Server::can_have_content_length(msg.req_method, msg.head.subject) { if Server::can_have_content_length(msg.req_method, msg.head.subject) {
extend(dst, b"content-length: 0\r\n"); header_name_writer.write_full_header_line(
dst,
"content-length: 0\r\n",
(header::CONTENT_LENGTH, ": 0\r\n"),
)
} }
Encoder::length(0) Encoder::length(0)
} }
@@ -571,7 +815,11 @@ impl Http1Transaction for Server {
if !Server::can_have_content_length(msg.req_method, msg.head.subject) { if !Server::can_have_content_length(msg.req_method, msg.head.subject) {
Encoder::length(0) Encoder::length(0)
} else { } else {
extend(dst, b"content-length: "); header_name_writer.write_header_name_with_colon(
dst,
"content-length: ",
header::CONTENT_LENGTH,
);
let _ = ::itoa::write(&mut dst, len); let _ = ::itoa::write(&mut dst, len);
extend(dst, b"\r\n"); extend(dst, b"\r\n");
Encoder::length(len) Encoder::length(len)
@@ -592,72 +840,32 @@ impl Http1Transaction for Server {
// cached date is much faster than formatting every request // cached date is much faster than formatting every request
if !wrote_date { if !wrote_date {
dst.reserve(date::DATE_VALUE_LENGTH + 8); dst.reserve(date::DATE_VALUE_LENGTH + 8);
extend(dst, b"date: "); header_name_writer.write_header_name_with_colon(dst, "date: ", header::DATE);
date::extend(dst); date::extend(dst);
extend(dst, b"\r\n\r\n"); extend(dst, b"\r\n\r\n");
} else { } else {
extend(dst, b"\r\n"); extend(dst, b"\r\n");
} }
ret.map(|()| encoder.set_last(is_last)) Ok(encoder.set_last(is_last))
}
fn on_error(err: &crate::Error) -> Option<MessageHead<Self::Outgoing>> {
use crate::error::Kind;
let status = match *err.kind() {
Kind::Parse(Parse::Method)
| Kind::Parse(Parse::Header)
| Kind::Parse(Parse::Uri)
| Kind::Parse(Parse::Version) => StatusCode::BAD_REQUEST,
Kind::Parse(Parse::TooLarge) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
_ => return None,
};
debug!("sending automatic response ({}) for parse error", status);
let mut msg = MessageHead::default();
msg.subject = status;
Some(msg)
}
fn is_server() -> bool {
true
}
fn update_date() {
date::update();
} }
} }
#[cfg(feature = "server")] #[cfg(feature = "server")]
impl Server { trait HeaderNameWriter {
fn can_have_body(method: &Option<Method>, status: StatusCode) -> bool { fn write_full_header_line(
Server::can_chunked(method, status) &mut self,
} dst: &mut Vec<u8>,
line: &str,
fn can_chunked(method: &Option<Method>, status: StatusCode) -> bool { name_value_pair: (HeaderName, &str),
if method == &Some(Method::HEAD) || method == &Some(Method::CONNECT) && status.is_success() );
{ fn write_header_name_with_colon(
false &mut self,
} else if status.is_informational() { dst: &mut Vec<u8>,
false name_with_colon: &str,
} else { name: HeaderName,
match status { );
StatusCode::NO_CONTENT | StatusCode::NOT_MODIFIED => false, fn write_header_name(&mut self, dst: &mut Vec<u8>, name: &HeaderName);
_ => true,
}
}
}
fn can_have_content_length(method: &Option<Method>, status: StatusCode) -> bool {
if status.is_informational() || method == &Some(Method::CONNECT) && status.is_success() {
false
} else {
match status {
StatusCode::NO_CONTENT | StatusCode::NOT_MODIFIED => false,
_ => true,
}
}
}
} }
#[cfg(feature = "client")] #[cfg(feature = "client")]
@@ -732,8 +940,11 @@ impl Http1Transaction for Client {
let mut keep_alive = version == Version::HTTP_11; let mut keep_alive = version == Version::HTTP_11;
#[cfg(feature = "ffi")] let mut header_case_map = if ctx.preserve_header_case {
let mut header_case_map = crate::ffi::HeaderCaseMap::default(); Some(HeaderCaseMap::default())
} else {
None
};
headers.reserve(headers_len); headers.reserve(headers_len);
for header in &headers_indices[..headers_len] { for header in &headers_indices[..headers_len] {
@@ -751,19 +962,16 @@ impl Http1Transaction for Client {
} }
} }
#[cfg(feature = "ffi")] if let Some(ref mut header_case_map) = header_case_map {
if ctx.preserve_header_case {
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));
} }
headers.append(name, value); headers.append(name, value);
} }
#[allow(unused_mut)]
let mut extensions = http::Extensions::default(); let mut extensions = http::Extensions::default();
#[cfg(feature = "ffi")] if let Some(header_case_map) = header_case_map {
if ctx.preserve_header_case {
extensions.insert(header_case_map); extensions.insert(header_case_map);
} }
@@ -830,26 +1038,17 @@ impl Http1Transaction for Client {
} }
extend(dst, b"\r\n"); extend(dst, b"\r\n");
#[cfg(feature = "ffi")] if let Some(orig_headers) = msg.head.extensions.get::<HeaderCaseMap>() {
{ write_headers_original_case(
if msg.title_case_headers { &msg.head.headers,
write_headers_title_case(&msg.head.headers, dst); orig_headers,
} else if let Some(orig_headers) = dst,
msg.head.extensions.get::<crate::ffi::HeaderCaseMap>() msg.title_case_headers,
{ );
write_headers_original_case(&msg.head.headers, orig_headers, dst); } else if msg.title_case_headers {
} else { write_headers_title_case(&msg.head.headers, dst);
write_headers(&msg.head.headers, dst); } else {
} write_headers(&msg.head.headers, dst);
}
#[cfg(not(feature = "ffi"))]
{
if msg.title_case_headers {
write_headers_title_case(&msg.head.headers, dst);
} else {
write_headers(&msg.head.headers, dst);
}
} }
extend(dst, b"\r\n"); extend(dst, b"\r\n");
@@ -1162,12 +1361,12 @@ fn write_headers(headers: &HeaderMap, dst: &mut Vec<u8>) {
} }
} }
#[cfg(feature = "ffi")]
#[cold] #[cold]
fn write_headers_original_case( fn write_headers_original_case(
headers: &HeaderMap, headers: &HeaderMap,
orig_case: &crate::ffi::HeaderCaseMap, orig_case: &HeaderCaseMap,
dst: &mut Vec<u8>, dst: &mut Vec<u8>,
title_case_headers: bool,
) { ) {
// For each header name/value pair, there may be a value in the casemap // For each header name/value pair, there may be a value in the casemap
// that corresponds to the HeaderValue. So, we iterator all the keys, // that corresponds to the HeaderValue. So, we iterator all the keys,
@@ -1175,11 +1374,13 @@ fn write_headers_original_case(
// //
// TODO: consider adding http::HeaderMap::entries() iterator // TODO: consider adding http::HeaderMap::entries() iterator
for name in headers.keys() { for name in headers.keys() {
let mut names = orig_case.get_all(name).iter(); let mut names = orig_case.get_all(name);
for value in headers.get_all(name) { for value in headers.get_all(name) {
if let Some(orig_name) = names.next() { if let Some(orig_name) = names.next() {
extend(dst, orig_name); extend(dst, orig_name.as_ref());
} else if title_case_headers {
title_case(dst, name.as_str().as_bytes());
} else { } else {
extend(dst, name.as_str().as_bytes()); extend(dst, name.as_str().as_bytes());
} }
@@ -1233,7 +1434,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut method, req_method: &mut method,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1257,7 +1457,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(crate::Method::GET), req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };
@@ -1276,7 +1475,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };
@@ -1293,7 +1491,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(crate::Method::GET), req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: true, h09_responses: true,
}; };
@@ -1312,7 +1509,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(crate::Method::GET), req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };
@@ -1335,7 +1531,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(crate::Method::GET), req_method: &mut Some(crate::Method::GET),
h1_parser_config, h1_parser_config,
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };
@@ -1355,13 +1550,45 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(crate::Method::GET), req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}; };
Client::parse(&mut raw, ctx).unwrap_err(); Client::parse(&mut raw, ctx).unwrap_err();
} }
#[test]
fn test_parse_preserve_header_case_in_request() {
let mut raw =
BytesMut::from("GET / HTTP/1.1\r\nHost: hyper.rs\r\nX-BREAD: baguette\r\n\r\n");
let ctx = ParseContext {
cached_headers: &mut None,
req_method: &mut None,
h1_parser_config: Default::default(),
preserve_header_case: true,
h09_responses: false,
};
let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap();
let orig_headers = parsed_message
.head
.extensions
.get::<HeaderCaseMap>()
.unwrap();
assert_eq!(
orig_headers
.get_all_internal(&HeaderName::from_static("host"))
.into_iter()
.collect::<Vec<_>>(),
vec![&Bytes::from("Host")]
);
assert_eq!(
orig_headers
.get_all_internal(&HeaderName::from_static("x-bread"))
.into_iter()
.collect::<Vec<_>>(),
vec![&Bytes::from("X-BREAD")]
);
}
#[test] #[test]
fn test_decoder_request() { fn test_decoder_request() {
fn parse(s: &str) -> ParsedMessage<RequestLine> { fn parse(s: &str) -> ParsedMessage<RequestLine> {
@@ -1372,7 +1599,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1389,7 +1615,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1605,7 +1830,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(Method::GET), req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
} }
@@ -1622,7 +1846,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(m), req_method: &mut Some(m),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1639,7 +1862,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(Method::GET), req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1927,6 +2149,75 @@ mod tests {
assert_eq!(vec, b"GET / HTTP/1.1\r\nContent-Length: 10\r\nContent-Type: application/json\r\n*-*: o_o\r\n\r\n".to_vec()); assert_eq!(vec, b"GET / HTTP/1.1\r\nContent-Length: 10\r\nContent-Type: application/json\r\n*-*: o_o\r\n\r\n".to_vec());
} }
#[test]
fn test_client_request_encode_orig_case() {
use crate::proto::BodyLength;
use http::header::{HeaderValue, CONTENT_LENGTH};
let mut head = MessageHead::default();
head.headers
.insert("content-length", HeaderValue::from_static("10"));
head.headers
.insert("content-type", HeaderValue::from_static("application/json"));
let mut orig_headers = HeaderCaseMap::default();
orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into());
head.extensions.insert(orig_headers);
let mut vec = Vec::new();
Client::encode(
Encode {
head: &mut head,
body: Some(BodyLength::Known(10)),
keep_alive: true,
req_method: &mut None,
title_case_headers: false,
},
&mut vec,
)
.unwrap();
assert_eq!(
&*vec,
b"GET / HTTP/1.1\r\nCONTENT-LENGTH: 10\r\ncontent-type: application/json\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};
let mut head = MessageHead::default();
head.headers
.insert("content-length", HeaderValue::from_static("10"));
head.headers
.insert("content-type", HeaderValue::from_static("application/json"));
let mut orig_headers = HeaderCaseMap::default();
orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into());
head.extensions.insert(orig_headers);
let mut vec = Vec::new();
Client::encode(
Encode {
head: &mut head,
body: Some(BodyLength::Known(10)),
keep_alive: true,
req_method: &mut None,
title_case_headers: true,
},
&mut vec,
)
.unwrap();
assert_eq!(
&*vec,
b"GET / HTTP/1.1\r\nCONTENT-LENGTH: 10\r\nContent-Type: application/json\r\n\r\n"
.as_ref(),
);
}
#[test] #[test]
fn test_server_encode_connect_method() { fn test_server_encode_connect_method() {
let mut head = MessageHead::default(); let mut head = MessageHead::default();
@@ -1947,6 +2238,104 @@ mod tests {
assert!(encoder.is_last()); assert!(encoder.is_last());
} }
#[test]
fn test_server_response_encode_title_case() {
use crate::proto::BodyLength;
use http::header::HeaderValue;
let mut head = MessageHead::default();
head.headers
.insert("content-length", HeaderValue::from_static("10"));
head.headers
.insert("content-type", HeaderValue::from_static("application/json"));
let mut vec = Vec::new();
Server::encode(
Encode {
head: &mut head,
body: Some(BodyLength::Known(10)),
keep_alive: true,
req_method: &mut None,
title_case_headers: true,
},
&mut vec,
)
.unwrap();
let expected_response =
b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\nContent-Type: application/json\r\n";
assert_eq!(&vec[..expected_response.len()], &expected_response[..]);
}
#[test]
fn test_server_response_encode_orig_case() {
use crate::proto::BodyLength;
use http::header::{HeaderValue, CONTENT_LENGTH};
let mut head = MessageHead::default();
head.headers
.insert("content-length", HeaderValue::from_static("10"));
head.headers
.insert("content-type", HeaderValue::from_static("application/json"));
let mut orig_headers = HeaderCaseMap::default();
orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into());
head.extensions.insert(orig_headers);
let mut vec = Vec::new();
Server::encode(
Encode {
head: &mut head,
body: Some(BodyLength::Known(10)),
keep_alive: true,
req_method: &mut None,
title_case_headers: false,
},
&mut vec,
)
.unwrap();
let expected_response =
b"HTTP/1.1 200 OK\r\nCONTENT-LENGTH: 10\r\ncontent-type: application/json\r\ndate: ";
assert_eq!(&vec[..expected_response.len()], &expected_response[..]);
}
#[test]
fn test_server_response_encode_orig_and_title_case() {
use crate::proto::BodyLength;
use http::header::{HeaderValue, CONTENT_LENGTH};
let mut head = MessageHead::default();
head.headers
.insert("content-length", HeaderValue::from_static("10"));
head.headers
.insert("content-type", HeaderValue::from_static("application/json"));
let mut orig_headers = HeaderCaseMap::default();
orig_headers.insert(CONTENT_LENGTH, "CONTENT-LENGTH".into());
head.extensions.insert(orig_headers);
let mut vec = Vec::new();
Server::encode(
Encode {
head: &mut head,
body: Some(BodyLength::Known(10)),
keep_alive: true,
req_method: &mut None,
title_case_headers: true,
},
&mut vec,
)
.unwrap();
let expected_response =
b"HTTP/1.1 200 OK\r\nCONTENT-LENGTH: 10\r\nContent-Type: application/json\r\nDate: ";
assert_eq!(&vec[..expected_response.len()], &expected_response[..]);
}
#[test] #[test]
fn parse_header_htabs() { fn parse_header_htabs() {
let mut bytes = BytesMut::from("HTTP/1.1 200 OK\r\nserver: hello\tworld\r\n\r\n"); let mut bytes = BytesMut::from("HTTP/1.1 200 OK\r\nserver: hello\tworld\r\n\r\n");
@@ -1956,7 +2345,6 @@ mod tests {
cached_headers: &mut None, cached_headers: &mut None,
req_method: &mut Some(Method::GET), req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -1967,17 +2355,16 @@ mod tests {
assert_eq!(parsed.head.headers["server"], "hello\tworld"); assert_eq!(parsed.head.headers["server"], "hello\tworld");
} }
#[cfg(feature = "ffi")]
#[test] #[test]
fn test_write_headers_orig_case_empty_value() { fn test_write_headers_orig_case_empty_value() {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
let name = http::header::HeaderName::from_static("x-empty"); let name = http::header::HeaderName::from_static("x-empty");
headers.insert(&name, "".parse().expect("parse empty")); headers.insert(&name, "".parse().expect("parse empty"));
let mut orig_cases = crate::ffi::HeaderCaseMap::default(); 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"));
let mut dst = Vec::new(); let mut dst = Vec::new();
super::write_headers_original_case(&headers, &orig_cases, &mut dst); super::write_headers_original_case(&headers, &orig_cases, &mut dst, false);
assert_eq!( assert_eq!(
dst, b"X-EmptY:\r\n", dst, b"X-EmptY:\r\n",
@@ -1985,7 +2372,6 @@ mod tests {
); );
} }
#[cfg(feature = "ffi")]
#[test] #[test]
fn test_write_headers_orig_case_multiple_entries() { fn test_write_headers_orig_case_multiple_entries() {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
@@ -1993,12 +2379,12 @@ mod tests {
headers.insert(&name, "a".parse().unwrap()); headers.insert(&name, "a".parse().unwrap());
headers.append(&name, "b".parse().unwrap()); headers.append(&name, "b".parse().unwrap());
let mut orig_cases = crate::ffi::HeaderCaseMap::default(); let mut orig_cases = HeaderCaseMap::default();
orig_cases.insert(name.clone(), Bytes::from_static(b"X-Empty")); orig_cases.insert(name.clone(), Bytes::from_static(b"X-Empty"));
orig_cases.append(name, Bytes::from_static(b"X-EMPTY")); orig_cases.append(name, Bytes::from_static(b"X-EMPTY"));
let mut dst = Vec::new(); let mut dst = Vec::new();
super::write_headers_original_case(&headers, &orig_cases, &mut dst); super::write_headers_original_case(&headers, &orig_cases, &mut dst, false);
assert_eq!(dst, b"X-Empty: a\r\nX-EMPTY: b\r\n"); assert_eq!(dst, b"X-Empty: a\r\nX-EMPTY: b\r\n");
} }
@@ -2039,7 +2425,6 @@ mod tests {
cached_headers: &mut headers, cached_headers: &mut headers,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },
@@ -2076,7 +2461,6 @@ mod tests {
cached_headers: &mut headers, cached_headers: &mut headers,
req_method: &mut None, req_method: &mut None,
h1_parser_config: Default::default(), h1_parser_config: Default::default(),
#[cfg(feature = "ffi")]
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
}, },

View File

@@ -88,6 +88,7 @@ pub struct Http<E = Exec> {
exec: E, exec: E,
h1_half_close: bool, h1_half_close: bool,
h1_keep_alive: bool, h1_keep_alive: bool,
h1_title_case_headers: bool,
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
h2_builder: proto::h2::server::Config, h2_builder: proto::h2::server::Config,
mode: ConnectionMode, mode: ConnectionMode,
@@ -234,6 +235,7 @@ impl Http {
exec: Exec::Default, exec: Exec::Default,
h1_half_close: false, h1_half_close: false,
h1_keep_alive: true, h1_keep_alive: true,
h1_title_case_headers: false,
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
h2_builder: Default::default(), h2_builder: Default::default(),
mode: ConnectionMode::default(), mode: ConnectionMode::default(),
@@ -286,6 +288,19 @@ impl<E> Http<E> {
self self
} }
/// Set whether HTTP/1 connections will write header names as title case at
/// the socket level.
///
/// Note that this setting does not affect HTTP/2.
///
/// Default is false.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_title_case_headers(&mut self, enabled: bool) -> &mut Self {
self.h1_title_case_headers = enabled;
self
}
/// Sets whether HTTP2 is required. /// Sets whether HTTP2 is required.
/// ///
/// Default is false /// Default is false
@@ -459,6 +474,7 @@ impl<E> Http<E> {
exec, exec,
h1_half_close: self.h1_half_close, h1_half_close: self.h1_half_close,
h1_keep_alive: self.h1_keep_alive, h1_keep_alive: self.h1_keep_alive,
h1_title_case_headers: self.h1_title_case_headers,
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
h2_builder: self.h2_builder, h2_builder: self.h2_builder,
mode: self.mode, mode: self.mode,
@@ -514,6 +530,9 @@ impl<E> Http<E> {
if self.h1_half_close { if self.h1_half_close {
conn.set_allow_half_close(); conn.set_allow_half_close();
} }
if self.h1_title_case_headers {
conn.set_title_case_headers();
}
conn.set_flush_pipeline(self.pipeline_flush); conn.set_flush_pipeline(self.pipeline_flush);
if let Some(max) = self.max_buf_size { if let Some(max) = self.max_buf_size {
conn.set_max_buf_size(max); conn.set_max_buf_size(max);

View File

@@ -231,6 +231,19 @@ impl<I, E> Builder<I, E> {
self self
} }
/// Set whether HTTP/1 connections will write header names as title case at
/// the socket level.
///
/// Note that this setting does not affect HTTP/2.
///
/// Default is false.
#[cfg(feature = "http1")]
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
pub fn http1_title_case_headers(&mut self, val: bool) -> &mut Self {
self.protocol.http1_title_case_headers(val);
self
}
/// Sets whether HTTP/1 is required. /// Sets whether HTTP/1 is required.
/// ///
/// Default is `false`. /// Default is `false`.