trillium_http/headers/
unknown_header_name.rs1use 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 !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
207static LOWER_INTERN: OnceLock<RwLock<HashSet<InternKey>>> = OnceLock::new();
220
221#[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
246fn 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 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 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 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 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 }
351
352 #[test]
353 fn intern_case_insensitive_hash_collapses_uppercase_variants() {
354 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}