Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/client/conn/http1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub struct Builder {
h1_title_case_headers: bool,
h1_preserve_header_case: bool,
h1_max_headers: Option<usize>,
h1_max_header_size: Option<usize>,
#[cfg(feature = "ffi")]
h1_preserve_header_order: bool,
h1_read_buf_exact_size: Option<usize>,
Expand Down Expand Up @@ -352,6 +353,7 @@ impl Builder {
h1_title_case_headers: false,
h1_preserve_header_case: false,
h1_max_headers: None,
h1_max_header_size: None,
#[cfg(feature = "ffi")]
h1_preserve_header_order: false,
h1_max_buf_size: None,
Expand Down Expand Up @@ -500,6 +502,17 @@ impl Builder {
self
}

/// Set the maximum size of response headers (including the status line) in bytes.
///
/// If the server sends headers exceeding this limit, the error "message head is too large"
/// is returned.
///
/// Default is `None`.
pub fn max_header_size(&mut self, val: usize) -> &mut Self {
self.h1_max_header_size = Some(val);
self
}

/// Set whether to support preserving original header order.
///
/// Currently, this will record the order in which headers are received, and store this
Expand Down Expand Up @@ -587,6 +600,9 @@ impl Builder {
if let Some(max_headers) = opts.h1_max_headers {
conn.set_http1_max_headers(max_headers);
}
if let Some(max_header_size) = opts.h1_max_header_size {
conn.set_http1_max_header_size(max_header_size);
}
#[cfg(feature = "ffi")]
if opts.h1_preserve_header_order {
conn.set_preserve_header_order();
Expand Down
11 changes: 9 additions & 2 deletions src/proto/h1/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ where
method: None,
h1_parser_config: ParserConfig::default(),
h1_max_headers: None,
h1_max_header_size: None,
#[cfg(feature = "server")]
h1_header_read_timeout: None,
#[cfg(feature = "server")]
Expand Down Expand Up @@ -141,6 +142,10 @@ where
self.state.h1_max_headers = Some(val);
}

pub(crate) fn set_http1_max_header_size(&mut self, val: usize) {
self.state.h1_max_header_size = Some(val);
}

#[cfg(feature = "server")]
pub(crate) fn set_http1_header_read_timeout(&mut self, val: Duration) {
self.state.h1_header_read_timeout = Some(val);
Expand Down Expand Up @@ -241,6 +246,7 @@ where
req_method: &mut self.state.method,
h1_parser_config: self.state.h1_parser_config.clone(),
h1_max_headers: self.state.h1_max_headers,
h1_max_header_size: self.state.h1_max_header_size,
preserve_header_case: self.state.preserve_header_case,
#[cfg(feature = "ffi")]
preserve_header_order: self.state.preserve_header_order,
Expand Down Expand Up @@ -309,15 +315,15 @@ where
self.try_keep_alive(cx);
}
} else if msg.expect_continue && msg.head.version.gt(&Version::HTTP_10) {
let h1_max_header_size = None; // TODO: remove this when we land h1_max_header_size support
let h1_max_header_size = self.state.h1_max_header_size;
self.state.reading = Reading::Continue(Decoder::new(
msg.decode,
self.state.h1_max_headers,
h1_max_header_size,
));
wants = wants.add(Wants::EXPECT);
} else {
let h1_max_header_size = None; // TODO: remove this when we land h1_max_header_size support
let h1_max_header_size = self.state.h1_max_header_size;
self.state.reading = Reading::Body(Decoder::new(
msg.decode,
self.state.h1_max_headers,
Expand Down Expand Up @@ -933,6 +939,7 @@ struct State {
method: Option<Method>,
h1_parser_config: ParserConfig,
h1_max_headers: Option<usize>,
h1_max_header_size: Option<usize>,
#[cfg(feature = "server")]
h1_header_read_timeout: Option<Duration>,
#[cfg(feature = "server")]
Expand Down
10 changes: 9 additions & 1 deletion src/proto/h1/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ where
req_method: parse_ctx.req_method,
h1_parser_config: parse_ctx.h1_parser_config.clone(),
h1_max_headers: parse_ctx.h1_max_headers,
h1_max_header_size: parse_ctx.h1_max_header_size,
preserve_header_case: parse_ctx.preserve_header_case,
#[cfg(feature = "ffi")]
preserve_header_order: parse_ctx.preserve_header_order,
Expand All @@ -200,8 +201,14 @@ where
self.partial_len = None;
return Poll::Ready(Ok(msg));
} else {
let max = self.read_buf_strategy.max();
let curr_len = self.read_buf.len();
if let Some(max_header_size) = parse_ctx.h1_max_header_size {
if curr_len >= max_header_size {
debug!("max_header_size ({}) reached, closing", max_header_size);
return Poll::Ready(Err(crate::Error::new_too_large()));
}
}
let max = self.read_buf_strategy.max();
if curr_len >= max {
debug!("max_buf_size ({}) reached, closing", max);
return Poll::Ready(Err(crate::Error::new_too_large()));
Expand Down Expand Up @@ -702,6 +709,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down
1 change: 1 addition & 0 deletions src/proto/h1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub(crate) struct ParseContext<'ctx> {
req_method: &'ctx mut Option<Method>,
h1_parser_config: ParserConfig,
h1_max_headers: Option<usize>,
h1_max_header_size: Option<usize>,
preserve_header_case: bool,
#[cfg(feature = "ffi")]
preserve_header_order: bool,
Expand Down
95 changes: 95 additions & 0 deletions src/proto/h1/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ impl Http1Transaction for Server {
) {
Ok(httparse::Status::Complete(parsed_len)) => {
trace!("Request.parse Complete({})", parsed_len);
if let Some(max_header_size) = ctx.h1_max_header_size {
if parsed_len > max_header_size {
return Err(Parse::TooLarge);
}
}
len = parsed_len;
let uri = req.path.expect("httparse completed");
if uri.len() > MAX_URI_LEN {
Expand Down Expand Up @@ -1052,6 +1057,11 @@ impl Http1Transaction for Client {
) {
Ok(httparse::Status::Complete(len)) => {
trace!("Response.parse Complete({})", len);
if let Some(max_header_size) = ctx.h1_max_header_size {
if len > max_header_size {
return Err(Parse::TooLarge);
}
}
let status = StatusCode::from_u16(res.code.expect("httparse completed"))?;

let reason = {
Expand Down Expand Up @@ -1692,6 +1702,7 @@ mod tests {
req_method: &mut method,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -1720,6 +1731,7 @@ mod tests {
req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1744,6 +1756,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1765,6 +1778,7 @@ mod tests {
req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1788,6 +1802,7 @@ mod tests {
req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -1815,6 +1830,7 @@ mod tests {
req_method: &mut Some(crate::Method::GET),
h1_parser_config,
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1839,6 +1855,7 @@ mod tests {
req_method: &mut Some(crate::Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -1867,6 +1884,7 @@ mod tests {
req_method: &mut method,
h1_parser_config,
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -1894,6 +1912,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1914,6 +1933,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: true,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -1953,6 +1973,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -1974,6 +1995,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -2214,6 +2236,7 @@ mod tests {
req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -2235,6 +2258,7 @@ mod tests {
req_method: &mut Some(m),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -2256,6 +2280,7 @@ mod tests {
req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -2826,6 +2851,7 @@ mod tests {
req_method: &mut Some(Method::GET),
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -2870,6 +2896,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: max_headers,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand All @@ -2894,6 +2921,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: max_headers,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -2970,6 +2998,70 @@ mod tests {
parse(Some(200), 210, false);
}

#[test]
fn test_h1_max_header_size() {
let _ = pretty_env_logger::try_init();

let req_str = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
assert_eq!(req_str.len(), 37);
let resp_str = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
assert_eq!(resp_str.len(), 38);

let parse_req = |max_header_size: Option<usize>| {
let mut bytes = BytesMut::from(req_str);
Server::parse(
&mut bytes,
ParseContext {
cached_headers: &mut None,
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: max_header_size,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
};

let parse_resp = |max_header_size: Option<usize>| {
let mut bytes = BytesMut::from(resp_str);
Client::parse(
&mut bytes,
ParseContext {
cached_headers: &mut None,
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: max_header_size,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
};

// Server checks
parse_req(None).unwrap().unwrap();
parse_req(Some(37)).unwrap().unwrap();
parse_req(Some(50)).unwrap().unwrap();
assert!(matches!(parse_req(Some(36)), Err(Parse::TooLarge)));
assert!(matches!(parse_req(Some(10)), Err(Parse::TooLarge)));

// Client checks
parse_resp(None).unwrap().unwrap();
parse_resp(Some(38)).unwrap().unwrap();
parse_resp(Some(50)).unwrap().unwrap();
assert!(matches!(parse_resp(Some(37)), Err(Parse::TooLarge)));
assert!(matches!(parse_resp(Some(10)), Err(Parse::TooLarge)));
}

#[test]
fn test_is_complete_fast() {
let s = b"GET / HTTP/1.1\r\na: b\r\n\r\n";
Expand Down Expand Up @@ -3014,6 +3106,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -3097,6 +3190,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down Expand Up @@ -3142,6 +3236,7 @@ mod tests {
req_method: &mut None,
h1_parser_config: Default::default(),
h1_max_headers: None,
h1_max_header_size: None,
preserve_header_case: false,
#[cfg(feature = "ffi")]
preserve_header_order: false,
Expand Down
Loading
Loading