Skip to main content

trillium_http/headers/
unknown_header_name.rs

1use super::{HeaderName, HeaderNameInner::UnknownHeader};
2use crate::compact_cow::CompactCow;
3use hashbrown::{Equivalent, HashMap, HashSet};
4use std::{
5    cell::RefCell,
6    cmp::Ordering,
7    fmt::{self, Debug, Display, Formatter},
8    hash::{Hash, Hasher},
9    ops::Deref,
10    sync::{OnceLock, RwLock},
11};
12
13#[derive(Clone)]
14pub(crate) struct UnknownHeaderName<'a>(CompactCow<'a>);
15
16impl UnknownHeaderName<'_> {
17    pub(crate) fn is_valid_lower(&self) -> bool {
18        // Lowercase tchar — the uppercase-letter branch is dropped because HTTP/2 and
19        // HTTP/3 require field names to be lowercase on the wire. Otherwise matches
20        // `is_tchar`.
21        !self.is_empty()
22            && self.chars().all(|c| {
23                matches!(c,
24                    'a'..='z'
25                    | '0'..='9'
26                    | '!'
27                    | '#'
28                    | '$'
29                    | '%'
30                    | '&'
31                    | '\''
32                    | '*'
33                    | '+'
34                    | '-'
35                    | '.'
36                    | '^'
37                    | '_'
38                    | '`'
39                    | '|'
40                    | '~',
41                )
42            })
43    }
44
45    pub(crate) fn into_lower(self) -> Self {
46        match self.0 {
47            CompactCow::Borrowed(borrowed) => {
48                if let Some(first_upper) = borrowed.chars().position(|c| c.is_ascii_uppercase()) {
49                    Self(CompactCow::Owned(
50                        borrowed[..first_upper]
51                            .chars()
52                            .chain(
53                                borrowed[first_upper..]
54                                    .chars()
55                                    .map(|c| c.to_ascii_lowercase()),
56                            )
57                            .collect(),
58                    ))
59                } else {
60                    Self(CompactCow::Borrowed(borrowed))
61                }
62            }
63            CompactCow::Owned(mut compact_string) => {
64                compact_string.make_ascii_lowercase();
65                Self(CompactCow::Owned(compact_string))
66            }
67            CompactCow::Shared(shared) => {
68                if shared.bytes().any(|b| b.is_ascii_uppercase()) {
69                    Self(CompactCow::Owned(shared.to_ascii_lowercase().into()))
70                } else {
71                    Self(CompactCow::Shared(shared))
72                }
73            }
74        }
75    }
76}
77
78impl PartialOrd for UnknownHeaderName<'_> {
79    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80        Some(self.cmp(other))
81    }
82}
83
84impl Ord for UnknownHeaderName<'_> {
85    fn cmp(&self, other: &Self) -> Ordering {
86        self.0.cmp(&*other.0)
87    }
88}
89
90impl PartialEq for UnknownHeaderName<'_> {
91    fn eq(&self, other: &Self) -> bool {
92        self.0.eq_ignore_ascii_case(&other.0)
93    }
94}
95
96impl Eq for UnknownHeaderName<'_> {}
97
98impl Hash for UnknownHeaderName<'_> {
99    fn hash<H: Hasher>(&self, state: &mut H) {
100        for c in self.0.as_bytes() {
101            c.to_ascii_lowercase().hash(state);
102        }
103    }
104}
105
106impl Debug for UnknownHeaderName<'_> {
107    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
108        Debug::fmt(&self.0, f)
109    }
110}
111
112impl Display for UnknownHeaderName<'_> {
113    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114        Display::fmt(&self.0, f)
115    }
116}
117
118impl<'a> From<UnknownHeaderName<'a>> for HeaderName<'a> {
119    fn from(value: UnknownHeaderName<'a>) -> Self {
120        HeaderName(UnknownHeader(value))
121    }
122}
123
124impl<'a> From<&'a UnknownHeaderName<'_>> for HeaderName<'a> {
125    fn from(value: &'a UnknownHeaderName<'_>) -> Self {
126        HeaderName(UnknownHeader(value.reborrow()))
127    }
128}
129
130fn is_tchar(c: char) -> bool {
131    matches!(
132        c,
133        'a'..='z'
134        | 'A'..='Z'
135        | '0'..='9'
136        | '!'
137        | '#'
138        | '$'
139        | '%'
140        | '&'
141        | '\''
142        | '*'
143        | '+'
144        | '-'
145        | '.'
146        | '^'
147        | '_'
148        | '`'
149        | '|'
150        | '~'
151    )
152}
153
154impl UnknownHeaderName<'_> {
155    pub(crate) fn is_valid(&self) -> bool {
156        !self.is_empty() && self.0.chars().all(is_tchar)
157    }
158
159    pub(crate) fn into_owned(self) -> UnknownHeaderName<'static> {
160        UnknownHeaderName(self.0.into_owned())
161    }
162}
163
164impl<'a> UnknownHeaderName<'a> {
165    pub(crate) fn reborrow<'b: 'a>(&'b self) -> UnknownHeaderName<'b> {
166        Self(CompactCow::Borrowed(&self.0))
167    }
168}
169
170impl From<String> for UnknownHeaderName<'static> {
171    fn from(value: String) -> Self {
172        Self(value.into())
173    }
174}
175
176impl<'a> From<&'a str> for UnknownHeaderName<'a> {
177    fn from(value: &'a str) -> Self {
178        Self(value.into())
179    }
180}
181
182impl<'a> From<CompactCow<'a>> for UnknownHeaderName<'a> {
183    fn from(value: CompactCow<'a>) -> Self {
184        Self(value)
185    }
186}
187
188impl<'a> From<UnknownHeaderName<'a>> for CompactCow<'a> {
189    fn from(value: UnknownHeaderName<'a>) -> Self {
190        value.0
191    }
192}
193
194impl Deref for UnknownHeaderName<'_> {
195    type Target = str;
196
197    fn deref(&self) -> &Self::Target {
198        &self.0
199    }
200}
201
202impl Equivalent<UnknownHeaderName<'_>> for &UnknownHeaderName<'_> {
203    fn equivalent(&self, key: &UnknownHeaderName<'_>) -> bool {
204        key.eq_ignore_ascii_case(self)
205    }
206}
207
208/// Process-global table of canonical lowercased `&'static str` for literal
209/// header names that contained uppercase characters in source. Pure-lowercase
210/// literals bypass this table entirely (no need to intern — they're already
211/// `&'static`).
212///
213/// It is written once per literal and read once per literal per thread, after which
214/// [`LOCAL_INTERN`] answers, so an `RwLock` is plenty. The hasher is case-insensitive so
215/// we can probe with the original uppercase input without first allocating its
216/// lowercased form.
217///
218/// Bounded above by distinct uppercase-containing lowercased literal names in
219/// the binary.
220static LOWER_INTERN: OnceLock<RwLock<HashSet<InternKey>>> = OnceLock::new();
221
222/// Wrapper around `&'static str` whose `Hash`/`Eq` are case-insensitive on
223/// ASCII. Lets the interner store the canonical lowercased form once and probe
224/// with the original casing without rebuilding the lowercased string just to
225/// look it up.
226#[derive(Copy, Clone, Eq)]
227struct InternKey(&'static str);
228
229impl PartialEq for InternKey {
230    fn eq(&self, other: &Self) -> bool {
231        self.0.eq_ignore_ascii_case(other.0)
232    }
233}
234
235impl Hash for InternKey {
236    fn hash<H: Hasher>(&self, state: &mut H) {
237        for b in self.0.bytes() {
238            state.write_u8(b.to_ascii_lowercase());
239        }
240    }
241}
242
243fn intern_table() -> &'static RwLock<HashSet<InternKey>> {
244    LOWER_INTERN.get_or_init(|| RwLock::new(HashSet::new()))
245}
246
247thread_local! {
248    /// Per-thread front for [`LOWER_INTERN`], keyed by a literal's address and length, which
249    /// together identify its contents. Even an uncontended `RwLock::read` writes to a lock word
250    /// that every core shares, so a thread that has already seen a literal skips the table.
251    static LOCAL_INTERN: RefCell<HashMap<(usize, usize), &'static str>> =
252        RefCell::new(HashMap::new());
253}
254
255/// Return a canonical lowercased `&'static str` for `s`.
256///
257/// - If `s` is already all-lowercase: returns `s` directly. No lock, no alloc.
258/// - If this thread has interned `s` before: returns the canonical pointer from [`LOCAL_INTERN`].
259/// - Otherwise: probes the intern table with a case-insensitive hash. On hit, returns the stored
260///   canonical pointer. On miss, allocates the lowercased form, leaks it to obtain a `&'static
261///   str`, and inserts.
262///
263/// The leak is bounded by the number of distinct uppercase-containing lowercased
264/// literals in the binary — typically zero or single digits for well-behaved code.
265fn intern_lowercase(s: &'static str) -> &'static str {
266    if !s.bytes().any(|b| b.is_ascii_uppercase()) {
267        return s;
268    }
269    let key = (s.as_ptr().addr(), s.len());
270    if let Some(interned) = LOCAL_INTERN.with_borrow(|local| local.get(&key).copied()) {
271        return interned;
272    }
273    let interned = intern_lowercase_shared(s);
274    LOCAL_INTERN.with_borrow_mut(|local| local.insert(key, interned));
275    interned
276}
277
278fn intern_lowercase_shared(s: &'static str) -> &'static str {
279    let probe = InternKey(s);
280    let table = intern_table();
281    {
282        let read = table.read().expect("intern table poisoned");
283        if let Some(hit) = read.get(&probe) {
284            return hit.0;
285        }
286    }
287    // Allocate-and-leak inside the write lock so two threads racing for the same
288    // uppercase literal don't both leak: the second arrival finds the first's
289    // insert via the post-acquire `get` and bails before allocating.
290    let mut write = table.write().expect("intern table poisoned");
291    if let Some(hit) = write.get(&probe) {
292        return hit.0;
293    }
294    let lowered: String = s.chars().map(|c| c.to_ascii_lowercase()).collect();
295    let leaked: &'static str = Box::leak(lowered.into_boxed_str());
296    write.insert(InternKey(leaked));
297    leaked
298}
299
300impl UnknownHeaderName<'static> {
301    /// Recover the underlying `&'static str` if this name is backed by a borrowed
302    /// reference into static memory (a literal or an interned lowercased literal).
303    /// Returns `None` for runtime-allocated names (`CompactCow::Owned`).
304    pub(crate) fn as_static_str(&self) -> Option<&'static str> {
305        match self.0 {
306            CompactCow::Borrowed(s) => Some(s),
307            CompactCow::Owned(_) | CompactCow::Shared(_) => None,
308        }
309    }
310
311    /// Like [`Self::into_lower`], but for the uppercase-borrowed-static case it
312    /// interns the lowercased form via [`intern_lowercase`] instead of allocating
313    /// an Owned copy. The result is therefore *always* `CompactCow::Borrowed` (and
314    /// hence `&'static str`-recoverable via [`as_static_str`]) when the input was
315    /// `CompactCow::Borrowed`. `Owned` inputs fall back to the regular
316    /// [`Self::into_lower`] path and are not interned.
317    ///
318    /// [`as_static_str`]: Self::as_static_str
319    pub(crate) fn into_lower_static(self) -> Self {
320        match self.0 {
321            CompactCow::Borrowed(s) => {
322                if s.bytes().any(|b| b.is_ascii_uppercase()) {
323                    Self(CompactCow::Borrowed(intern_lowercase(s)))
324                } else {
325                    Self(CompactCow::Borrowed(s))
326                }
327            }
328            CompactCow::Owned(_) | CompactCow::Shared(_) => self.into_lower(),
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    fn ensure_interned(s: &'static str) -> &'static str {
338        intern_lowercase(s)
339    }
340
341    #[test]
342    fn interned_names_are_canonical_across_threads_and_addresses() {
343        let here = ensure_interned("X-Thread-Front");
344        let there = std::thread::spawn(|| ensure_interned("X-Thread-Front"))
345            .join()
346            .unwrap();
347        let elsewhere = ensure_interned(String::from("X-THREAD-FRONT").leak());
348        assert_eq!(here, "x-thread-front");
349        assert_eq!(here.as_ptr(), there.as_ptr());
350        assert_eq!(here.as_ptr(), elsewhere.as_ptr());
351    }
352
353    #[test]
354    fn intern_idempotent() {
355        let a = ensure_interned("X-Idempotent-Header");
356        let b = ensure_interned("X-Idempotent-Header");
357        assert_eq!(a, "x-idempotent-header");
358        assert!(
359            std::ptr::eq(a, b),
360            "intern must return identical &'static str on repeat uppercase input",
361        );
362    }
363
364    #[test]
365    fn intern_lowercase_input_is_passthrough() {
366        // Pure-lowercase input bypasses the intern table — caller's pointer is
367        // returned directly.
368        let original: &'static str = "x-already-lowercase";
369        let got = ensure_interned(original);
370        assert!(
371            std::ptr::eq(got, original),
372            "pure-lowercase literal should bypass interning entirely",
373        );
374    }
375
376    #[test]
377    fn intern_uppercase_then_lowercase_content_equal() {
378        let upper = ensure_interned("X-Cross-Casing-Header");
379        let lower = ensure_interned("x-cross-casing-header");
380        assert_eq!(upper, lower);
381        // Pointer identity may or may not hold depending on which call ran first.
382    }
383
384    #[test]
385    fn intern_case_insensitive_hash_collapses_uppercase_variants() {
386        // Two different uppercase castings of the same lowercased content must
387        // intern to the same pointer.
388        let a = ensure_interned("X-Mixed-Casing");
389        let b = ensure_interned("x-MIXED-casing");
390        assert!(
391            std::ptr::eq(a, b),
392            "case-insensitive hash must collapse uppercase variants",
393        );
394    }
395
396    #[test]
397    fn into_lower_static_borrowed_uppercase() {
398        let n = UnknownHeaderName(CompactCow::Borrowed("X-Static-Upper")).into_lower_static();
399        assert_eq!(n.as_static_str(), Some("x-static-upper"));
400    }
401
402    #[test]
403    fn into_lower_static_borrowed_lowercase_passthrough() {
404        let original: &'static str = "x-static-lower";
405        let n = UnknownHeaderName(CompactCow::Borrowed(original)).into_lower_static();
406        let got = n.as_static_str().unwrap();
407        assert!(
408            std::ptr::eq(got, original),
409            "already-lowercase literal should pass through without interning",
410        );
411    }
412
413    #[test]
414    fn into_lower_static_owned_stays_owned() {
415        let owned = UnknownHeaderName::from(String::from("X-Owned-Upper"));
416        let lowered = owned.into_lower_static();
417        assert_eq!(lowered.as_static_str(), None);
418        assert_eq!(&*lowered, "x-owned-upper");
419    }
420}