Skip to main content

trillium_http/headers/
unknown_header_name.rs

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