The chunk-size line admits whitespace only before an extension
RFC 9112 7.1 allows whitespace before a chunk extension and nowhere else. The reader trimmed both ends of the size, so " 3" and "3 " were taken. It now splits at the first ';', trims trailing SP and HTAB only when an extension follows, and leaves parse_digits as the digit authority. Rows use an honest body so only the size line differs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5f8417589d
commit
bf5347c370
1 changed files with 39 additions and 3 deletions
|
|
@ -5813,10 +5813,16 @@ fn request_http_snapshot(target: SocketAddr, payload: &[u8]) -> Result<Vec<u8>,
|
|||
let mut response = Vec::new();
|
||||
loop {
|
||||
let size = answer.read_line(ROUTE_HTTP_CHUNK_LINE_BYTES, body)?;
|
||||
// RFC 9112 7.1: `chunk-size [ chunk-ext ] CRLF`, where an
|
||||
// extension may follow whitespace. Nothing else is trimmed.
|
||||
let size = std::str::from_utf8(&size)
|
||||
.ok()
|
||||
.and_then(|size| size.split(';').next())
|
||||
.and_then(|size| parse_digits(size.trim_matches([' ', '\t']), 16))
|
||||
.and_then(|line| match line.split_once(';') {
|
||||
Some((digits, _extension)) => {
|
||||
parse_digits(digits.trim_end_matches([' ', '\t']), 16)
|
||||
}
|
||||
None => parse_digits(line, 16),
|
||||
})
|
||||
.context("stable CultNet HTTP chunk size is invalid")
|
||||
.map_err(refused)?;
|
||||
if size == 0 {
|
||||
|
|
@ -8786,9 +8792,15 @@ mod tests {
|
|||
|
||||
/// An honest chunked answer with `after_body` in place of its end.
|
||||
fn chunked_honest_then(id: &str, after_body: &[u8]) -> Vec<u8> {
|
||||
chunked_honest_sized(id, "", "", after_body)
|
||||
}
|
||||
|
||||
/// An honest chunked answer whose chunk-size line is the hex length between
|
||||
/// `before` and `after`.
|
||||
fn chunked_honest_sized(id: &str, before: &str, after: &str, after_body: &[u8]) -> Vec<u8> {
|
||||
let body = honest_body(id);
|
||||
let mut reply = CHUNKED_HEAD.to_vec();
|
||||
reply.extend_from_slice(format!("{:x}\r\n", body.len()).as_bytes());
|
||||
reply.extend_from_slice(format!("{before}{:x}{after}\r\n", body.len()).as_bytes());
|
||||
reply.extend_from_slice(&body);
|
||||
reply.extend_from_slice(b"\r\n");
|
||||
reply.extend_from_slice(after_body);
|
||||
|
|
@ -8803,6 +8815,30 @@ mod tests {
|
|||
challenge(scripted_peer(|id| chunked_honest_then(id, b"0\r\nT: 1\r\nU: 2\r\n\r\n")), |_| {})
|
||||
.expect("an honest chunked answer with trailers");
|
||||
|
||||
// RFC 9112 7.1: whitespace may precede a chunk extension and nothing
|
||||
// else. The answer is otherwise honest, so only the size line differs.
|
||||
for (before, after) in [("", ";x"), ("", " ;x"), ("", "\t;x"), ("", ";x=1;y")] {
|
||||
challenge(
|
||||
scripted_peer(move |id| chunked_honest_sized(id, before, after, b"0\r\n\r\n")),
|
||||
|_| {},
|
||||
)
|
||||
.unwrap_or_else(|error| panic!("size line {before:?}+size+{after:?}: {error:?}"));
|
||||
}
|
||||
for (why, before, after) in [
|
||||
("whitespace before the chunk size", " ", ""),
|
||||
("a tab before the chunk size", "\t", ""),
|
||||
("whitespace after the chunk size and no extension", "", " "),
|
||||
("whitespace before the size, ahead of an extension", " ", ";x"),
|
||||
] {
|
||||
assert_refused(
|
||||
challenge(
|
||||
scripted_peer(move |id| chunked_honest_sized(id, before, after, b"0\r\n\r\n")),
|
||||
|_| {},
|
||||
),
|
||||
why,
|
||||
);
|
||||
}
|
||||
|
||||
// A trailer that begins and then the peer stops.
|
||||
assert_refused(
|
||||
challenge(scripted_peer(|id| chunked_honest_then(id, b"0\r\nT: 1\r\n")), |_| {}),
|
||||
|
|
|
|||
Loading…
Reference in a new issue