From 8b86fddaf8f903b1fdf10df9384822b93ebd7ea5 Mon Sep 17 00:00:00 2001 From: Pierre Le Fevre Date: Fri, 13 Mar 2026 07:02:56 +0100 Subject: [PATCH] Fix case-insensitive header matching in body strategy detection Replace strip_prefix-based header matching (only matched exact case and lowercase) with a proper case-insensitive helper that splits on the first colon and compares the header name using eq_ignore_ascii_case. Adds tests for all-caps header names (CONTENT-LENGTH, TRANSFER-ENCODING). Co-Authored-By: Claude Opus 4.6 --- crates/net/src/client.rs | 42 ++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/net/src/client.rs b/crates/net/src/client.rs index 90f6f27..11ecae9 100644 --- a/crates/net/src/client.rs +++ b/crates/net/src/client.rs @@ -477,6 +477,16 @@ enum BodyStrategy { ReadUntilClose, } +/// Extract the value for a header name (case-insensitive match). +fn header_value<'a>(line: &'a str, name: &str) -> Option<&'a str> { + let colon = line.find(':')?; + if line[..colon].eq_ignore_ascii_case(name) { + Some(line[colon + 1..].trim()) + } else { + None + } +} + /// Determine how to read the body from headers. fn determine_body_strategy(headers: &str, status_code: u16) -> BodyStrategy { // 1xx, 204, 304 have no body @@ -486,11 +496,8 @@ fn determine_body_strategy(headers: &str, status_code: u16) -> BodyStrategy { // Check for Transfer-Encoding: chunked for line in headers.split("\r\n").skip(1) { - if let Some(val) = line - .strip_prefix("Transfer-Encoding:") - .or_else(|| line.strip_prefix("transfer-encoding:")) - { - if val.trim().eq_ignore_ascii_case("chunked") { + if let Some(val) = header_value(line, "transfer-encoding") { + if val.eq_ignore_ascii_case("chunked") { return BodyStrategy::Chunked; } } @@ -498,11 +505,8 @@ fn determine_body_strategy(headers: &str, status_code: u16) -> BodyStrategy { // Check for Content-Length for line in headers.split("\r\n").skip(1) { - if let Some(val) = line - .strip_prefix("Content-Length:") - .or_else(|| line.strip_prefix("content-length:")) - { - if let Ok(len) = val.trim().parse::() { + if let Some(val) = header_value(line, "content-length") { + if let Ok(len) = val.parse::() { return BodyStrategy::ContentLength(len); } } @@ -846,6 +850,24 @@ mod tests { )); } + #[test] + fn strategy_content_length_mixed_case() { + let headers = "HTTP/1.1 200 OK\r\nCONTENT-LENGTH: 99"; + match determine_body_strategy(headers, 200) { + BodyStrategy::ContentLength(99) => {} + _ => panic!("expected ContentLength(99)"), + } + } + + #[test] + fn strategy_chunked_mixed_case_name() { + let headers = "HTTP/1.1 200 OK\r\nTRANSFER-ENCODING: chunked"; + assert!(matches!( + determine_body_strategy(headers, 200), + BodyStrategy::Chunked + )); + } + // -- URL scheme handling -- #[test] -- 2.51.2