use crate::{
error,
error::Error,
utils::{
rcvec::RcVec,
rcvecext::{RcVecExt, RcVecU8Ext},
},
};
use std::{
io::{BufReader, Read},
net::TcpStream,
};
#[derive(Debug)]
pub struct Request<T = BufReader<TcpStream>, const HEADER_SIZE_MAX: usize = 4096> {
pub header: RcVec<u8>,
pub method: RcVec<u8>,
pub target: RcVec<u8>,
pub version: RcVec<u8>,
pub fields: Vec<(RcVec<u8>, RcVec<u8>)>,
pub stream: T,
}
impl<T, const HEADER_SIZE_MAX: usize> Request<T, HEADER_SIZE_MAX> {
pub fn from_stream(mut stream: T) -> Result<Option<Self>, Error>
where
T: Read,
{
let header = Self::read_header(&mut stream)?;
if header.is_empty() {
return Ok(None);
}
let mut header_parsing = header.clone();
let (method, target, version) = {
let (method, target, version) = Self::parse_start_line(&mut header_parsing)?;
(method.trim(), target.trim(), version.trim())
};
let mut fields = Vec::new();
while !header_parsing.eq(b"\r\n") {
let (key, value) = Self::parse_field(&mut header_parsing)?;
fields.push((key, value));
}
Ok(Some(Self { header, method, target, version, fields, stream }))
}
fn read_header(stream: &mut T) -> Result<RcVec<u8>, Error>
where
T: Read,
{
let mut header = Vec::with_capacity(HEADER_SIZE_MAX);
'read_loop: for byte in stream.bytes() {
let byte = byte?;
header.push(byte);
if header.ends_with(b"\r\n\r\n") {
break 'read_loop;
}
if header.len() == HEADER_SIZE_MAX {
return Err(error!("HTTP header is too large"));
}
}
header.shrink_to_fit();
let header = RcVec::new(header);
Ok(header)
}
#[allow(clippy::type_complexity)]
fn parse_start_line(header: &mut RcVec<u8>) -> Result<(RcVec<u8>, RcVec<u8>, RcVec<u8>), Error> {
let mut line = header.split_off(b"\r\n").ok_or(error!("Truncated HTTP start line: {header}"))?;
let method = line.split_off(b" ").ok_or(error!("Invalid HTTP start line: {line}"))?;
let target = line.split_off(b" ").ok_or(error!("Invalid HTTP start line: {line}"))?;
Ok((method, target, line))
}
fn parse_field(header: &mut RcVec<u8>) -> Result<(RcVec<u8>, RcVec<u8>), Error> {
let mut line = header.split_off(b"\r\n").ok_or(error!("Truncated HTTP header field: {header}"))?;
let key = line.split_off(b":").ok_or(error!("Invalid HTTP header field: {line}"))?;
let key = key.trim();
let value = line.trim();
Ok((key, value))
}
}