From a108e51cab28bb97e601632b5c5f88d22ae132b2 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Fri, 19 Mar 2021 14:53:33 +0100 Subject: [PATCH] feat(client): allow HTTP/0.9 responses behind a flag (fixes #2468) --- src/client/client.rs | 8 ++++++++ src/client/conn.rs | 10 ++++++++++ src/proto/h1/conn.rs | 8 ++++++++ src/proto/h1/io.rs | 2 ++ src/proto/h1/mod.rs | 1 + src/proto/h1/role.rs | 28 +++++++++++++++++++++++++--- 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/client/client.rs b/src/client/client.rs index 10cbc37b63..124a10c4f8 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -972,6 +972,14 @@ impl Builder { self } + /// Set whether HTTP/0.9 responses should be tolerated. + /// + /// Default is false. + pub fn http09_responses(&mut self, val: bool) -> &mut Self { + self.conn_builder.h09_responses(val); + self + } + /// Set whether the connection **must** use HTTP/2. /// /// The destination must either allow HTTP2 Prior Knowledge, or the diff --git a/src/client/conn.rs b/src/client/conn.rs index 2da083db16..b87600d85a 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -122,6 +122,7 @@ where #[derive(Clone, Debug)] pub struct Builder { pub(super) exec: Exec, + h09_responses: bool, h1_title_case_headers: bool, h1_read_buf_exact_size: Option, h1_max_buf_size: Option, @@ -493,6 +494,7 @@ impl Builder { pub fn new() -> Builder { Builder { exec: Exec::Default, + h09_responses: false, h1_read_buf_exact_size: None, h1_title_case_headers: false, h1_max_buf_size: None, @@ -514,6 +516,11 @@ impl Builder { self } + pub(super) fn h09_responses(&mut self, enabled: bool) -> &mut Builder { + self.h09_responses = enabled; + self + } + pub(super) fn h1_title_case_headers(&mut self, enabled: bool) -> &mut Builder { self.h1_title_case_headers = enabled; self @@ -700,6 +707,9 @@ impl Builder { if opts.h1_title_case_headers { conn.set_title_case_headers(); } + if opts.h09_responses { + conn.set_h09_responses(); + } if let Some(sz) = opts.h1_read_buf_exact_size { conn.set_read_buf_exact_size(sz); } diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 174a1d8695..d3c8bfbc89 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -47,6 +47,7 @@ where #[cfg(feature = "ffi")] preserve_header_case: false, title_case_headers: false, + h09_responses: false, notify_read: false, reading: Reading::Init, writing: Writing::Init, @@ -78,6 +79,11 @@ where self.state.title_case_headers = true; } + #[cfg(feature = "client")] + pub(crate) fn set_h09_responses(&mut self) { + self.state.h09_responses = true; + } + #[cfg(feature = "server")] pub(crate) fn set_allow_half_close(&mut self) { self.state.allow_half_close = true; @@ -146,6 +152,7 @@ where req_method: &mut self.state.method, #[cfg(feature = "ffi")] preserve_header_case: self.state.preserve_header_case, + h09_responses: self.state.h09_responses, } )) { Ok(msg) => msg, @@ -753,6 +760,7 @@ struct State { #[cfg(feature = "ffi")] preserve_header_case: bool, title_case_headers: bool, + h09_responses: bool, /// Set to true when the Dispatcher should poll read operations /// again. See the `maybe_notify` method for more. notify_read: bool, diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 5536b5d164..c7ce48664b 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -161,6 +161,7 @@ where req_method: parse_ctx.req_method, #[cfg(feature = "ffi")] preserve_header_case: parse_ctx.preserve_header_case, + h09_responses: parse_ctx.h09_responses, }, )? { Some(msg) => { @@ -640,6 +641,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }; assert!(buffered .parse::(cx, parse_ctx) diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index 1498872ea8..01a9253fa3 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -72,6 +72,7 @@ pub(crate) struct ParseContext<'a> { req_method: &'a mut Option, #[cfg(feature = "ffi")] preserve_header_case: bool, + h09_responses: bool, } /// Passed to Http1Transaction::encode diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index b7310ba99d..aab5b0ef97 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -683,8 +683,8 @@ impl Http1Transaction for Client { ); let mut res = httparse::Response::new(&mut headers); let bytes = buf.as_ref(); - match res.parse(bytes)? { - httparse::Status::Complete(len) => { + match res.parse(bytes) { + Ok(httparse::Status::Complete(len)) => { trace!("Response.parse Complete({})", len); let status = StatusCode::from_u16(res.code.unwrap())?; @@ -710,7 +710,18 @@ impl Http1Transaction for Client { let headers_len = res.headers.len(); (len, status, reason, version, headers_len) } - httparse::Status::Partial => return Ok(None), + Ok(httparse::Status::Partial) => return Ok(None), + Err(httparse::Error::Version) if ctx.h09_responses => { + trace!("Response.parse accepted HTTP/0.9 response"); + + #[cfg(not(feature = "ffi"))] + let reason = (); + #[cfg(feature = "ffi")] + let reason = None; + + (0, StatusCode::OK, reason, Version::HTTP_09, 0) + } + Err(e) => return Err(e.into()), } }; @@ -1222,6 +1233,7 @@ mod tests { req_method: &mut method, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .unwrap() @@ -1244,6 +1256,7 @@ mod tests { req_method: &mut Some(crate::Method::GET), #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); assert_eq!(raw.len(), 0); @@ -1261,6 +1274,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }; Server::parse(&mut raw, ctx).unwrap_err(); } @@ -1276,6 +1290,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .expect("parse ok") @@ -1291,6 +1306,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .expect_err(comment) @@ -1505,6 +1521,7 @@ mod tests { req_method: &mut Some(Method::GET), #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, } ) .expect("parse ok") @@ -1520,6 +1537,7 @@ mod tests { req_method: &mut Some(m), #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .expect("parse ok") @@ -1535,6 +1553,7 @@ mod tests { req_method: &mut Some(Method::GET), #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .expect_err("parse should err") @@ -1850,6 +1869,7 @@ mod tests { req_method: &mut Some(Method::GET), #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .expect("parse ok") @@ -1931,6 +1951,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .unwrap() @@ -1966,6 +1987,7 @@ mod tests { req_method: &mut None, #[cfg(feature = "ffi")] preserve_header_case: false, + h09_responses: false, }, ) .unwrap()