use std::collections::HashMap;
use crate::{Error, http::Method};
use super::Request;
use url::Url;
#[derive(Clone, Debug)]
pub struct RequestHeader {
pub(crate) method: Method,
pub(crate) url: Url,
pub(crate) variable_indices: Vec<usize>,
pub(crate) depth: usize,
pub headers: HashMap<String, Vec<String>>,
#[allow(unused)]
pub(crate) header_size: usize,
pub(crate) addr: std::net::SocketAddr,
}
impl RequestHeader {
pub(crate) fn parse(source: &mut Vec<u8>, addr: std::net::SocketAddr) -> Result<RequestHeader, Error> {
let (one, two) = (source.iter(), source.iter().skip(2));
let mut split_index = None;
for (idx, (a, b)) in one.zip(two).enumerate() {
if a==b && b==&b'\n' && idx > 0 && source[idx-1] == b'\r' && source[idx+1] == b'\r' {
split_index = Some(idx);
break;
}
}
let split_index = split_index.ok_or(Error::Parse(format!("no end of header was found")))?;
let mut header: Vec<_> = source.drain((split_index - 1)..).collect();
header.drain(..4);
std::mem::swap(&mut header, source);
let header_size = header.len() + 4;
let header_string = String::from_utf8(header).map_err(|e| Error::Parse(format!("{}", e)))?;
let mut lines = header_string.split("\r\n");
let first_line = lines.next().ok_or(Error::Parse("request has no first line".into()))?;
let tokens = first_line.split(" ").collect::<Vec<_>>();
let (method, path, version) = if tokens.len() != 3 {
return Err(Error::Parse("request's first has incorrect format".into()));
} else {
(
tokens[0].into(), tokens[1],
tokens[2]
)
};
let mut headers = HashMap::new();
for line in lines {
let idx = line.find(":").ok_or(Error::Parse(format!("corrupted header missing colon")))?;
let (key, value) = line.split_at(idx);
let (key, value) = (key.to_string(), value.trim_start_matches(": ").trim_end().to_string());
headers.entry(key).or_insert_with(|| Vec::new()).push(value);
}
if !version.starts_with("HTTP") {
return Err(Error::Parse("unsupported protocol".into()))
}
let host = headers.get("Host").map(|h| h.get(0).map(|v| &v[..])).flatten().unwrap_or_else(|| "missing.host");
let url = Url::parse(&format!("http://{}{}", host, path)).map_err(Error::Url)?;
Ok(RequestHeader {
method,
url,
variable_indices: vec![],
depth: 0,
headers,
header_size,
addr
})
}
pub(crate) fn requests_keep_alive(&self) -> bool {
self.headers.get("Connection").map(|values| values.into_iter().find(|v| *v == "keep-alive")).flatten().is_some()
}
pub(crate) fn content(self, content: Vec<u8>) -> Request {
Request {
header: self,
content
}
}
}