From 5c442cddd8dc36b6ef9ad334d21cdeebb5e5b599 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 5 Oct 2026 14:49:50 -0400 Subject: [PATCH] fix(http1): always remove content-length if transfer-encoding is sent --- src/proto/h1/role.rs | 64 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index f8556db637..4c91319bc7 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -1455,7 +1455,7 @@ impl Client { // This is because we need a second mutable borrow to remove // content-length header. if let Some(encoder) = encoder { - if should_remove_con_len && existing_con_len.is_some() { + if should_remove_con_len { headers.remove(header::CONTENT_LENGTH); } return encoder; @@ -2572,6 +2572,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, @@ -2649,6 +2650,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, @@ -2736,6 +2738,66 @@ mod tests { assert_eq!(unfold("obs\r\n fold\r\n\t line"), "obs fold line",); } + #[test] + fn test_client_request_encode_explicit_transfer_encoding_removes_content_length() { + use crate::proto::BodyLength; + use http::header::HeaderValue; + + for values in [ + vec!["10"], + vec![""], + vec!["abc"], + vec!["+10"], + vec!["18446744073709551616"], + vec!["10", "11"], + vec!["10, 11"], + vec!["10", "abc"], + ] { + for transfer_encoding in ["chunked", "gzip"] { + for body in [BodyLength::Known(10), BodyLength::Unknown] { + let mut head = MessageHead::default(); + for value in &values { + head.headers + .append("content-length", HeaderValue::from_static(value)); + } + head.headers.insert( + "transfer-encoding", + HeaderValue::from_static(transfer_encoding), + ); + + let mut vec = Vec::new(); + let encoder = Client::encode( + Encode { + head: &mut head, + body: Some(body), + #[cfg(feature = "server")] + keep_alive: true, + req_method: &mut None, + title_case_headers: false, + #[cfg(feature = "server")] + date_header: true, + }, + &mut vec, + ) + .unwrap(); + + assert!(encoder.is_chunked()); + let expected_te = if transfer_encoding == "chunked" { + "chunked" + } else { + "gzip, chunked" + }; + assert_eq!( + vec, + format!("GET / HTTP/1.1\r\ntransfer-encoding: {expected_te}\r\n\r\n") + .as_bytes(), + "content-length: {values:?}, transfer-encoding: {transfer_encoding}" + ); + } + } + } + } + #[test] fn test_client_request_encode_title_case() { use crate::proto::BodyLength;