Skip to content

Commit 3534d75

Browse files
authored
fix(http1): recognize \n\r\n as a head terminator in the partial-read fast path (#4147)
`is_complete_fast` recognizes `\r\n\r\n` and `\n\n` as head terminators but not `\n\r\n`, while the full parser (httparse) accepts all three. So a request whose head ends with `\n\r\n` parses fine when it arrives in a single read, but stalls when it arrives split across reads: the fast path never reports the head complete and the connection keeps waiting for more bytes. This extends the `\n` branch to also accept a following `\r\n`, using the same panic-safe slicing idiom as the `\r` branch. Added the `\n\r\n` witness and the `\n\r` negative to `test_is_complete_fast`, plus a parse-level test documenting that the full parser accepts this terminator. Closes #4145
1 parent fa3a4b2 commit 3534d75

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

src/proto/h1/role.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ fn is_complete_fast(bytes: &[u8], prev_len: usize) -> bool {
106106
if bytes[i + 1..].chunks(3).next() == Some(&b"\n\r\n"[..]) {
107107
return true;
108108
}
109-
} else if b == b'\n' && bytes.get(i + 1) == Some(&b'\n') {
109+
} else if b == b'\n'
110+
&& (bytes.get(i + 1) == Some(&b'\n')
111+
|| bytes[i + 1..].chunks(2).next() == Some(&b"\r\n"[..]))
112+
{
110113
return true;
111114
}
112115
}
@@ -2977,6 +2980,10 @@ mod tests {
29772980
for n in 0..s.len() {
29782981
assert!(is_complete_fast(s, n));
29792982
}
2983+
let s = b"GET / HTTP/1.1\r\na: b\n\r\n";
2984+
for n in 0..s.len() {
2985+
assert!(is_complete_fast(s, n), "{:?}; {}", s, n);
2986+
}
29802987

29812988
// Not
29822989
let s = b"GET / HTTP/1.1\r\na: b\r\n\r";
@@ -2987,6 +2994,36 @@ mod tests {
29872994
for n in 0..s.len() {
29882995
assert!(!is_complete_fast(s, n));
29892996
}
2997+
let s = b"GET / HTTP/1.1\r\na: b\n\r";
2998+
for n in 0..s.len() {
2999+
assert!(!is_complete_fast(s, n));
3000+
}
3001+
}
3002+
3003+
#[cfg(feature = "server")]
3004+
#[test]
3005+
fn test_parse_accepts_lf_crlf_terminator() {
3006+
// The full parser (httparse) accepts a bare-LF line ending followed
3007+
// by a CRLF blank line as the end of the head, so the partial-read
3008+
// fast path must recognize it too.
3009+
let mut bytes = BytesMut::from("GET / HTTP/1.1\r\na: b\n\r\n");
3010+
Server::parse(
3011+
&mut bytes,
3012+
ParseContext {
3013+
cached_headers: &mut None,
3014+
req_method: &mut None,
3015+
h1_parser_config: Default::default(),
3016+
h1_max_headers: None,
3017+
preserve_header_case: false,
3018+
#[cfg(feature = "ffi")]
3019+
preserve_header_order: false,
3020+
h09_responses: false,
3021+
#[cfg(feature = "client")]
3022+
on_informational: &mut None,
3023+
},
3024+
)
3025+
.expect("parse ok")
3026+
.expect("parse complete");
29903027
}
29913028

29923029
#[test]

0 commit comments

Comments
 (0)