use crate::error::{EncodeError, Error};
use crate::status_code::StatusClass;
use crate::validate::{
is_valid_field_value, is_valid_header_name, is_valid_method, is_valid_protocol_version,
is_valid_reason_phrase, is_valid_request_target, is_valid_status_code,
};
use alloc::string::String;
use alloc::vec::Vec;
pub trait HttpHead {
fn version(&self) -> &str;
fn headers(&self) -> &[(String, String)];
fn get_header(&self, name: &str) -> Option<&str> {
self.headers()
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
fn get_headers(&self, name: &str) -> Vec<&str> {
self.headers()
.iter()
.filter(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
.collect()
}
fn has_header(&self, name: &str) -> bool {
self.headers()
.iter()
.any(|(n, _)| n.eq_ignore_ascii_case(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.eq_ignore_ascii_case("Connection") {
continue;
}
for token in value.split(',') {
let token = token.trim();
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.eq_ignore_ascii_case("Transfer-Encoding") {
continue;
}
for token in value.split(',') {
let token = token.trim();
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: String,
pub(crate) uri: String,
pub(crate) version: String,
pub(crate) headers: Vec<(String, String)>,
}
impl RequestHead {
pub fn new(method: &str, uri: &str) -> Result<Self, EncodeError> {
Self::with_version(method, uri, "HTTP/1.1")
}
pub fn with_version(method: &str, uri: &str, version: &str) -> Result<Self, EncodeError> {
if !is_valid_method(method) {
return Err(EncodeError::InvalidMethod {
method: method.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: method.into(),
uri: uri.into(),
version: version.into(),
headers: Vec::new(),
})
}
pub fn header(mut self, name: &str, value: &str) -> Result<Self, EncodeError> {
self.add_header(name, value)?;
Ok(self)
}
pub fn add_header(&mut self, name: &str, value: &str) -> Result<&mut Self, EncodeError> {
if !is_valid_header_name(name) {
return Err(EncodeError::InvalidHeaderName { name: name.into() });
}
if !is_valid_field_value(value) {
return Err(EncodeError::InvalidHeaderValue {
name: name.into(),
value: value.into(),
});
}
self.headers.push((name.into(), value.into()));
Ok(self)
}
#[must_use]
pub fn method(&self) -> &str {
&self.method
}
#[must_use]
pub fn uri(&self) -> &str {
&self.uri
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
#[must_use]
pub fn headers(&self) -> &[(String, String)] {
&self.headers
}
#[cfg(debug_assertions)]
pub(crate) fn from_validated_parts(
method: String,
uri: String,
version: String,
headers: Vec<(String, String)>,
) -> Self {
debug_assert!(is_valid_method(&method), "method must be valid token");
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 (name, value) in &headers {
debug_assert!(
is_valid_header_name(name),
"header name must be valid token"
);
debug_assert!(is_valid_field_value(value), "header value must be valid");
}
Self {
method,
uri,
version,
headers,
}
}
#[cfg(not(debug_assertions))]
pub(crate) fn from_validated_parts(
method: String,
uri: String,
version: String,
headers: Vec<(String, String)>,
) -> Self {
Self {
method,
uri,
version,
headers,
}
}
}
impl HttpHead for RequestHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(String, 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<(String, 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: &str, value: &str) -> Result<Self, EncodeError> {
self.add_header(name, value)?;
Ok(self)
}
pub fn add_header(&mut self, name: &str, value: &str) -> Result<&mut Self, EncodeError> {
if !is_valid_header_name(name) {
return Err(EncodeError::InvalidHeaderName { name: name.into() });
}
if !is_valid_field_value(value) {
return Err(EncodeError::InvalidHeaderValue {
name: name.into(),
value: value.into(),
});
}
self.headers.push((name.into(), 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) -> &[(String, String)] {
&self.headers
}
#[cfg(debug_assertions)]
pub(crate) fn from_validated_parts(
version: String,
status_code: u16,
reason_phrase: String,
headers: Vec<(String, 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 (name, value) in &headers {
debug_assert!(
is_valid_header_name(name),
"header name must be valid token"
);
debug_assert!(is_valid_field_value(value), "header value must be valid");
}
Self {
version,
status_code,
reason_phrase,
headers,
}
}
#[cfg(not(debug_assertions))]
pub(crate) fn from_validated_parts(
version: String,
status_code: u16,
reason_phrase: String,
headers: Vec<(String, String)>,
) -> Self {
Self {
version,
status_code,
reason_phrase,
headers,
}
}
}
impl HttpHead for ResponseHead {
fn version(&self) -> &str {
&self.version
}
fn headers(&self) -> &[(String, String)] {
&self.headers
}
}