mod test;
use std::str::FromStr;
use smallvec::SmallVec;
use tokio::io::{AsyncBufRead, AsyncBufReadExt};
use crate::{
config::PRE_ALLOCATED_REQUEST_HEADERS,
http::{errors::HttpError, headers::HeaderEntry, methods::HttpMethod},
};
fn parse_uri<'a>(buffer: &'a str) -> Result<(&'a str, &'a str), HttpError> {
let (uri, rest) = parse_to_delimiter(" ", buffer, "Error parsing URI")?;
if !uri.starts_with('/') {
Err(HttpError::BadRequest)
} else {
Ok((uri, rest))
}
}
fn parse_method<'a>(buffer: &'a str) -> Result<(HttpMethod, &'a str), HttpError> {
let (method_str, rest) = parse_to_delimiter(" ", &buffer, "Error parsing method")?;
let method = HttpMethod::from_str(&method_str)
.map_err(|_| format!("Invalid method: \"{}\".", method_str))
.map_err(|_| HttpError::NotImplemented)?;
Ok((method, rest))
}
fn parse_http_version<'a>(buffer: &'a str) -> Result<(u8, u8), HttpError> {
let (http_literal, version_str) =
parse_to_delimiter("/", buffer, "Error parsing HTTP version")?;
if http_literal.trim() != "HTTP" {
return Err(format!("Invalid HTTP version format: {}", buffer))
.map_err(|_| HttpError::BadRequest)?;
}
let version_str = version_str
.strip_suffix("\r\n")
.ok_or("Missing end line in request line")
.map_err(|_| HttpError::BadRequest)?;
let (major, minor) = version_str
.split_once('.')
.or(Some((version_str, "0")))
.ok_or(HttpError::BadRequest)?;
let major = major.parse::<u8>().map_err(|_| HttpError::BadRequest)?;
let minor = minor.parse::<u8>().map_err(|_| HttpError::BadRequest)?;
Ok((major, minor))
}
pub async fn parse_request_line<R: AsyncBufRead + Unpin>(
reader: &mut R,
) -> Result<(HttpMethod, String, (u8, u8)), HttpError> {
let mut buffer = String::with_capacity(128);
reader
.read_line(&mut buffer)
.await
.map_err(|error| format!("Error parsing request line: {}.", error))
.inspect_err(|error| println!("{error}"))
.map_err(|_| HttpError::BadRequest)?;
let (method, rest) = parse_method(&buffer)?;
let (uri, version_part) = parse_uri(rest)?;
let (major, minor) = parse_http_version(version_part)?;
Ok((method, uri.to_owned(), (major, minor)))
}
pub async fn parse_headers<R: AsyncBufRead + Unpin>(
reader: &mut R,
) -> Result<SmallVec<[HeaderEntry; PRE_ALLOCATED_REQUEST_HEADERS]>, String> {
let mut headers = SmallVec::new();
let mut buffer = String::with_capacity(256);
loop {
buffer.clear();
let bytes_read = reader
.read_line(&mut buffer)
.await
.map_err(|e| e.to_string())?;
if bytes_read == 0 {
return Err("Unexpected EOF".to_string());
}
if buffer == "\r\n" {
break;
}
let header = buffer
.strip_suffix("\r\n")
.ok_or("Missing end line in header")?
.trim()
.to_string();
match HeaderEntry::from_str(header.as_str()) {
Ok(header_entry) => {
headers.push(header_entry);
}
_ => {}
}
}
Ok(headers)
}
fn parse_to_delimiter<'a, 'b>(
delimiter: &'a str,
content: &'a str,
error_message: &'b str,
) -> Result<(&'a str, &'a str), HttpError> {
match content.split_once(delimiter) {
Some((part, rest)) => Ok((part, rest)),
None => {
println!("{error_message}");
Err(HttpError::BadRequest)
}
}
}