use crate::error::{EncodeError, Error};
use crate::header_name::HeaderName;
use crate::method::Method;
use crate::status_code::StatusClass;
use crate::validate::{
is_valid_field_value, is_valid_protocol_version, is_valid_reason_phrase,
is_valid_request_target, is_valid_status_code, trim_ows,
};
use alloc::string::String;
use alloc::vec::Vec;
pub trait HttpHead {
fn version(&self) -> &str;
fn headers(&self) -> &[(HeaderName, String)];
fn get_header(&self, name: &str) -> Option<&str> {
self.headers()
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| v.as_str())
}
fn get_headers(&self, name: &str) -> Vec<&str> {
self.headers()
.iter()
.filter(|(n, _)| n == name)
.map(|(_, v)| v.as_str())
.collect()
}
fn has_header(&self, name: &str) -> bool {
self.headers().iter().any(|(n, _)| n == name)
}
fn connection(&self) -> Option<&str> {
self.get_header("Connection")
}
fn is_keep_alive(&self) -> bool {
let mut has_keep_alive = false;
for (name, value) in self.headers() {
if name != "Connection" {
continue;
}
for token in value.split(',') {
let token = trim_ows(token);
if token.eq_ignore_ascii_case("close") {
return false;
}
if token.eq_ignore_ascii_case("keep-alive") {
has_keep_alive = true;
}
}
}
if has_keep_alive {
return true;
}
self.version() == "HTTP/1.1"
}
fn content_length(&self) -> Result<Option<u64>, Error> {
crate::decoder::body::parse_content_length(self.headers())
}
fn is_chunked(&self) -> bool {
let mut last_token: Option<&str> = None;
for (name, value) in self.headers() {
if name != "Transfer-Encoding" {
continue;
}
for token in value.split(',') {
let token = trim_ows(token);
if !token.is_empty() {
last_token = Some(token);
}
}
}
last_token.is_some_and(|t| t.eq_ignore_ascii_case("chunked"))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct RequestHead {
pub(crate) method: Method,
pub(crate) uri: String,
pub(crate) version: String,
pub(crate) headers: Vec<(HeaderName, String)>,
}
impl RequestHead {
pub fn new(
method: impl TryInto<Method, Error: Into<EncodeError>>,
uri: &str,
) -> Result<Self, EncodeError> {
Self::with_version(method, uri, "HTTP/1.1")
}
pub fn with_version(
method: impl TryInto<Method, Error: Into<EncodeError>>,
uri: &str,
version: &str,
) -> Result<Self, EncodeError> {
let method: Method = method.try_into().map_err(Into::into)?;
if !is_valid_request_target(uri) {
return Err(EncodeError::InvalidRequestTarget { uri: uri.into() });
}
if !is_valid_protocol_version(version) {
return Err(EncodeError::InvalidVersion {
version: version.into(),
});
}
Ok(Self {
method,
uri: uri.into(),
version: version.into(),
headers: Vec::new(),
})
}
pub fn header(
mut self,
name: impl TryInto<HeaderName, Error: Into<EncodeError>>,
value: &str,
) -> Result<Self, EncodeError> {
self.add_header(name, value)?;
Ok(self)
}
pub fn add_header(
&mut self,
name: impl TryInto<HeaderName, Error: Into<EncodeError>>,
value: &str,
) -> Result<&mut Self, EncodeError> {
let name: HeaderName = name.try_into().map_err(Into::into)?;
if !is_valid_field_value(value) {
return Err(EncodeError::InvalidHeaderValue {
name: name.as_str().into(),
value: value.into(),
});
}
self.headers.push((name, value.into()));
Ok(self)
}
#[must_use]
pub fn method(&self) -> &str {
self.method.as_str()
}
#[must_use]
pub fn uri(&self) -> &str {
&self.uri
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
#[must_use]
pub fn headers(&self) -> &[(HeaderName, String)] {
&self.headers
}
pub(crate) fn from_validated_parts(
method: Method,
uri: String,
version: String,
headers: Vec<(HeaderName, String)>,
) -> Self {
debug_assert!(
is_valid_request_target(&uri),
"uri must be valid request-target"
);
debug_assert!(
is_valid_protocol_version(&version),
"version must be valid HTTP-version"
);
for (_, value) in &headers {
debug_assert!(is_valid_field_value(value), "header value must be valid");
}
Self {
method,
uri,
version,
headers,
}
}
}
impl HttpHead for RequestHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(HeaderName, String)] {
&self.headers
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ResponseHead {
pub(crate) version: String,
pub(crate) status_code: u16,
pub(crate) reason_phrase: String,
pub(crate) headers: Vec<(HeaderName, String)>,
}
impl ResponseHead {
pub fn new(status_code: u16, reason_phrase: &str) -> Result<Self, EncodeError> {
Self::with_version("HTTP/1.1", status_code, reason_phrase)
}
pub fn with_version(
version: &str,
status_code: u16,
reason_phrase: &str,
) -> Result<Self, EncodeError> {
if !is_valid_protocol_version(version) {
return Err(EncodeError::InvalidVersion {
version: version.into(),
});
}
if !is_valid_status_code(status_code) {
return Err(EncodeError::InvalidStatusCode { code: status_code });
}
if !reason_phrase.is_empty() && !is_valid_reason_phrase(reason_phrase) {
return Err(EncodeError::InvalidReasonPhrase {
phrase: reason_phrase.into(),
});
}
Ok(Self {
version: version.into(),
status_code,
reason_phrase: reason_phrase.into(),
headers: Vec::new(),
})
}
pub fn header(
mut self,
name: impl TryInto<HeaderName, Error: Into<EncodeError>>,
value: &str,
) -> Result<Self, EncodeError> {
self.add_header(name, value)?;
Ok(self)
}
pub fn add_header(
&mut self,
name: impl TryInto<HeaderName, Error: Into<EncodeError>>,
value: &str,
) -> Result<&mut Self, EncodeError> {
let name: HeaderName = name.try_into().map_err(Into::into)?;
if !is_valid_field_value(value) {
return Err(EncodeError::InvalidHeaderValue {
name: name.as_str().into(),
value: value.into(),
});
}
self.headers.push((name, value.into()));
Ok(self)
}
#[must_use]
pub fn status_code(&self) -> u16 {
self.status_code
}
#[must_use]
pub fn reason_phrase(&self) -> &str {
&self.reason_phrase
}
#[must_use]
pub fn status_class(&self) -> StatusClass {
StatusClass::from_status_code(self.status_code)
.expect("status_code is in 100..=599 by construction invariant")
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
#[must_use]
pub fn headers(&self) -> &[(HeaderName, String)] {
&self.headers
}
pub(crate) fn from_validated_parts(
version: String,
status_code: u16,
reason_phrase: String,
headers: Vec<(HeaderName, String)>,
) -> Self {
debug_assert!(
is_valid_protocol_version(&version),
"version must be valid HTTP-version"
);
debug_assert!(
is_valid_status_code(status_code),
"status_code must be in 100..=599"
);
debug_assert!(
reason_phrase.is_empty() || is_valid_reason_phrase(&reason_phrase),
"reason_phrase must be valid or empty"
);
for (_, value) in &headers {
debug_assert!(is_valid_field_value(value), "header value must be valid");
}
Self {
version,
status_code,
reason_phrase,
headers,
}
}
}
impl HttpHead for ResponseHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(HeaderName, String)] {
&self.headers
}
}