A status line splits on single spaces only, and every chunk line must end in CRLF. The bounded `as` casts in http.rs, handshake.rs and proto's sha1.rs become try_from and from. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
58 lines
1.7 KiB
Rust
58 lines
1.7 KiB
Rust
//! The HTTP reader is strict where it can be: a status line with anything but single spaces, and a
|
|
//! chunk line not ended by CRLF, are protocol errors (M4a review, finding 3).
|
|
|
|
use std::io::Cursor;
|
|
|
|
use gatewayd::http::{HttpError, read_body, read_head};
|
|
|
|
fn read(response: &[u8]) -> Result<Vec<u8>, HttpError> {
|
|
let mut c = Cursor::new(response.to_vec());
|
|
let head = read_head(&mut c)?;
|
|
read_body(&mut c, &head)
|
|
}
|
|
|
|
#[test]
|
|
fn a_status_line_has_single_spaces() {
|
|
for line in [
|
|
"HTTP/1.1 200 OK",
|
|
"HTTP/1.1 200 OK",
|
|
" HTTP/1.1 200 OK",
|
|
"HTTP/1.1\t200 OK",
|
|
] {
|
|
let response = format!("{line}\r\nContent-Length: 0\r\n\r\n");
|
|
let got = read(response.as_bytes());
|
|
if line == "HTTP/1.1 200 OK" {
|
|
// The reason phrase is free text: two spaces inside it are allowed.
|
|
assert!(got.is_ok(), "{line:?}: {got:?}");
|
|
} else {
|
|
assert!(
|
|
matches!(got, Err(HttpError::Protocol(_))),
|
|
"{line:?}: {got:?}"
|
|
);
|
|
}
|
|
}
|
|
assert!(
|
|
read(b"HTTP/1.1 204\r\n\r\n").is_ok(),
|
|
"a status line without a reason phrase"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn chunk_lines_end_in_crlf() {
|
|
let head = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n";
|
|
let good = format!("{head}2\r\nab\r\n0\r\n\r\n");
|
|
assert_eq!(read(good.as_bytes()).unwrap(), b"ab");
|
|
for body in [
|
|
"2\nab\r\n0\r\n\r\n",
|
|
"2\r\nab\n0\r\n\r\n",
|
|
"2\r\nab\r\n0\n\r\n",
|
|
"2\r\nab\r\n0\r\n\n",
|
|
] {
|
|
let got = read(format!("{head}{body}").as_bytes());
|
|
assert!(
|
|
matches!(got, Err(HttpError::Protocol(_))),
|
|
"{body:?}: {got:?}"
|
|
);
|
|
}
|
|
}
|