use crate::{Digits, Str, is, unwrap, whilst, write_at};
use crate::{HttpError, HttpMethod, HttpStatus, HttpVersion, TextScanner};
#[doc = crate::_tags!(network protocol)]
#[doc = crate::_doc_meta!{location("sys/net/http")}]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct HttpResponseHead<'a> {
version: HttpVersion,
status: HttpStatus,
content_type: Option<&'a str>,
content_length: Option<usize>,
}
impl<'a> HttpResponseHead<'a> {
#[must_use]
pub const fn new(status: HttpStatus) -> Self {
Self {
version: HttpVersion::Http11,
status,
content_type: None,
content_length: None,
}
}
#[must_use]
pub const fn ok() -> Self {
Self::new(HttpStatus::OK)
}
#[must_use]
pub const fn not_found() -> Self {
Self::new(HttpStatus::NOT_FOUND)
}
#[must_use]
pub const fn with_version(mut self, version: HttpVersion) -> Self {
self.version = version;
self
}
#[must_use]
pub const fn with_content_type(mut self, content_type: &'a str) -> Self {
self.content_type = Some(content_type);
self
}
#[must_use]
pub const fn with_content_length(mut self, len: usize) -> Self {
self.content_length = Some(len);
self
}
#[must_use]
pub const fn version(self) -> HttpVersion {
self.version
}
#[must_use]
pub const fn status(self) -> HttpStatus {
self.status
}
#[must_use]
pub const fn content_type(self) -> Option<&'a str> {
self.content_type
}
#[must_use]
pub const fn content_length(self) -> Option<usize> {
self.content_length
}
pub const fn http1_encoded_len(self) -> Result<usize, HttpError> {
let Some(version) = self.version.http1_token() else {
return Err(HttpError::InvalidVersion);
};
let reason = match self.status.reason() {
Some(reason) => reason,
None => "",
};
let mut len = version.len()
+ 1 + 3 + 1 + reason.len()
+ 2; if let Some(content_type) = self.content_type {
if !Self::is_valid_header_value(content_type.as_bytes()) {
return Err(HttpError::InvalidHeaderValue);
}
len += b"Content-Type: ".len() + content_type.len() + 2; }
if let Some(content_length) = self.content_length {
len += b"Content-Length: ".len() + Digits(content_length).count_digits10() as usize + 2; }
Ok(len + 2) }
pub const fn write_http1_into(self, dst: &mut [u8]) -> Result<usize, HttpError> {
let needed = match self.http1_encoded_len() {
Ok(needed) => needed,
Err(error) => return Err(error),
};
if dst.len() < needed {
return Err(HttpError::NotEnoughSpace(needed));
}
let Some(version) = self.version.http1_token() else {
return Err(HttpError::InvalidVersion);
};
let reason = match self.status.reason() {
Some(reason) => reason,
None => "",
};
let mut pos = 0;
write_at!(dst, +=pos, @version.as_bytes(), b' ');
pos += Digits(self.status.code()).write_digits10(dst, pos);
write_at!(dst, +=pos, b' ', @reason.as_bytes(), @b"\r\n");
if let Some(content_type) = self.content_type {
write_at!(dst, +=pos, @b"Content-Type: ", @content_type.as_bytes(), @b"\r\n");
}
if let Some(content_length) = self.content_length {
write_at!(dst, +=pos, @b"Content-Length: ");
pos += Digits(content_length).write_digits10(dst, pos);
write_at!(dst, +=pos, @b"\r\n");
}
write_at!(dst, +=pos, @b"\r\n");
Ok(pos)
}
const fn is_valid_header_value(bytes: &[u8]) -> bool {
whilst! { i in 0..bytes.len(); {
let byte = bytes[i];
if (byte < b' ' && byte != b'\t') || byte == 0x7f { return false; }
}}
true
}
}