gatewayd: strict status lines and chunk lines; no as casts (M4a review, findings 1 and 3)
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>
This commit is contained in:
+18
-12
@@ -171,8 +171,9 @@ pub fn read_head(stream: &mut dyn Read) -> Result<Head, HttpError> {
|
||||
}
|
||||
|
||||
fn parse_status_line(line: &str) -> Result<(&str, u16), HttpError> {
|
||||
// "HTTP/1.1 200 OK": a 1.1 or 1.0 version, then a 3-digit status in 100..=599, then anything.
|
||||
let mut parts = line.split_whitespace();
|
||||
// "HTTP/1.1 200 OK": a 1.1 or 1.0 version, one space, a 3-digit status in 100..=599, then
|
||||
// optionally one space and a free-text reason. Any other spacing is refused.
|
||||
let mut parts = line.splitn(3, ' ');
|
||||
let version = parts.next().unwrap_or("");
|
||||
let code = parts.next().unwrap_or("");
|
||||
if version != "HTTP/1.1" && version != "HTTP/1.0" {
|
||||
@@ -219,7 +220,8 @@ pub fn read_body(stream: &mut dyn Read, head: &Head) -> Result<Vec<u8>, HttpErro
|
||||
}
|
||||
None => {
|
||||
let mut buf = Vec::new();
|
||||
stream.take((MAX_BODY + 1) as u64).read_to_end(&mut buf)?;
|
||||
let limit = u64::try_from(MAX_BODY).map_or(u64::MAX, |n| n.saturating_add(1));
|
||||
stream.take(limit).read_to_end(&mut buf)?;
|
||||
if buf.len() > MAX_BODY {
|
||||
return Err(HttpError::TooLarge("body"));
|
||||
}
|
||||
@@ -260,8 +262,11 @@ fn read_line(stream: &mut dyn Read, cap: usize) -> Result<String, HttpError> {
|
||||
debug_assert_eq!(n, 1);
|
||||
match byte[0] {
|
||||
b'\n' => {
|
||||
if bytes.last() == Some(&b'\r') {
|
||||
bytes.pop();
|
||||
// Lines end in CRLF; a bare LF is not accepted.
|
||||
if bytes.pop() != Some(b'\r') {
|
||||
return Err(HttpError::Protocol(
|
||||
"a line is not ended by CRLF".to_string(),
|
||||
));
|
||||
}
|
||||
return String::from_utf8(bytes)
|
||||
.map_err(|_| HttpError::Protocol("a line is not UTF-8".to_string()));
|
||||
@@ -296,10 +301,11 @@ fn read_chunked(stream: &mut dyn Read) -> Result<Vec<u8>, HttpError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if size > MAX_BODY as u128 - out.len() as u128 {
|
||||
return Err(HttpError::TooLarge("body"));
|
||||
}
|
||||
let size = size as usize;
|
||||
let room = MAX_BODY.saturating_sub(out.len());
|
||||
let size = usize::try_from(size)
|
||||
.ok()
|
||||
.filter(|s| *s <= room)
|
||||
.ok_or(HttpError::TooLarge("body"))?;
|
||||
let mut chunk = vec![0u8; size];
|
||||
stream.read_exact(&mut chunk)?;
|
||||
out.extend_from_slice(&chunk);
|
||||
@@ -318,9 +324,9 @@ fn parse_hex(s: &str) -> Result<u128, HttpError> {
|
||||
let mut value: u128 = 0;
|
||||
for b in s.bytes() {
|
||||
let d = match b {
|
||||
b'0'..=b'9' => (b - b'0') as u128,
|
||||
b'a'..=b'f' => (b - b'a' + 10) as u128,
|
||||
b'A'..=b'F' => (b - b'A' + 10) as u128,
|
||||
b'0'..=b'9' => u128::from(b - b'0'),
|
||||
b'a'..=b'f' => u128::from(b - b'a' + 10),
|
||||
b'A'..=b'F' => u128::from(b - b'A' + 10),
|
||||
_ => return Err(HttpError::Protocol("a chunk size is not hex".to_string())),
|
||||
};
|
||||
value = value
|
||||
|
||||
Reference in New Issue
Block a user