use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::validate::{is_valid_token, trim_ows};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum TrailerError {
Empty,
InvalidFormat,
InvalidFieldName,
ProhibitedField(String),
}
impl fmt::Display for TrailerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TrailerError::Empty => write!(f, "empty Trailer header"),
TrailerError::InvalidFormat => write!(f, "invalid Trailer header format"),
TrailerError::InvalidFieldName => write!(f, "invalid Trailer field name"),
TrailerError::ProhibitedField(name) => {
write!(f, "prohibited trailer field: {}", name)
}
}
}
}
impl core::error::Error for TrailerError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Trailer {
fields: Vec<String>,
}
impl Trailer {
pub fn parse(input: &str) -> Result<Self, TrailerError> {
let input = trim_ows(input);
let mut fields = Vec::new();
for part in input.split(',') {
let name = trim_ows(part);
if name.is_empty() {
continue;
}
if !is_valid_token(name) {
return Err(TrailerError::InvalidFieldName);
}
let lower_name = name.to_ascii_lowercase();
if is_prohibited_trailer_field(&lower_name) {
return Err(TrailerError::ProhibitedField(lower_name));
}
fields.push(lower_name);
}
Ok(Trailer { fields })
}
pub fn fields(&self) -> &[String] {
&self.fields
}
}
impl fmt::Display for Trailer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.fields.join(", "))
}
}
pub fn is_prohibited_trailer_field(name: &str) -> bool {
name.eq_ignore_ascii_case("transfer-encoding")
|| name.eq_ignore_ascii_case("content-length")
|| name.eq_ignore_ascii_case("host")
|| name.eq_ignore_ascii_case("if-match")
|| name.eq_ignore_ascii_case("if-none-match")
|| name.eq_ignore_ascii_case("if-modified-since")
|| name.eq_ignore_ascii_case("if-unmodified-since")
|| name.eq_ignore_ascii_case("if-range")
|| name.eq_ignore_ascii_case("range")
|| name.eq_ignore_ascii_case("expect")
|| name.eq_ignore_ascii_case("te")
|| name.eq_ignore_ascii_case("authorization")
|| name.eq_ignore_ascii_case("proxy-authorization")
|| name.eq_ignore_ascii_case("www-authenticate")
|| name.eq_ignore_ascii_case("proxy-authenticate")
|| name.eq_ignore_ascii_case("cache-control")
|| name.eq_ignore_ascii_case("vary")
|| name.eq_ignore_ascii_case("date")
|| name.eq_ignore_ascii_case("expires")
|| name.eq_ignore_ascii_case("age")
|| name.eq_ignore_ascii_case("set-cookie")
|| name.eq_ignore_ascii_case("content-encoding")
|| name.eq_ignore_ascii_case("content-type")
|| name.eq_ignore_ascii_case("content-range")
|| name.eq_ignore_ascii_case("connection")
|| name.eq_ignore_ascii_case("upgrade")
|| name.eq_ignore_ascii_case("trailer")
}