use crate::http_head::HeadError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Framing {
Empty,
Length(u64),
Chunked,
UntilClose,
}
pub(crate) fn framing(
fields: &[(String, String)],
assume_close: bool,
) -> Result<Framing, HeadError> {
let mut lengths = fields
.iter()
.filter(|(name, _)| name.eq_ignore_ascii_case("content-length"))
.map(|(_, value)| value.trim_matches([' ', '\t']));
let mut codings = fields
.iter()
.filter(|(name, _)| name.eq_ignore_ascii_case("transfer-encoding"))
.peekable();
let chunked = codings.peek().is_some();
if chunked {
if lengths.next().is_some() {
return Err(HeadError::ConflictingFraming);
}
let all: Vec<String> = codings
.flat_map(|(_, value)| value.split(','))
.map(|token| token.trim().to_ascii_lowercase())
.filter(|token| !token.is_empty())
.collect();
if all.last().map(String::as_str) != Some("chunked") || all.len() != 1 {
return Err(HeadError::UnsupportedTransferCoding);
}
return Ok(Framing::Chunked);
}
let Some(first) = lengths.next() else {
return Ok(if assume_close {
Framing::UntilClose
} else {
Framing::Empty
});
};
for other in lengths {
if other != first {
return Err(HeadError::ConflictingFraming);
}
}
if first.is_empty() || !first.bytes().all(|b| b.is_ascii_digit()) {
return Err(HeadError::ConflictingFraming);
}
let length = first
.parse::<u64>()
.map_err(|_| HeadError::ConflictingFraming)?;
Ok(Framing::Length(length))
}
pub(crate) fn response_framing(
status: u16,
method: &str,
fields: &[(String, String)],
) -> Result<Framing, HeadError> {
if is_interim(status) || status == 204 || status == 304 || method.eq_ignore_ascii_case("head") {
return Ok(Framing::Empty);
}
framing(fields, true)
}
pub(crate) const fn is_interim(status: u16) -> bool {
status >= 100 && status < 200
}