use crate::{Digits, Str, is, unwrap, whilst, write_at};
use crate::{HttpError, HttpMethod, HttpStatus, HttpVersion, TextScanner};
#[doc = crate::_tags!(network protocol parser lifetime)]
#[doc = crate::_doc_meta!{location("sys/net/http")}]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HttpRequestLine<'a> {
method: HttpMethod<'a>,
target: &'a str,
version: HttpVersion,
}
impl<'a> HttpRequestLine<'a> {
pub const fn parse(line: &'a [u8]) -> Result<Self, HttpError> {
is! { line.is_empty(), return Err(HttpError::EmptyRequestLine) }
let mut scan = TextScanner::from_bytes(line);
let method_range = scan.take_until_byte(b' ');
is! { method_range.is_empty(), return Err(HttpError::MissingMethod) }
is! { scan.expect_byte(b' ').is_err(), return Err(HttpError::MissingTarget) }
let target_range = scan.take_until_byte(b' ');
is! { target_range.is_empty(), return Err(HttpError::MissingTarget) }
is! { scan.expect_byte(b' ').is_err(), return Err(HttpError::MissingVersion) }
let version_range = scan.take_until_byte(b' ');
is! { version_range.is_empty(), return Err(HttpError::MissingVersion) }
is! { !scan.is_eof(), return Err(HttpError::TrailingData); }
let Some(method_str) = scan.slice_str(method_range) else {
return Err(HttpError::InvalidMethod);
};
let Ok(method) = HttpMethod::parse(method_str) else {
return Err(HttpError::InvalidMethod);
};
let target_bytes = scan.slice(target_range);
is! { !Self::is_valid_target(target_bytes), return Err(HttpError::InvalidTarget) }
let Some(target) = scan.slice_str(target_range) else {
return Err(HttpError::InvalidTarget);
};
let Some(version) = HttpVersion::parse_http1_token(scan.slice(version_range)) else {
return Err(HttpError::InvalidVersion);
};
Ok(Self { method, target, version })
}
#[must_use]
pub const fn method(self) -> HttpMethod<'a> {
self.method
}
#[must_use]
pub const fn target(self) -> &'a str {
self.target
}
#[must_use]
pub const fn version(self) -> HttpVersion {
self.version
}
#[must_use]
pub const fn origin_path(self) -> Option<&'a str> {
let bytes = self.target.as_bytes();
is! { bytes.is_empty() || bytes[0] != b'/', return None }
whilst! { i in 1..bytes.len(); {
if bytes[i] == b'?' { return Some(Str::range_to(self.target, i)); }
}}
Some(self.target)
}
const fn is_valid_target(bytes: &[u8]) -> bool {
is! { bytes.is_empty(), return false }
whilst! { i in 0..bytes.len(); {
let byte = bytes[i];
if byte <= b' ' || byte == 0x7f { return false; }
}}
true
}
}