use std::borrow::Cow;
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
pub struct HeaderName(pub(super) Cow<'static, str>);
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[error("Invalid header name: {0:?}")]
pub struct InvalidHeaderName(Cow<'static, str>);
impl From<&'static str> for HeaderName {
fn from(s: &'static str) -> HeaderName {
match HeaderName::new(s) {
Ok(v) => v,
Err(e) => {
panic!("Illegal locally specified header {}", e);
}
}
}
}
impl From<String> for HeaderName {
fn from(s: String) -> HeaderName {
match HeaderName::new(s) {
Ok(v) => v,
Err(e) => {
panic!("Illegal locally specified header {}", e);
}
}
}
}
impl From<Cow<'static, str>> for HeaderName {
fn from(s: Cow<'static, str>) -> HeaderName {
match HeaderName::new(s) {
Ok(v) => v,
Err(e) => {
panic!("Illegal locally specified header {}", e);
}
}
}
}
impl InvalidHeaderName {
pub fn name(&self) -> &str {
&self.0[..]
}
}
fn validate_header(mut name: Cow<'static, str>) -> Result<HeaderName, InvalidHeaderName> {
if name.len() == 0 {
return Err(invalid_header_name(name));
}
let mut need_lower_case = false;
for b in name.bytes() {
let validity = VALID_HEADER_LUT[b as usize];
if validity == 0 {
return Err(invalid_header_name(name));
}
if validity == 2 {
need_lower_case = true;
}
}
if need_lower_case {
name.to_mut().make_ascii_lowercase();
}
Ok(HeaderName(name))
}
impl HeaderName {
#[inline]
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Result<Self, InvalidHeaderName> {
validate_header(s.into())
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0[..]
}
}
impl std::fmt::Display for HeaderName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cold]
#[inline(never)]
fn invalid_header_name(s: Cow<'static, str>) -> InvalidHeaderName {
log::warn!("Invalid header name: {}", s);
InvalidHeaderName(s)
}
static VALID_HEADER_LUT: [u8; 256] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
impl std::ops::Deref for HeaderName {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for HeaderName {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for HeaderName {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl From<HeaderName> for String {
#[inline]
fn from(h: HeaderName) -> Self {
h.0.into()
}
}
impl From<HeaderName> for Cow<'static, str> {
#[inline]
fn from(h: HeaderName) -> Self {
h.0
}
}
impl From<HeaderName> for Vec<u8> {
#[inline]
fn from(h: HeaderName) -> Self {
String::from(h.0).into()
}
}
macro_rules! partialeq_boilerplate {
($T0:ty, $T1:ty) => {
#[allow(clippy::extra_unused_lifetimes)]
impl<'a> PartialEq<$T0> for $T1 {
fn eq(&self, other: &$T0) -> bool {
(&*self).eq_ignore_ascii_case(&*other)
}
}
#[allow(clippy::extra_unused_lifetimes)]
impl<'a> PartialEq<$T1> for $T0 {
fn eq(&self, other: &$T1) -> bool {
PartialEq::eq(other, self)
}
}
};
}
partialeq_boilerplate!(HeaderName, str);
partialeq_boilerplate!(HeaderName, &'a str);
partialeq_boilerplate!(HeaderName, String);
partialeq_boilerplate!(HeaderName, &'a String);
partialeq_boilerplate!(HeaderName, Cow<'a, str>);
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_lut() {
let mut expect = [0u8; 256];
for b in b'0'..=b'9' {
expect[b as usize] = 1;
}
for b in b'a'..=b'z' {
expect[b as usize] = 1;
}
for b in b'A'..=b'Z' {
expect[b as usize] = 2;
}
for b in b"!#$%&'*+-.^_`|~" {
expect[*b as usize] = 1;
}
assert_eq!(&VALID_HEADER_LUT[..], &expect[..]);
}
#[test]
fn test_validate() {
assert!(validate_header("".into()).is_err());
assert!(validate_header(" foo ".into()).is_err());
assert!(validate_header("a=b".into()).is_err());
assert_eq!(
validate_header("content-type".into()),
Ok(HeaderName("content-type".into()))
);
assert_eq!(
validate_header("Content-Type".into()),
Ok(HeaderName("content-type".into()))
);
}
}