use crate::{
Conn, Error, Headers, KnownHeaderName, Method, Result, Status, Version,
conn::{ConnInner, ReceivedBodyState, shared::authority_matches_host},
util::{CRLF, is_tchar},
};
use futures_lite::{AsyncRead, AsyncWrite};
use std::borrow::Cow;
pub(crate) enum HeadError<Transport> {
BadRequest(Conn<Transport>),
Fatal(Error),
}
impl<T> Conn<T>
where
T: AsyncRead + AsyncWrite + Unpin + Send + Sync + 'static,
{
pub(super) fn parse_head(
mut self,
head_size: usize,
mut previous_request_headers: Option<Headers>,
) -> Result<Self, HeadError<T>> {
let inner = &mut *self.inner;
let first_line_index = CRLF
.find(&inner.buffer)
.ok_or(HeadError::Fatal(Error::InvalidHead))?;
let RequestLine {
method,
path,
authority,
scheme,
version,
error: mut first_error,
unrecognized_method,
} = RequestLine::parse(&inner.buffer[..first_line_index]);
inner.method = method;
inner.path = path;
inner.authority = authority;
inner.scheme = scheme;
inner.version = version;
let header_bytes = &inner.buffer[first_line_index + 2..head_size];
let parsed = match &mut previous_request_headers {
Some(previous) => {
let parsed = inner
.request_headers
.extend_parse_reusing(header_bytes, previous);
previous.clear();
parsed
}
None => inner.request_headers.extend_parse(header_bytes),
};
inner.spare_request_headers = previous_request_headers;
if let Err(e) = parsed {
first_error.get_or_insert(e);
}
if let Some(default_headers) = inner.context.shared_state().get().cloned() {
inner.response_headers.insert_all(default_headers);
}
inner.buffer.ignore_front(head_size);
inner.request_body_state = initial_request_body_state(&inner.request_headers);
if first_error.is_none() {
first_error = self
.validate_headers_h1()
.and_then(|()| self.validate_request_target())
.err();
}
match first_error.or_else(|| unrecognized_method.map(Error::UnrecognizedMethod)) {
None => {
log::trace!(
"received:\n{} {} {}\n{}",
self.inner.method,
self.inner.path,
self.inner.version,
self.inner.request_headers
);
Ok(self)
}
Some(ref e) => Err(HeadError::BadRequest(self.reject(e))),
}
}
pub(super) fn reject(self, error: &Error) -> Self {
log::debug!("rejecting request: {error}");
self.close_with(error.into())
}
pub(super) fn reject_oversized_head(self) -> Self {
let status = oversized_head_status(&self.inner.buffer);
log::debug!("rejecting request: head longer than allowed ({status})");
self.close_with(status)
}
fn close_with(mut self, status: Status) -> Self {
self.inner.status = Some(status);
self.inner
.response_headers
.insert(KnownHeaderName::Connection, "close");
self
}
fn validate_headers_h1(&self) -> Result<()> {
let ConnInner {
ref request_headers,
version,
method,
..
} = *self.inner;
let content_length = request_headers.get_values(KnownHeaderName::ContentLength);
let transfer_encoding = request_headers.get_values(KnownHeaderName::TransferEncoding);
if let Some(te) = transfer_encoding
&& te
.as_str()
.is_none_or(|te_str| !te_str.eq_ignore_ascii_case("chunked"))
{
return Err(Error::UnexpectedHeader(
KnownHeaderName::TransferEncoding.into(),
));
}
if content_length.is_some() && transfer_encoding.is_some() {
return Err(Error::UnexpectedHeader(
KnownHeaderName::ContentLength.into(),
));
}
crate::util::validate_content_length(content_length)?;
if let Some(expect) = request_headers.get_values(KnownHeaderName::Expect) {
let all_continue = expect.iter().all(|value| {
value.as_str().is_some_and(|value| {
value
.split(',')
.all(|token| token.trim().eq_ignore_ascii_case("100-continue"))
})
});
if !all_continue {
return Err(Error::ExpectationFailed);
}
}
match request_headers.get_values(KnownHeaderName::Host) {
None => {
if version == Version::Http1_1 && method != Method::Connect {
return Err(Error::HeaderMissing(KnownHeaderName::Host.into()));
}
}
Some(host) => {
let valid = host.as_str().is_some_and(|host| {
!host.is_empty()
&& !host
.bytes()
.any(|b| matches!(b, b'@' | b'/' | b',') || b <= b' ')
});
if !valid {
return Err(Error::InvalidHeaderValue(KnownHeaderName::Host.into()));
}
}
}
Ok(())
}
fn validate_request_target(&self) -> Result<()> {
if &*self.inner.path == "*" {
if self.inner.method != Method::Options {
return Err(Error::InvalidHead);
}
} else if self.inner.method == Method::Connect {
} else if self.inner.path.starts_with('/') {
if self.inner.path.contains(['#', '\\']) {
return Err(Error::InvalidHead);
}
} else {
return Err(Error::InvalidHead);
}
if self.inner.method != Method::Connect
&& let Some(authority) = &self.inner.authority
&& let Some(host) = self.inner.request_headers.get_str(KnownHeaderName::Host)
&& !authority_matches_host(authority, host, self.inner.scheme.as_deref())
{
return Err(Error::InvalidHeaderValue(KnownHeaderName::Host.into()));
}
Ok(())
}
}
struct RequestLine {
method: Method,
path: Cow<'static, str>,
authority: Option<Cow<'static, str>>,
scheme: Option<Cow<'static, str>>,
version: Version,
error: Option<Error>,
unrecognized_method: Option<String>,
}
impl RequestLine {
fn parse(first_line: &[u8]) -> Self {
let mut spaces = memchr::memchr_iter(b' ', first_line);
let Some(first_space) = spaces.next() else {
return Self::malformed(Error::MissingMethod);
};
let Some(second_space) = spaces.next() else {
return Self::malformed(Error::RequestPathMissing);
};
let mut error: Option<Error> = None;
let mut unrecognized_method = None;
let method_bytes = &first_line[..first_space];
let method = match Method::parse(method_bytes) {
Ok(method) => method,
Err(Error::UnrecognizedMethod(token)) => {
unrecognized_method = Some(token);
Method::Get
}
Err(e) => {
error = Some(e);
Method::Get
}
};
let version = match Version::parse(&first_line[second_space + 1..]) {
Ok(version) => version,
Err(e) => {
error.get_or_insert(e);
Version::Http1_1
}
};
let target = &first_line[first_space + 1..second_space];
let mut authority = None;
let mut scheme = None;
let path = if target.is_empty() || target.iter().any(|&b| !(0x21..=0x7e).contains(&b)) {
error.get_or_insert(Error::InvalidHead);
Cow::Borrowed("/")
} else {
let target = std::str::from_utf8(target).unwrap_or("/");
if method == Method::Connect {
authority = Some(Cow::Owned(target.to_string()));
Cow::Borrowed("/")
} else if target == "*" {
Cow::Borrowed("*")
} else if target.starts_with('/') {
Cow::Owned(target.to_string())
} else if let Some((parsed_scheme, parsed_authority, parsed_path)) =
split_absolute_form(target)
{
scheme = Some(Cow::Owned(parsed_scheme));
authority = Some(Cow::Owned(parsed_authority));
Cow::Owned(parsed_path)
} else {
Cow::Owned(target.to_string())
}
};
Self {
method,
path,
authority,
scheme,
version,
error,
unrecognized_method,
}
}
fn malformed(error: Error) -> Self {
Self {
method: Method::Get,
path: Cow::Borrowed("/"),
authority: None,
scheme: None,
version: Version::Http1_1,
error: Some(error),
unrecognized_method: None,
}
}
}
fn split_absolute_form(target: &str) -> Option<(String, String, String)> {
let (scheme, rest) = target.split_once("://")?;
let mut scheme_bytes = scheme.bytes();
let valid_scheme = scheme_bytes.next().is_some_and(|b| b.is_ascii_alphabetic())
&& scheme_bytes.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.'));
if !valid_scheme {
return None;
}
let authority_end = rest.find(['/', '?', '#']).unwrap_or(rest.len());
let authority = &rest[..authority_end];
if authority.is_empty() {
return None;
}
let path = &rest[authority_end..];
let path = if path.is_empty() || path.starts_with(['?', '#']) {
format!("/{path}")
} else {
path.to_string()
};
Some((scheme.to_string(), authority.to_string(), path))
}
fn oversized_head_status(head: &[u8]) -> Status {
if CRLF.find(head).is_some() {
return Status::RequestHeaderFieldsTooLarge;
}
let mut spaces = memchr::memchr_iter(b' ', head);
match (spaces.next(), spaces.next()) {
(None, _) if head.iter().all(|&b| is_tchar(b)) => Status::NotImplemented,
(Some(_), None) => Status::UriTooLong,
_ => Status::BadRequest,
}
}
fn initial_request_body_state(request_headers: &Headers) -> ReceivedBodyState {
let chunked = request_headers.has_header(KnownHeaderName::TransferEncoding);
let content_length = if chunked {
None
} else {
request_headers.content_length().or(Some(0))
};
ReceivedBodyState::new_h1(content_length, chunked)
}