use super::{
parse_headers, parse_version,
simd::{match_path_vectored, match_uri_vectored},
HttpParsingError::{self, *},
};
use crate::{Headers, Method, RequestUri};
#[derive(Debug)]
pub struct Request<'b> {
pub method: Method,
pub uri: RequestUri<'b>,
pub http_version: u8,
pub headers: Headers<'b>,
pub buf_offset: usize,
}
impl<'b> Request<'b> {
pub fn parse(buf: &'b [u8]) -> Result<Request<'b>, HttpParsingError> {
let start = buf.len();
let (method, rest) = parse_method(buf)?;
let (uri, rest) = parse_uri(rest)?;
let (http_version, rest) = parse_version(rest)?;
let rest = rest.get(2..).ok_or(UnexpectedEof)?; let (headers, rest) = parse_headers(rest)?;
Ok(Request {
method,
uri,
http_version,
headers,
buf_offset: start - rest.len(),
})
}
}
#[inline]
fn parse_method(buf: &[u8]) -> Result<(Method, &[u8]), HttpParsingError> {
if let Some(rest) = buf.strip_prefix(b"GET ") {
return Ok((Method::Get, rest));
}
if let Some(rest) = buf.strip_prefix(b"POST ") {
return Ok((Method::Post, rest));
}
let mut i = 0;
while i < buf.len() {
let b = buf[i];
if b == b' ' {
let method_bytes = &buf[..i];
let method = match method_bytes {
b"HEAD" => Method::Head,
b"PUT" => Method::Put,
b"PATCH" => Method::Patch,
b"DELETE" => Method::Delete,
b"OPTIONS" => Method::Options,
b"TRACE" => Method::Trace,
_ => {
if !method_bytes.iter().all(|b| b.is_ascii_alphabetic()) {
return Err(MalformedStatusLine);
}
let s = unsafe { std::str::from_utf8_unchecked(method_bytes) };
Method::Custom(s.to_string())
}
};
return Ok((method, &buf[i + 1..]));
}
i += 1;
}
Err(UnexpectedEof) }
#[inline]
fn parse_uri(buf: &[u8]) -> Result<(RequestUri<'_>, &[u8]), HttpParsingError> {
let origin_form = match *buf.first().ok_or(UnexpectedEof)? {
b'/' => true,
b'*' => {
return Ok((
RequestUri::new("*", 0, 1),
buf.get(1..).ok_or(MalformedStatusLine)?,
));
}
_ => false,
};
let mut path_start_i = 0;
let mut i = 0;
if !origin_form {
while i < buf.len() {
let b = buf[i];
match b {
b':' if i + 2 < buf.len() && &buf[i..i + 3] == b"://" => {
i += 3;
continue;
}
b'/' => {
path_start_i = i;
break;
}
b' ' => {
break;
}
_ => {
if !is_valid_uri_byte(b) {
return Err(MalformedStatusLine);
}
}
}
i += 1;
}
if path_start_i == 0 {
let mut j = i;
j += match_uri_vectored(&buf[j..]);
match buf.get(j).copied() {
Some(b' ') => {
let uri = unsafe { core::str::from_utf8_unchecked(&buf[..j]) };
let rest = buf.get(j + 1..).ok_or(MalformedStatusLine)?;
return Ok((RequestUri::new(uri, 0, 0), rest));
}
Some(_) => return Err(MalformedStatusLine),
None => return Err(MalformedStatusLine),
}
}
}
i += match_path_vectored(&buf[i..]);
let path_end_i = i;
if let Some(&b'?') = buf.get(i) {
i += 1; let n = match_uri_vectored(&buf[i..]); i += n;
match buf.get(i).copied() {
Some(b' ') => {} Some(_) => return Err(MalformedStatusLine), None => return Err(UnexpectedEof), }
} else {
match buf.get(i) {
Some(b' ') => {}
Some(_) => return Err(MalformedStatusLine),
None => return Err(UnexpectedEof),
}
}
let uri = unsafe { core::str::from_utf8_unchecked(&buf[..i]) };
let rest = buf.get(i + 1..).ok_or(UnexpectedEof)?; Ok((RequestUri::new(uri, path_start_i, path_end_i), rest))
}
const fn make_uri_byte_mask() -> [bool; 256] {
let mut mask = [false; 256];
let valid =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=%";
let mut i = 0;
while i < valid.len() {
mask[valid[i] as usize] = true;
i += 1;
}
mask
}
static URI_BYTE_MASK: [bool; 256] = make_uri_byte_mask();
#[inline(always)]
pub(crate) fn is_valid_uri_byte(b: u8) -> bool {
URI_BYTE_MASK[b as usize]
}