Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
/// Represents a header name that we know to be both valid and lowercase.
/// Internally, this avoids allocating for headers that are constant strings,
/// like the predefined ones in this crate, however even without that
/// optimization, we would still likely have an equivalent of this for use
/// as a case-insensitive string guaranteed to only have valid characters.
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
pub struct HeaderName(pub(super) Cow<'static, str>);
/// Indicates an invalid header name. Note that we only emit
/// this for response headers, for request headers, we panic
/// instead. This is because it would likely come through as
/// a network error if we emitted it for local headers, when
/// it's actually a bug that we'd need to fix.
#[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 {
// Only do this if needed, since it causes us to own the header.
name.to_mut().make_ascii_lowercase();
}
Ok(HeaderName(name))
}
impl HeaderName {
/// Create a new header. In general you likely want to use `HeaderName::from(s)`
/// instead for headers being specified locally (This will panic instead of
/// returning a Result, since we have control over headers we specify locally,
/// and want to know if we specify an illegal one).
#[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())
}
}
// Separate for dumb micro-optimization reasons.
#[cold]
#[inline(never)]
fn invalid_header_name(s: Cow<'static, str>) -> InvalidHeaderName {
log::warn!("Invalid header name: {}", s);
InvalidHeaderName(s)
}
// Note: 0 = invalid, 1 = valid, 2 = valid but needs lowercasing. I'd use an
// enum for this, but it would make this LUT *way* harder to look at. This
// includes 0-9, a-z, A-Z (as 2), and ('!' | '#' | '$' | '%' | '&' | '\'' | '*'
// | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~'), matching the field-name
// token production defined at https://tools.ietf.org/html/rfc7230#section-3.2.
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) => {
// This macro is used for items with and without lifetimes.
#[allow(clippy::extra_unused_lifetimes)]
impl<'a> PartialEq<$T0> for $T1 {
fn eq(&self, other: &$T0) -> bool {
// The &* should invoke Deref::deref if it exists, no-op otherwise.
(&*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()))
);
}
}