diff --git a/crates/fetch/src/http.rs b/crates/fetch/src/http.rs index c95478388a..1002e5205e 100644 --- a/crates/fetch/src/http.rs +++ b/crates/fetch/src/http.rs @@ -519,7 +519,8 @@ impl Response { if *left == 0 { let line = self.conn.line(1024, limits)?; let hex = line.split(';').next().unwrap_or_default().trim(); - let size = u64::from_str_radix(hex, 16).map_err(|_| HttpError::Protocol(format!("bad chunk size `{hex}`")))?; + let size = if hex.bytes().all(|b| b.is_ascii_hexdigit()) { u64::from_str_radix(hex, 16).ok() } else { None } + .ok_or_else(|| HttpError::Protocol(format!("bad chunk size `{hex}`")))?; if size == 0 { // trailers, up to the empty line let mut n = 0; diff --git a/crates/fetch/src/tests.rs b/crates/fetch/src/tests.rs index 08f869dcca..e1e7a5679d 100644 --- a/crates/fetch/src/tests.rs +++ b/crates/fetch/src/tests.rs @@ -86,6 +86,9 @@ fn handle(mut s: TcpStream, hits: &AtomicUsize) { b.extend_from_slice(&[7u8; 5000]); send(&mut s, "200 OK", "", &b) } + "signedchunk" => { + let _ = s.write_all(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n+5\r\nhello\r\n0\r\n\r\n"); + } "chunked" => { let _ = s.write_all(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"); for c in body.chunks(70_001) { @@ -249,6 +252,16 @@ fn a_416_or_a_bad_complete_part_starts_over() { let _ = std::fs::remove_dir_all(&dir); } +#[test] +fn signed_chunk_size_is_rejected() { + let srv = server(); + let dir = tmp("signedchunk"); + let f = FileSpec { name: "vocab.json", size: None, sha256: None, max: 1 << 20 }; + let r = run(&[f], &[format!("{}/signedchunk", srv.base)], &dir, &AtomicBool::new(false)).0; + assert!(r.is_err(), "a chunk size of `+5` was accepted"); + let _ = std::fs::remove_dir_all(&dir); +} + #[test] fn chunked_bodies_and_unpinned_files() { let srv = server();