feat(client): add option to allow misplaced spaces in HTTP/1 responses (#2506)
This commit is contained in:
@@ -30,7 +30,7 @@ futures-util = { version = "0.3", default-features = false }
|
|||||||
http = "0.2"
|
http = "0.2"
|
||||||
http-body = "0.4"
|
http-body = "0.4"
|
||||||
httpdate = "1.0"
|
httpdate = "1.0"
|
||||||
httparse = "1.0"
|
httparse = "1.4"
|
||||||
h2 = { version = "0.3", optional = true }
|
h2 = { version = "0.3", optional = true }
|
||||||
itoa = "0.4.1"
|
itoa = "0.4.1"
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@@ -961,6 +961,31 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether HTTP/1 connections will accept spaces between header names
|
||||||
|
/// and the colon that follow them in responses.
|
||||||
|
///
|
||||||
|
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||||
|
/// to say about it:
|
||||||
|
///
|
||||||
|
/// > No whitespace is allowed between the header field-name and colon. In
|
||||||
|
/// > the past, differences in the handling of such whitespace have led to
|
||||||
|
/// > security vulnerabilities in request routing and response handling. A
|
||||||
|
/// > server MUST reject any received request message that contains
|
||||||
|
/// > whitespace between a header field-name and colon with a response code
|
||||||
|
/// > of 400 (Bad Request). A proxy MUST remove any such whitespace from a
|
||||||
|
/// > response message before forwarding the message downstream.
|
||||||
|
///
|
||||||
|
/// Note that this setting does not affect HTTP/2.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
///
|
||||||
|
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||||
|
pub fn http1_allow_spaces_after_header_name_in_responses(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.conn_builder
|
||||||
|
.h1_allow_spaces_after_header_name_in_responses(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether HTTP/1 connections will write header names as title case at
|
/// Set whether HTTP/1 connections will write header names as title case at
|
||||||
/// the socket level.
|
/// the socket level.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_util::future::{self, Either, FutureExt as _};
|
use futures_util::future::{self, Either, FutureExt as _};
|
||||||
|
use httparse::ParserConfig;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
@@ -123,6 +124,7 @@ where
|
|||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
pub(super) exec: Exec,
|
pub(super) exec: Exec,
|
||||||
h09_responses: bool,
|
h09_responses: bool,
|
||||||
|
h1_parser_config: ParserConfig,
|
||||||
h1_title_case_headers: bool,
|
h1_title_case_headers: 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>,
|
||||||
@@ -496,6 +498,7 @@ impl Builder {
|
|||||||
exec: Exec::Default,
|
exec: Exec::Default,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
h1_read_buf_exact_size: None,
|
h1_read_buf_exact_size: None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
h1_title_case_headers: false,
|
h1_title_case_headers: false,
|
||||||
h1_max_buf_size: None,
|
h1_max_buf_size: None,
|
||||||
#[cfg(feature = "http2")]
|
#[cfg(feature = "http2")]
|
||||||
@@ -521,6 +524,14 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn h1_allow_spaces_after_header_name_in_responses(
|
||||||
|
&mut self,
|
||||||
|
enabled: bool,
|
||||||
|
) -> &mut Builder {
|
||||||
|
self.h1_parser_config.allow_spaces_after_header_name_in_responses(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn h1_title_case_headers(&mut self, enabled: bool) -> &mut Builder {
|
pub(super) fn h1_title_case_headers(&mut self, enabled: bool) -> &mut Builder {
|
||||||
self.h1_title_case_headers = enabled;
|
self.h1_title_case_headers = enabled;
|
||||||
self
|
self
|
||||||
@@ -704,6 +715,7 @@ impl Builder {
|
|||||||
#[cfg(feature = "http1")]
|
#[cfg(feature = "http1")]
|
||||||
Proto::Http1 => {
|
Proto::Http1 => {
|
||||||
let mut conn = proto::Conn::new(io);
|
let mut conn = proto::Conn::new(io);
|
||||||
|
conn.set_h1_parser_config(opts.h1_parser_config);
|
||||||
if opts.h1_title_case_headers {
|
if opts.h1_title_case_headers {
|
||||||
conn.set_title_case_headers();
|
conn.set_title_case_headers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::marker::PhantomData;
|
|||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
use http::header::{HeaderValue, CONNECTION};
|
use http::header::{HeaderValue, CONNECTION};
|
||||||
use http::{HeaderMap, Method, Version};
|
use http::{HeaderMap, Method, Version};
|
||||||
|
use httparse::ParserConfig;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
use super::io::Buffered;
|
use super::io::Buffered;
|
||||||
@@ -44,6 +45,7 @@ where
|
|||||||
error: None,
|
error: None,
|
||||||
keep_alive: KA::Busy,
|
keep_alive: KA::Busy,
|
||||||
method: None,
|
method: None,
|
||||||
|
h1_parser_config: ParserConfig::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
title_case_headers: false,
|
title_case_headers: false,
|
||||||
@@ -79,6 +81,11 @@ where
|
|||||||
self.state.title_case_headers = true;
|
self.state.title_case_headers = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(crate) fn set_h1_parser_config(&mut self, parser_config: ParserConfig) {
|
||||||
|
self.state.h1_parser_config = parser_config;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub(crate) fn set_h09_responses(&mut self) {
|
pub(crate) fn set_h09_responses(&mut self) {
|
||||||
self.state.h09_responses = true;
|
self.state.h09_responses = true;
|
||||||
@@ -150,6 +157,7 @@ where
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[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,
|
||||||
@@ -284,7 +292,10 @@ where
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn poll_read_keep_alive(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
pub(crate) fn poll_read_keep_alive(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<crate::Result<()>> {
|
||||||
debug_assert!(!self.can_read_head() && !self.can_read_body());
|
debug_assert!(!self.can_read_head() && !self.can_read_body());
|
||||||
|
|
||||||
if self.is_read_closed() {
|
if self.is_read_closed() {
|
||||||
@@ -760,6 +771,7 @@ struct State {
|
|||||||
/// This is used to know things such as if the message can include
|
/// This is used to know things such as if the message can include
|
||||||
/// a body or not.
|
/// a body or not.
|
||||||
method: Option<Method>,
|
method: Option<Method>,
|
||||||
|
h1_parser_config: ParserConfig,
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: bool,
|
preserve_header_case: bool,
|
||||||
title_case_headers: bool,
|
title_case_headers: bool,
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ where
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[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,
|
||||||
@@ -183,7 +184,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn poll_read_from_io(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<usize>> {
|
pub(crate) fn poll_read_from_io(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
self.read_blocked = false;
|
self.read_blocked = false;
|
||||||
let next = self.read_buf_strategy.next();
|
let next = self.read_buf_strategy.next();
|
||||||
if self.read_buf_remaining_mut() < next {
|
if self.read_buf_remaining_mut() < next {
|
||||||
@@ -378,7 +382,7 @@ impl ReadStrategy {
|
|||||||
*decrease_now = false;
|
*decrease_now = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
ReadStrategy::Exact(_) => (),
|
ReadStrategy::Exact(_) => (),
|
||||||
}
|
}
|
||||||
@@ -639,6 +643,7 @@ mod tests {
|
|||||||
let parse_ctx = ParseContext {
|
let parse_ctx = ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use http::{HeaderMap, Method};
|
use http::{HeaderMap, Method};
|
||||||
|
use httparse::ParserConfig;
|
||||||
|
|
||||||
use crate::body::DecodedLength;
|
use crate::body::DecodedLength;
|
||||||
use crate::proto::{BodyLength, MessageHead};
|
use crate::proto::{BodyLength, MessageHead};
|
||||||
@@ -70,6 +71,7 @@ pub(crate) struct ParsedMessage<T> {
|
|||||||
pub(crate) struct ParseContext<'a> {
|
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,
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: bool,
|
preserve_header_case: bool,
|
||||||
h09_responses: bool,
|
h09_responses: bool,
|
||||||
|
|||||||
@@ -683,7 +683,8 @@ impl Http1Transaction for Client {
|
|||||||
);
|
);
|
||||||
let mut res = httparse::Response::new(&mut headers);
|
let mut res = httparse::Response::new(&mut headers);
|
||||||
let bytes = buf.as_ref();
|
let bytes = buf.as_ref();
|
||||||
match res.parse(bytes) {
|
match ctx.h1_parser_config.parse_response(&mut res, bytes)
|
||||||
|
{
|
||||||
Ok(httparse::Status::Complete(len)) => {
|
Ok(httparse::Status::Complete(len)) => {
|
||||||
trace!("Response.parse Complete({})", len);
|
trace!("Response.parse Complete({})", len);
|
||||||
let status = StatusCode::from_u16(res.code.unwrap())?;
|
let status = StatusCode::from_u16(res.code.unwrap())?;
|
||||||
@@ -1231,6 +1232,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut method,
|
req_method: &mut method,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1254,6 +1256,7 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1272,6 +1275,7 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1288,6 +1292,7 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: true,
|
h09_responses: true,
|
||||||
@@ -1306,6 +1311,7 @@ mod tests {
|
|||||||
let ctx = ParseContext {
|
let ctx = ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1314,6 +1320,48 @@ mod tests {
|
|||||||
assert_eq!(raw, H09_RESPONSE);
|
assert_eq!(raw, H09_RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON: &'static str =
|
||||||
|
"HTTP/1.1 200 OK\r\nAccess-Control-Allow-Credentials : true\r\n\r\n";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_allow_response_with_spaces_before_colons() {
|
||||||
|
use httparse::ParserConfig;
|
||||||
|
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let mut raw = BytesMut::from(RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON);
|
||||||
|
let mut h1_parser_config = ParserConfig::default();
|
||||||
|
h1_parser_config.allow_spaces_after_header_name_in_responses(true);
|
||||||
|
let ctx = ParseContext {
|
||||||
|
cached_headers: &mut None,
|
||||||
|
req_method: &mut Some(crate::Method::GET),
|
||||||
|
h1_parser_config,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
|
h09_responses: false,
|
||||||
|
};
|
||||||
|
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
|
||||||
|
assert_eq!(raw.len(), 0);
|
||||||
|
assert_eq!(msg.head.subject, crate::StatusCode::OK);
|
||||||
|
assert_eq!(msg.head.version, crate::Version::HTTP_11);
|
||||||
|
assert_eq!(msg.head.headers.len(), 1);
|
||||||
|
assert_eq!(msg.head.headers["Access-Control-Allow-Credentials"], "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_reject_response_with_spaces_before_colons() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let mut raw = BytesMut::from(RESPONSE_WITH_WHITESPACE_BETWEEN_HEADER_NAME_AND_COLON);
|
||||||
|
let ctx = ParseContext {
|
||||||
|
cached_headers: &mut None,
|
||||||
|
req_method: &mut Some(crate::Method::GET),
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_case: false,
|
||||||
|
h09_responses: false,
|
||||||
|
};
|
||||||
|
Client::parse(&mut raw, ctx).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decoder_request() {
|
fn test_decoder_request() {
|
||||||
fn parse(s: &str) -> ParsedMessage<RequestLine> {
|
fn parse(s: &str) -> ParsedMessage<RequestLine> {
|
||||||
@@ -1323,6 +1371,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1339,6 +1388,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1554,6 +1604,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1570,6 +1621,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut None,
|
cached_headers: &mut None,
|
||||||
req_method: &mut Some(m),
|
req_method: &mut Some(m),
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1586,6 +1638,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1902,6 +1955,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
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(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -1984,6 +2038,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut headers,
|
cached_headers: &mut headers,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
@@ -2020,6 +2075,7 @@ mod tests {
|
|||||||
ParseContext {
|
ParseContext {
|
||||||
cached_headers: &mut headers,
|
cached_headers: &mut headers,
|
||||||
req_method: &mut None,
|
req_method: &mut None,
|
||||||
|
h1_parser_config: Default::default(),
|
||||||
#[cfg(feature = "ffi")]
|
#[cfg(feature = "ffi")]
|
||||||
preserve_header_case: false,
|
preserve_header_case: false,
|
||||||
h09_responses: false,
|
h09_responses: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user