//! 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, 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:?}" ); } }