use core::str;
use std::rc::Rc;
use crate::{
constants::MAX_RESPONSE_LENGTH,
http::{
body::Body,
headers::{ContentLength, IntoHeader},
result::{H10LibError, H10LibResult},
status_code::StatusCode,
},
};
use crate::http::{headers::Headers, version::Version};
use super::Response;
#[derive(Debug)]
pub struct ResponseParser;
impl ResponseParser {
pub fn parse(bytes: &[u8]) -> H10LibResult<Response> {
let now = std::time::Instant::now();
if bytes.len() > MAX_RESPONSE_LENGTH {
return Err(H10LibError::ResponseParser(format!(
"Response size is larger than expected. MAX: {} Bytes",
MAX_RESPONSE_LENGTH
)));
}
let headers_region = Self::get_header_region(bytes)?;
let status_line_bytes = Self::get_status_line_bytes(headers_region)?;
let http_version = Self::parse_http_version(status_line_bytes)?;
let status = Self::parse_statuscode(status_line_bytes)?;
println!(
"ResponseParser: Status-Line security check in {} secs",
now.elapsed().as_secs_f64()
);
let valid_bytes = {
let mut found = 0;
for (idx, c) in bytes.iter().enumerate() {
if *c == b'\0' {
found = idx;
break;
}
}
let (valid, _) = bytes.split_at(found);
valid
};
let request_str = std::str::from_utf8(valid_bytes)?;
let rc_request_str: Rc<str> = request_str.into();
let (headers_region, body_region) =
rc_request_str
.split_once("\r\n\r\n")
.ok_or(H10LibError::ResponseParser(
"Invalid HTTP Response on split headers and body".into(),
))?;
let headers = Headers::parse(headers_region)?;
let body: Body = body_region.parse()?;
let body_length_number = body.len();
let maybe_content_length = headers.get(ContentLength::default().into_header().name());
if let Some(content_length) = maybe_content_length {
let content_length_number = content_length.value().parse()?;
if body_length_number != content_length_number {
return Err(H10LibError::InvalidInputData(
format!("Invalid body by Content-Length header. content_length_number: {}, body.len(): {}",content_length_number,body_length_number),
));
}
} else {
if body_length_number > 0 {
return Err(H10LibError::InvalidInputData(
"Invalid body by nonexistence of Content-Length header".into(),
));
}
}
Ok(Response {
http_version,
status,
headers,
body: Some(body),
})
}
fn get_header_region<'a>(raw_request: &'a [u8]) -> H10LibResult<&'a [u8]> {
let seq = b"\r\n\r\n";
let mut i = 0;
while i + seq.len() <= raw_request.len() {
if raw_request[i..i + seq.len()] == *seq {
return Ok(&raw_request[..(i + 2)]);
}
i += 1;
}
Err(H10LibError::ResponseParser(
"Invalid HTTP Response Header region".into(),
))
}
fn get_status_line_bytes<'a>(input: &'a [u8]) -> H10LibResult<&'a [u8]> {
let seq = b"\r\n";
let mut i = 0;
while i + seq.len() <= input.len() {
if input[i..i + seq.len()] == *seq {
return Ok(&input[..i]);
}
i += 1;
}
Err(H10LibError::ResponseParser(
"Invalid HTTP Response header line".into(),
))
}
fn parse_statuscode(input: &[u8]) -> H10LibResult<StatusCode> {
use std::str;
let mut iter = input.split(|b| *b == b' ');
let _discard_version = iter.next();
let bytes = iter.next().ok_or(H10LibError::ResponseParser(
"Malformed bytes HTTP Method line on parsing Method".into(),
))?;
if bytes.len() > StatusCode::MAX_LENGTH {
return Err(H10LibError::ResponseParser(format!(
"HTTP StatusCode payload size is larger than expected. MAX: {} Bytes. Found: {} Bytes",
StatusCode::MAX_LENGTH,
input.len()
)));
}
let method_str = str::from_utf8(bytes)?;
method_str.parse()
}
fn parse_http_version(input: &[u8]) -> H10LibResult<Version> {
let bytes = input
.split(|b| *b == b' ')
.next()
.ok_or(H10LibError::ResponseParser(
"Malformed bytes HTTP Method lineon parsing HTTP Version".into(),
))?;
if bytes.len() > Version::MAX_LENGTH {
return Err(H10LibError::ResponseParser(format!(
"HTTP Version payload size is larger than expected. MAX: {} Bytes. Found: {} Bytes",
Version::MAX_LENGTH,
input.len()
)));
}
let version = std::str::from_utf8(bytes)?;
version.parse()
}
}