#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Framing {
None,
Length(u64),
Chunked,
}
pub(crate) fn framing(head: &[u8]) -> Framing {
let mut headers = [httparse::EMPTY_HEADER; crate::request::MAX_HEADERS + 2];
let mut req = httparse::Request::new(&mut headers);
if !matches!(req.parse(head), Ok(httparse::Status::Complete(_))) {
return Framing::None;
}
for h in req.headers.iter() {
if h.name.eq_ignore_ascii_case("transfer-encoding") {
return Framing::Chunked;
}
if h.name.eq_ignore_ascii_case("content-length") {
return std::str::from_utf8(h.value)
.ok()
.and_then(|v| v.trim().parse().ok())
.map_or(Framing::None, Framing::Length);
}
}
Framing::None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Malformed;
const MAX_LINE: usize = 8 * 1024;
const MAX_CHUNK: u64 = (1 << 31) - 1;
#[derive(Debug)]
pub(crate) struct BodyEnd {
state: State,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
Length(u64),
Size {
size: u64,
digits: usize,
len: usize,
part: Part,
},
SizeLf {
size: u64,
},
Data(u64),
DataCr,
DataLf,
Trailer {
len: usize,
},
TrailerLf {
empty: bool,
},
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Part {
Digits,
Space,
Extension,
}
const SIZE_START: State = State::Size {
size: 0,
digits: 0,
len: 0,
part: Part::Digits,
};
impl BodyEnd {
pub(crate) fn new(framing: Framing) -> Self {
let state = match framing {
Framing::None | Framing::Length(0) => State::Done,
Framing::Length(n) => State::Length(n),
Framing::Chunked => SIZE_START,
};
Self { state }
}
pub(crate) fn done(&self) -> bool {
self.state == State::Done
}
pub(crate) fn feed(&mut self, bytes: &[u8]) -> Result<usize, Malformed> {
let mut at = 0;
while at < bytes.len() && !self.done() {
at += self.step(&bytes[at..])?;
}
Ok(at)
}
fn step(&mut self, bytes: &[u8]) -> Result<usize, Malformed> {
let available = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
match self.state {
State::Length(left) | State::Data(left) => {
let take = left.min(available);
let rest = left - take;
self.state = match (self.state, rest) {
(State::Length(_), 0) => State::Done,
(State::Length(_), _) => State::Length(rest),
(_, 0) => State::DataCr,
_ => State::Data(rest),
};
Ok(usize::try_from(take).unwrap_or(bytes.len()))
}
_ => {
self.byte(bytes[0])?;
Ok(1)
}
}
}
fn byte(&mut self, b: u8) -> Result<(), Malformed> {
self.state = match self.state {
State::Size {
size,
digits,
len,
part,
} => size_line(size, digits, len, part, b)?,
State::SizeLf { size } if b == b'\n' => {
if size == 0 {
State::Trailer { len: 0 }
} else {
State::Data(size)
}
}
State::DataCr if b == b'\r' => State::DataLf,
State::DataLf if b == b'\n' => SIZE_START,
State::Trailer { len } if b == b'\r' => State::TrailerLf { empty: len == 0 },
State::Trailer { len } if len < MAX_LINE && b != b'\n' => {
State::Trailer { len: len + 1 }
}
State::TrailerLf { empty: true } if b == b'\n' => State::Done,
State::TrailerLf { empty: false } if b == b'\n' => State::Trailer { len: 0 },
_ => return Err(Malformed),
};
Ok(())
}
}
fn size_line(size: u64, digits: usize, len: usize, part: Part, b: u8) -> Result<State, Malformed> {
if len >= MAX_LINE {
return Err(Malformed);
}
let next = |size, digits, part| State::Size {
size,
digits,
len: len + 1,
part,
};
match (part, b) {
(_, b'\r') if digits > 0 => Ok(State::SizeLf { size }),
(_, b'\r' | b'\n') => Err(Malformed),
(Part::Extension, _) => Ok(next(size, digits, Part::Extension)),
(Part::Digits | Part::Space, b';') if digits > 0 => Ok(next(size, digits, Part::Extension)),
(Part::Digits | Part::Space, b' ' | b'\t') if digits > 0 => {
Ok(next(size, digits, Part::Space))
}
(Part::Digits, _) => {
let digit = char::from(b).to_digit(16).ok_or(Malformed)?;
let size = size
.checked_mul(16)
.and_then(|s| s.checked_add(u64::from(digit)))
.filter(|s| *s <= MAX_CHUNK)
.ok_or(Malformed)?;
Ok(next(size, digits + 1, Part::Digits))
}
(Part::Space, _) => Err(Malformed),
}
}
#[cfg(test)]
mod tests;