viaduct/headers/
name.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::borrow::Cow;
6
7/// Represents a header name that we know to be both valid and lowercase.
8/// Internally, this avoids allocating for headers that are constant strings,
9/// like the predefined ones in this crate, however even without that
10/// optimization, we would still likely have an equivalent of this for use
11/// as a case-insensitive string guaranteed to only have valid characters.
12#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
13pub struct HeaderName(pub(super) Cow<'static, str>);
14
15/// Indicates an invalid header name. Note that we only emit
16/// this for response headers, for request headers, we panic
17/// instead. This is because it would likely come through as
18/// a network error if we emitted it for local headers, when
19/// it's actually a bug that we'd need to fix.
20#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
21#[error("Invalid header name: {0:?}")]
22pub struct InvalidHeaderName(Cow<'static, str>);
23
24impl From<&'static str> for HeaderName {
25    fn from(s: &'static str) -> HeaderName {
26        match HeaderName::new(s) {
27            Ok(v) => v,
28            Err(e) => {
29                panic!("Illegal locally specified header {}", e);
30            }
31        }
32    }
33}
34
35impl From<String> for HeaderName {
36    fn from(s: String) -> HeaderName {
37        match HeaderName::new(s) {
38            Ok(v) => v,
39            Err(e) => {
40                panic!("Illegal locally specified header {}", e);
41            }
42        }
43    }
44}
45
46impl From<Cow<'static, str>> for HeaderName {
47    fn from(s: Cow<'static, str>) -> HeaderName {
48        match HeaderName::new(s) {
49            Ok(v) => v,
50            Err(e) => {
51                panic!("Illegal locally specified header {}", e);
52            }
53        }
54    }
55}
56
57impl InvalidHeaderName {
58    pub fn name(&self) -> &str {
59        &self.0[..]
60    }
61}
62
63fn validate_header(mut name: Cow<'static, str>) -> Result<HeaderName, InvalidHeaderName> {
64    if name.is_empty() {
65        return Err(invalid_header_name(name));
66    }
67    let mut need_lower_case = false;
68    for b in name.bytes() {
69        let validity = VALID_HEADER_LUT[b as usize];
70        if validity == 0 {
71            return Err(invalid_header_name(name));
72        }
73        if validity == 2 {
74            need_lower_case = true;
75        }
76    }
77    if need_lower_case {
78        // Only do this if needed, since it causes us to own the header.
79        name.to_mut().make_ascii_lowercase();
80    }
81    Ok(HeaderName(name))
82}
83
84impl HeaderName {
85    /// Create a new header. In general you likely want to use `HeaderName::from(s)`
86    /// instead for headers being specified locally (This will panic instead of
87    /// returning a Result, since we have control over headers we specify locally,
88    /// and want to know if we specify an illegal one).
89    #[inline]
90    pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Result<Self, InvalidHeaderName> {
91        validate_header(s.into())
92    }
93
94    #[inline]
95    pub fn as_str(&self) -> &str {
96        &self.0[..]
97    }
98}
99
100impl std::fmt::Display for HeaderName {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.write_str(self.as_str())
103    }
104}
105
106// Separate for dumb micro-optimization reasons.
107#[cold]
108#[inline(never)]
109fn invalid_header_name(s: Cow<'static, str>) -> InvalidHeaderName {
110    crate::warn!("Invalid header name: {}", s);
111    InvalidHeaderName(s)
112}
113// Note: 0 = invalid, 1 = valid, 2 = valid but needs lowercasing. I'd use an
114// enum for this, but it would make this LUT *way* harder to look at. This
115// includes 0-9, a-z, A-Z (as 2), and ('!' | '#' | '$' | '%' | '&' | '\'' | '*'
116// | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~'), matching the field-name
117// token production defined at https://tools.ietf.org/html/rfc7230#section-3.2.
118static VALID_HEADER_LUT: [u8; 256] = [
119    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,
120    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,
121    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,
122    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,
123    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,
124    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,
125    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,
126    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,
127];
128
129impl std::ops::Deref for HeaderName {
130    type Target = str;
131    #[inline]
132    fn deref(&self) -> &str {
133        self.as_str()
134    }
135}
136
137impl AsRef<str> for HeaderName {
138    #[inline]
139    fn as_ref(&self) -> &str {
140        self.as_str()
141    }
142}
143
144impl AsRef<[u8]> for HeaderName {
145    #[inline]
146    fn as_ref(&self) -> &[u8] {
147        self.as_str().as_bytes()
148    }
149}
150
151impl From<HeaderName> for String {
152    #[inline]
153    fn from(h: HeaderName) -> Self {
154        h.0.into()
155    }
156}
157
158impl From<HeaderName> for Cow<'static, str> {
159    #[inline]
160    fn from(h: HeaderName) -> Self {
161        h.0
162    }
163}
164
165impl From<HeaderName> for Vec<u8> {
166    #[inline]
167    fn from(h: HeaderName) -> Self {
168        String::from(h.0).into()
169    }
170}
171
172macro_rules! partialeq_boilerplate {
173    ($T0:ty, $T1:ty) => {
174        // This macro is used for items with and without lifetimes.
175        #[allow(clippy::extra_unused_lifetimes)]
176        impl<'a> PartialEq<$T0> for $T1 {
177            fn eq(&self, other: &$T0) -> bool {
178                // The &* should invoke Deref::deref if it exists, no-op otherwise.
179                (&*self).eq_ignore_ascii_case(&*other)
180            }
181        }
182        #[allow(clippy::extra_unused_lifetimes)]
183        impl<'a> PartialEq<$T1> for $T0 {
184            fn eq(&self, other: &$T1) -> bool {
185                PartialEq::eq(other, self)
186            }
187        }
188    };
189}
190
191partialeq_boilerplate!(HeaderName, str);
192partialeq_boilerplate!(HeaderName, &'a str);
193partialeq_boilerplate!(HeaderName, String);
194partialeq_boilerplate!(HeaderName, &'a String);
195partialeq_boilerplate!(HeaderName, Cow<'a, str>);
196
197#[cfg(test)]
198mod test {
199    use super::*;
200
201    #[test]
202    fn test_lut() {
203        let mut expect = [0u8; 256];
204        for b in b'0'..=b'9' {
205            expect[b as usize] = 1;
206        }
207        for b in b'a'..=b'z' {
208            expect[b as usize] = 1;
209        }
210        for b in b'A'..=b'Z' {
211            expect[b as usize] = 2;
212        }
213        for b in b"!#$%&'*+-.^_`|~" {
214            expect[*b as usize] = 1;
215        }
216        assert_eq!(&VALID_HEADER_LUT[..], &expect[..]);
217    }
218    #[test]
219    fn test_validate() {
220        assert!(validate_header("".into()).is_err());
221        assert!(validate_header(" foo ".into()).is_err());
222        assert!(validate_header("a=b".into()).is_err());
223        assert_eq!(
224            validate_header("content-type".into()),
225            Ok(HeaderName("content-type".into()))
226        );
227        assert_eq!(
228            validate_header("Content-Type".into()),
229            Ok(HeaderName("content-type".into()))
230        );
231    }
232}