use crate::util::BytesDebug;
use std::{
error::Error,
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
iter, mem,
str::FromStr,
vec::IntoIter,
};
pub type HeaderField = (FieldName, FieldBody);
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct HeaderFieldError;
impl Display for HeaderFieldError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "failed to process header field")
}
}
impl Error for HeaderFieldError {}
#[derive(Clone, Eq)]
pub struct FieldName(Box<str>);
impl FieldName {
pub fn new(value: impl Into<Box<str>>) -> Result<Self, HeaderFieldError> {
let value = value.into();
if value.is_empty() || !value.chars().all(|c| c.is_ascii_graphic() && c != ':') {
return Err(HeaderFieldError);
}
Ok(Self(value))
}
}
impl AsRef<str> for FieldName {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for FieldName {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Debug for FieldName {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl PartialEq for FieldName {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl PartialEq<&str> for FieldName {
fn eq(&self, other: &&str) -> bool {
self.0.eq_ignore_ascii_case(other)
}
}
impl Hash for FieldName {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_ascii_lowercase().hash(state);
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct FieldBody(Box<[u8]>);
impl FieldBody {
pub fn new(value: impl Into<Box<[u8]>>) -> Result<Self, HeaderFieldError> {
let value = value.into();
for (i, line) in split_crlf(&value).enumerate() {
if line.iter().any(|b| b.is_ascii_control() && *b != b'\t') {
return Err(HeaderFieldError);
}
if i != 0 {
if !line.starts_with(b" ") && !line.starts_with(b"\t") {
return Err(HeaderFieldError);
}
if line.iter().all(|b| matches!(b, b' ' | b'\t')) {
return Err(HeaderFieldError);
}
}
}
Ok(Self(value))
}
}
impl AsRef<[u8]> for FieldBody {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for FieldBody {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &BytesDebug(&self.0))
}
}
fn split_crlf(mut value: &[u8]) -> impl Iterator<Item = &[u8]> {
let mut done = false;
iter::from_fn(move || {
if done {
None
} else if let Some(i) = value.windows(2).position(|b| b == b"\r\n") {
let (chunk, rest) = value.split_at(i);
value = &rest[2..];
Some(chunk)
} else {
done = true;
Some(value)
}
})
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct HeaderFields(Vec<HeaderField>);
impl HeaderFields {
pub fn new(value: impl Into<Vec<HeaderField>>) -> Result<Self, HeaderFieldError> {
let value = value.into();
if value.is_empty() {
return Err(HeaderFieldError);
}
Ok(Self(value))
}
pub fn from_vec(value: Vec<(String, Vec<u8>)>) -> Result<Self, HeaderFieldError> {
let value: Vec<_> = value
.into_iter()
.map(|(name, value)| {
let name = FieldName::new(name)?;
let body = FieldBody::new(value)?;
Ok((name, body))
})
.collect::<Result<_, _>>()?;
Self::new(value)
}
}
impl AsRef<[HeaderField]> for HeaderFields {
fn as_ref(&self) -> &[HeaderField] {
&self.0
}
}
impl From<HeaderFields> for Vec<HeaderField> {
fn from(header_fields: HeaderFields) -> Self {
header_fields.0
}
}
impl IntoIterator for HeaderFields {
type Item = HeaderField;
type IntoIter = IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromStr for HeaderFields {
type Err = HeaderFieldError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut lines = s.lines();
let first_line = lines.next()
.filter(|l| !is_continuation_line(l))
.ok_or(HeaderFieldError)?;
let (mut name, mut value) = split_header_field(first_line)?;
let mut headers = vec![];
for line in lines {
if is_continuation_line(line) {
value.extend(b"\r\n");
value.extend(line.bytes());
} else {
let (next_name, next_value) = split_header_field(line)?;
let name = mem::replace(&mut name, next_name);
let value = mem::replace(&mut value, next_value);
let value = FieldBody::new(value)?;
headers.push((name, value));
}
}
let value = FieldBody::new(value)?;
headers.push((name, value));
Self::new(headers)
}
}
fn is_continuation_line(s: &str) -> bool {
s.starts_with(' ') || s.starts_with('\t')
}
fn split_header_field(s: &str) -> Result<(FieldName, Vec<u8>), HeaderFieldError> {
let (name, value) = s.split_once(':').ok_or(HeaderFieldError)?;
let name = FieldName::new(name)?;
let value = value.bytes().collect();
Ok((name, value))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_name_ok() {
assert!(FieldName::new("abc").is_ok());
assert!(FieldName::new("").is_err());
assert!(FieldName::new("abc ").is_err());
assert!(FieldName::new("a:c").is_err());
}
#[test]
fn field_body_ok() {
assert!(FieldBody::new(*b"").is_ok());
assert!(FieldBody::new(*b"abc").is_ok());
assert!(FieldBody::new(*b" ab\r\n\tcd ").is_ok());
assert!(FieldBody::new(*b"\r\n\ta").is_ok());
assert!(FieldBody::new(*b" ").is_ok());
assert!(FieldBody::new(*b" \r\na").is_err());
assert!(FieldBody::new(*b" \r\n\r\n a").is_err());
assert!(FieldBody::new(*b" \r\n \r\n a").is_err());
assert!(FieldBody::new(*b" \ra").is_err());
assert!(FieldBody::new(*b" \na").is_err());
assert!(FieldBody::new(*b" abc\r\n").is_err());
}
#[test]
fn split_crlf_ok() {
assert!(split_crlf(b"").eq([b""]));
assert!(split_crlf(b"a").eq([b"a"]));
assert!(split_crlf(b"\r").eq([b"\r"]));
assert!(split_crlf(b"\rb").eq([b"\rb"]));
assert!(split_crlf(b"\r\n").eq([b"", b""]));
assert!(split_crlf(b"a\r\n").eq([&b"a"[..], &b""[..]]));
assert!(split_crlf(b"\r\nb").eq([&b""[..], &b"b"[..]]));
assert!(split_crlf(b"\r\n\r\n").eq([b"", b"", b""]));
assert!(split_crlf(b"ab\r\n\r\nc\r\r\n\rde\r").eq([
&b"ab"[..],
&b""[..],
&b"c\r"[..],
&b"\rde\r"[..]
]));
}
#[test]
fn header_fields_ok() {
assert!(HeaderFields::new([
(
FieldName::new("From").unwrap(),
FieldBody::new(*b" me").unwrap()
),
(
FieldName::new("To").unwrap(),
FieldBody::new(*b" you (yes,\r\n\t you!)").unwrap()
),
])
.is_ok());
}
}