use std::io::Read;
pub const MAX_BODY: usize = 64 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Framing {
Empty,
Length(usize),
Chunked,
Close,
}
pub(super) fn read(stream: &mut impl Read, mut raw: Vec<u8>, framing: Framing) -> Option<Vec<u8>> {
let limit = match framing {
Framing::Empty => return Some(Vec::new()),
Framing::Length(n) if n > MAX_BODY => return None,
Framing::Length(n) => n,
Framing::Chunked => 2 * MAX_BODY,
Framing::Close => MAX_BODY,
};
let mut chunk = [0u8; 4096];
loop {
match framing {
Framing::Length(n) if raw.len() >= n => {
raw.truncate(n);
return Some(raw);
}
Framing::Chunked => match dechunk(&raw) {
Chunks::Done(body) if body.len() <= MAX_BODY => return Some(body),
Chunks::Done(_) | Chunks::Bad => return None,
Chunks::More => {}
},
_ => {}
}
if raw.len() > limit {
return None;
}
let n = stream.read(&mut chunk).ok()?;
if n == 0 {
return (framing == Framing::Close).then_some(raw);
}
raw.extend_from_slice(&chunk[..n]);
}
}
#[derive(Debug, PartialEq, Eq)]
pub(super) enum Chunks {
Done(Vec<u8>),
More,
Bad,
}
pub(super) fn dechunk(raw: &[u8]) -> Chunks {
let mut body = Vec::new();
let mut at = 0;
loop {
let Some(end) = raw[at..].windows(2).position(|w| w == b"\r\n") else {
return Chunks::More;
};
let line = &raw[at..at + end];
let size = line.split(|b| *b == b';').next().unwrap_or_default();
let Some(size) = std::str::from_utf8(size)
.ok()
.and_then(|s| usize::from_str_radix(s.trim(), 16).ok())
else {
return Chunks::Bad;
};
at += end + 2;
if size == 0 {
return Chunks::Done(body);
}
let Some(data_end) = at.checked_add(size) else {
return Chunks::Bad;
};
if raw.len() < data_end.saturating_add(2) {
return Chunks::More;
}
if &raw[data_end..data_end + 2] != b"\r\n" {
return Chunks::Bad;
}
body.extend_from_slice(&raw[at..data_end]);
at = data_end + 2;
}
}