use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::hash::{Hash, Hasher};
use crate::validate::is_token_char;
#[derive(Debug, Clone)]
pub struct HeaderName(Cow<'static, [u8]>);
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HeaderNameError {
Empty { input: String },
InvalidByte {
byte: u8,
position: usize,
input: String,
},
}
impl HeaderNameError {
pub fn input(&self) -> &str {
match self {
HeaderNameError::Empty { input } => input,
HeaderNameError::InvalidByte { input, .. } => input,
}
}
pub fn into_input(self) -> String {
match self {
HeaderNameError::Empty { input } => input,
HeaderNameError::InvalidByte { input, .. } => input,
}
}
}
impl fmt::Display for HeaderNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeaderNameError::Empty { input } => {
write!(f, "empty header name: {:?}", input)
}
HeaderNameError::InvalidByte {
byte,
position,
input,
} => {
write!(
f,
"invalid byte 0x{:02X} at position {} in header name: {:?}",
byte, position, input
)
}
}
}
}
impl core::error::Error for HeaderNameError {}
impl HeaderName {
pub fn new(name: impl AsRef<[u8]>) -> Result<Self, HeaderNameError> {
let bytes = name.as_ref();
if bytes.is_empty() {
return Err(HeaderNameError::Empty {
input: String::from_utf8_lossy(bytes).into_owned(),
});
}
let mut i = 0;
while i < bytes.len() {
if !is_token_char(bytes[i]) {
return Err(HeaderNameError::InvalidByte {
byte: bytes[i],
position: i,
input: String::from_utf8_lossy(bytes).into_owned(),
});
}
i += 1;
}
Ok(Self(Cow::Owned(bytes.to_vec())))
}
pub const fn from_static(name: &'static [u8]) -> Self {
if name.is_empty() {
panic!("HeaderName: empty header name");
}
let mut i = 0;
while i < name.len() {
if !is_token_char(name[i]) {
panic!("HeaderName: invalid byte in header name");
}
i += 1;
}
Self(Cow::Borrowed(name))
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
pub(crate) fn from_validated_bytes(name: Vec<u8>) -> Self {
debug_assert!(!name.is_empty() && name.iter().all(|&b| is_token_char(b)));
Self(Cow::Owned(name))
}
}
impl TryFrom<&'static str> for HeaderName {
type Error = HeaderNameError;
fn try_from(s: &'static str) -> Result<Self, Self::Error> {
let bytes = s.as_bytes();
if bytes.is_empty() {
return Err(HeaderNameError::Empty {
input: s.to_string(),
});
}
for (i, &b) in bytes.iter().enumerate() {
if !is_token_char(b) {
return Err(HeaderNameError::InvalidByte {
byte: b,
position: i,
input: s.to_string(),
});
}
}
Ok(Self(Cow::Borrowed(bytes)))
}
}
impl TryFrom<&'static [u8]> for HeaderName {
type Error = HeaderNameError;
fn try_from(bytes: &'static [u8]) -> Result<Self, Self::Error> {
if bytes.is_empty() {
return Err(HeaderNameError::Empty {
input: String::from_utf8_lossy(bytes).into_owned(),
});
}
for (i, &b) in bytes.iter().enumerate() {
if !is_token_char(b) {
return Err(HeaderNameError::InvalidByte {
byte: b,
position: i,
input: String::from_utf8_lossy(bytes).into_owned(),
});
}
}
Ok(Self(Cow::Borrowed(bytes)))
}
}
impl PartialEq for HeaderName {
fn eq(&self, other: &Self) -> bool {
self.as_bytes().eq_ignore_ascii_case(other.as_bytes())
}
}
impl Eq for HeaderName {}
impl Hash for HeaderName {
fn hash<H: Hasher>(&self, state: &mut H) {
for &b in self.as_bytes() {
state.write_u8(b.to_ascii_lowercase());
}
}
}
impl PartialEq<str> for HeaderName {
fn eq(&self, other: &str) -> bool {
self.as_bytes().eq_ignore_ascii_case(other.as_bytes())
}
}
impl PartialEq<&str> for HeaderName {
fn eq(&self, other: &&str) -> bool {
self.as_bytes().eq_ignore_ascii_case(other.as_bytes())
}
}
impl PartialEq<HeaderName> for str {
fn eq(&self, other: &HeaderName) -> bool {
self.as_bytes().eq_ignore_ascii_case(other.as_bytes())
}
}
impl fmt::Display for HeaderName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<HeaderName> for String {
fn from(name: HeaderName) -> Self {
String::from_utf8(name.0.into_owned()).expect("HeaderName is always valid ASCII")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_validated_parts_matches_new() {
let names: &[&[u8]] = &[b"host", b"Content-Type", b"X-Custom"];
for &name in names {
let v1 = HeaderName::new(name).unwrap();
let v2 = HeaderName::from_validated_bytes(name.to_vec());
assert_eq!(v1, v2);
}
}
}