feat(client): allow HTTP/0.9 responses behind a flag (#2473)
Fixes #2468
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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<usize>, | ||||
|     h1_max_buf_size: Option<usize>, | ||||
| @@ -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); | ||||
|                     } | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| #![doc(html_root_url = "https://docs.rs/hyper/0.14.4")] | ||||
| #![deny(missing_docs)] | ||||
| #![deny(missing_debug_implementations)] | ||||
| #![cfg_attr(test, deny(rust_2018_idioms))] | ||||
|   | ||||
| @@ -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, | ||||
| @@ -157,6 +164,9 @@ where | ||||
|  | ||||
|         debug!("incoming body is {}", msg.decode); | ||||
|  | ||||
|         // Prevent accepting HTTP/0.9 responses after the initial one, if any. | ||||
|         self.state.h09_responses = false; | ||||
|  | ||||
|         self.state.busy(); | ||||
|         self.state.keep_alive &= msg.keep_alive; | ||||
|         self.state.version = msg.head.version; | ||||
| @@ -753,6 +763,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, | ||||
|   | ||||
| @@ -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::<ClientTransaction>(cx, parse_ctx) | ||||
|   | ||||
| @@ -72,6 +72,7 @@ pub(crate) struct ParseContext<'a> { | ||||
|     req_method: &'a mut Option<Method>, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     preserve_header_case: bool, | ||||
|     h09_responses: bool, | ||||
| } | ||||
|  | ||||
| /// Passed to Http1Transaction::encode | ||||
|   | ||||
| @@ -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,10 +1274,46 @@ mod tests { | ||||
|             req_method: &mut None, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_case: false, | ||||
|             h09_responses: false, | ||||
|         }; | ||||
|         Server::parse(&mut raw, ctx).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     const H09_RESPONSE: &'static str = "Baguettes are super delicious, don't you agree?"; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_response_h09_allowed() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         let mut raw = BytesMut::from(H09_RESPONSE); | ||||
|         let ctx = ParseContext { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_case: false, | ||||
|             h09_responses: true, | ||||
|         }; | ||||
|         let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); | ||||
|         assert_eq!(raw, H09_RESPONSE); | ||||
|         assert_eq!(msg.head.subject, crate::StatusCode::OK); | ||||
|         assert_eq!(msg.head.version, crate::Version::HTTP_09); | ||||
|         assert_eq!(msg.head.headers.len(), 0); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_parse_response_h09_rejected() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         let mut raw = BytesMut::from(H09_RESPONSE); | ||||
|         let ctx = ParseContext { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_case: false, | ||||
|             h09_responses: false, | ||||
|         }; | ||||
|         Client::parse(&mut raw, ctx).unwrap_err(); | ||||
|         assert_eq!(raw, H09_RESPONSE); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_decoder_request() { | ||||
|         fn parse(s: &str) -> ParsedMessage<RequestLine> { | ||||
| @@ -1276,6 +1325,7 @@ mod tests { | ||||
|                     req_method: &mut None, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 }, | ||||
|             ) | ||||
|             .expect("parse ok") | ||||
| @@ -1291,6 +1341,7 @@ mod tests { | ||||
|                     req_method: &mut None, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 }, | ||||
|             ) | ||||
|             .expect_err(comment) | ||||
| @@ -1505,6 +1556,7 @@ mod tests { | ||||
|                     req_method: &mut Some(Method::GET), | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 } | ||||
|             ) | ||||
|             .expect("parse ok") | ||||
| @@ -1520,6 +1572,7 @@ mod tests { | ||||
|                     req_method: &mut Some(m), | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 }, | ||||
|             ) | ||||
|             .expect("parse ok") | ||||
| @@ -1535,6 +1588,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 +1904,7 @@ mod tests { | ||||
|                 req_method: &mut Some(Method::GET), | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_case: false, | ||||
|                 h09_responses: false, | ||||
|             }, | ||||
|         ) | ||||
|         .expect("parse ok") | ||||
| @@ -1931,6 +1986,7 @@ mod tests { | ||||
|                     req_method: &mut None, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 }, | ||||
|             ) | ||||
|             .unwrap() | ||||
| @@ -1966,6 +2022,7 @@ mod tests { | ||||
|                     req_method: &mut None, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_case: false, | ||||
|                     h09_responses: false, | ||||
|                 }, | ||||
|             ) | ||||
|             .unwrap() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user