use crate::NetError;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpHead {
pub status: u16,
pub reason: String,
pub headers: Vec<(String, String)>,
}
impl HttpHead {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HttpBodyMode {
ContentLength(usize),
Chunked,
UntilEof,
Empty,
}
pub fn parse_http_head(head_text: &str) -> Result<HttpHead, NetError> {
let body = head_text.trim_end_matches("\r\n\r\n");
let mut lines = body.split("\r\n");
let status_line = lines
.next()
.ok_or_else(|| NetError::InvalidHead("missing status line".to_owned()))?;
let mut parts = status_line.split_whitespace();
let _version = parts
.next()
.ok_or_else(|| NetError::InvalidHead("missing version".to_owned()))?;
let status = parts
.next()
.ok_or_else(|| NetError::InvalidHead("missing status".to_owned()))?
.parse::<u16>()
.map_err(|_| NetError::InvalidHead("invalid status code".to_owned()))?;
let reason = parts.collect::<Vec<_>>().join(" ");
let mut headers = Vec::new();
for line in lines {
if line.is_empty() {
continue;
}
let (key, value) = line
.split_once(':')
.ok_or_else(|| NetError::InvalidHead("invalid header line".to_owned()))?;
headers.push((key.to_owned(), value.trim().to_owned()));
}
Ok(HttpHead {
status,
reason,
headers,
})
}
pub fn body_mode(head: &HttpHead) -> Result<HttpBodyMode, NetError> {
if let Some(encoding) = head.header("Transfer-Encoding")
&& encoding.eq_ignore_ascii_case("chunked")
{
return Ok(HttpBodyMode::Chunked);
}
match head.header("Content-Length") {
Some(raw) => {
let length = raw
.parse::<usize>()
.map_err(|_| NetError::InvalidHead("invalid content-length".to_owned()))?;
if length == 0 {
Ok(HttpBodyMode::UntilEof)
} else {
Ok(HttpBodyMode::ContentLength(length))
}
}
None => Ok(HttpBodyMode::UntilEof),
}
}