From b68f6526ec9e6b04adfa9ee80cc175c1d766d36c Mon Sep 17 00:00:00 2001 From: nam2ee Date: Sun, 9 Aug 2026 13:47:10 +0900 Subject: [PATCH 1/2] fix(http1): recognize \n\r\n as a head terminator in the partial-read fast path --- src/proto/h1/role.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index f5c8db5ed0..1ca78d6889 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -106,8 +106,12 @@ fn is_complete_fast(bytes: &[u8], prev_len: usize) -> bool { if bytes[i + 1..].chunks(3).next() == Some(&b"\n\r\n"[..]) { return true; } - } else if b == b'\n' && bytes.get(i + 1) == Some(&b'\n') { - return true; + } else if b == b'\n' { + if bytes.get(i + 1) == Some(&b'\n') + || bytes[i + 1..].chunks(2).next() == Some(&b"\r\n"[..]) + { + return true; + } } } @@ -2977,6 +2981,10 @@ mod tests { for n in 0..s.len() { assert!(is_complete_fast(s, n)); } + let s = b"GET / HTTP/1.1\r\na: b\n\r\n"; + for n in 0..s.len() { + assert!(is_complete_fast(s, n), "{:?}; {}", s, n); + } // Not let s = b"GET / HTTP/1.1\r\na: b\r\n\r"; @@ -2987,6 +2995,36 @@ mod tests { for n in 0..s.len() { assert!(!is_complete_fast(s, n)); } + let s = b"GET / HTTP/1.1\r\na: b\n\r"; + for n in 0..s.len() { + assert!(!is_complete_fast(s, n)); + } + } + + #[cfg(feature = "server")] + #[test] + fn test_parse_accepts_lf_crlf_terminator() { + // The full parser (httparse) accepts a bare-LF line ending followed + // by a CRLF blank line as the end of the head, so the partial-read + // fast path must recognize it too. + let mut bytes = BytesMut::from("GET / HTTP/1.1\r\na: b\n\r\n"); + Server::parse( + &mut bytes, + ParseContext { + cached_headers: &mut None, + req_method: &mut None, + h1_parser_config: Default::default(), + h1_max_headers: None, + preserve_header_case: false, + #[cfg(feature = "ffi")] + preserve_header_order: false, + h09_responses: false, + #[cfg(feature = "client")] + on_informational: &mut None, + }, + ) + .expect("parse ok") + .expect("parse complete"); } #[test] From b5f9aaf2f7a399c4cacd736f56e3db33bc0af90c Mon Sep 17 00:00:00 2001 From: nam2ee Date: Tue, 11 Aug 2026 00:56:32 +0900 Subject: [PATCH 2/2] chore: collapse nested if to satisfy clippy::collapsible_if --- src/proto/h1/role.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 1ca78d6889..29bcd44b0d 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -106,12 +106,11 @@ fn is_complete_fast(bytes: &[u8], prev_len: usize) -> bool { if bytes[i + 1..].chunks(3).next() == Some(&b"\n\r\n"[..]) { return true; } - } else if b == b'\n' { - if bytes.get(i + 1) == Some(&b'\n') - || bytes[i + 1..].chunks(2).next() == Some(&b"\r\n"[..]) - { - return true; - } + } else if b == b'\n' + && (bytes.get(i + 1) == Some(&b'\n') + || bytes[i + 1..].chunks(2).next() == Some(&b"\r\n"[..])) + { + return true; } }