1use crate::ByteStr;
8use bytes::Bytes;
9use smallvec::SmallVec;
10
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13pub enum HeaderId {
14 Host,
16 ContentLength,
18 ContentType,
20 TransferEncoding,
22 Connection,
24 Expect,
26 Upgrade,
28 Date,
30 Server,
32 Accept,
34 AcceptEncoding,
36 AcceptLanguage,
38 Authorization,
40 CacheControl,
42 Cookie,
44 SetCookie,
46 Etag,
48 IfNoneMatch,
50 IfModifiedSince,
52 LastModified,
54 Location,
56 Referer,
58 UserAgent,
60 Vary,
62 Allow,
64 Trailer,
66 Te,
68 ContentEncoding,
70 ContentLanguage,
72 Range,
74 IfMatch,
76 IfUnmodifiedSince,
78 Origin,
80 Other(ByteStr),
82}
83
84impl HeaderId {
85 pub const WELL_KNOWN: &'static [HeaderId] = &[
87 HeaderId::Host,
88 HeaderId::ContentLength,
89 HeaderId::ContentType,
90 HeaderId::TransferEncoding,
91 HeaderId::Connection,
92 HeaderId::Expect,
93 HeaderId::Upgrade,
94 HeaderId::Date,
95 HeaderId::Server,
96 HeaderId::Accept,
97 HeaderId::AcceptEncoding,
98 HeaderId::AcceptLanguage,
99 HeaderId::Authorization,
100 HeaderId::CacheControl,
101 HeaderId::Cookie,
102 HeaderId::SetCookie,
103 HeaderId::Etag,
104 HeaderId::IfNoneMatch,
105 HeaderId::IfModifiedSince,
106 HeaderId::LastModified,
107 HeaderId::Location,
108 HeaderId::Referer,
109 HeaderId::UserAgent,
110 HeaderId::Vary,
111 HeaderId::Allow,
112 HeaderId::Trailer,
113 HeaderId::Te,
114 HeaderId::ContentEncoding,
115 HeaderId::ContentLanguage,
116 HeaderId::Range,
117 HeaderId::IfMatch,
118 HeaderId::IfUnmodifiedSince,
119 HeaderId::Origin,
120 ];
121
122 #[inline]
131 pub fn from_bytes(name: &[u8]) -> Option<HeaderId> {
132 macro_rules! m {
133 ($($lit:literal => $variant:expr),+ $(,)?) => {{
134 $(if name.eq_ignore_ascii_case($lit) { return Some($variant); })+
135 None
136 }};
137 }
138
139 match name.len() {
140 2 => m!(b"te" => HeaderId::Te),
141 4 => m!(
142 b"host" => HeaderId::Host,
143 b"date" => HeaderId::Date,
144 b"vary" => HeaderId::Vary,
145 b"etag" => HeaderId::Etag,
146 ),
147 5 => m!(b"allow" => HeaderId::Allow, b"range" => HeaderId::Range),
148 6 => m!(
149 b"accept" => HeaderId::Accept,
150 b"expect" => HeaderId::Expect,
151 b"cookie" => HeaderId::Cookie,
152 b"server" => HeaderId::Server,
153 b"origin" => HeaderId::Origin,
154 ),
155 7 => m!(
156 b"upgrade" => HeaderId::Upgrade,
157 b"trailer" => HeaderId::Trailer,
158 b"referer" => HeaderId::Referer,
159 ),
160 8 => m!(b"if-match" => HeaderId::IfMatch, b"location" => HeaderId::Location),
161 10 => m!(
162 b"connection" => HeaderId::Connection,
163 b"set-cookie" => HeaderId::SetCookie,
164 b"user-agent" => HeaderId::UserAgent,
165 ),
166 12 => m!(b"content-type" => HeaderId::ContentType),
167 13 => m!(
168 b"authorization" => HeaderId::Authorization,
169 b"cache-control" => HeaderId::CacheControl,
170 b"if-none-match" => HeaderId::IfNoneMatch,
171 b"last-modified" => HeaderId::LastModified,
172 ),
173 14 => m!(b"content-length" => HeaderId::ContentLength),
174 15 => m!(
175 b"accept-encoding" => HeaderId::AcceptEncoding,
176 b"accept-language" => HeaderId::AcceptLanguage,
177 ),
178 16 => m!(
179 b"content-encoding" => HeaderId::ContentEncoding,
180 b"content-language" => HeaderId::ContentLanguage,
181 ),
182 17 => m!(
183 b"transfer-encoding" => HeaderId::TransferEncoding,
184 b"if-modified-since" => HeaderId::IfModifiedSince,
185 ),
186 19 => m!(b"if-unmodified-since" => HeaderId::IfUnmodifiedSince),
187 _ => None,
188 }
189 }
190
191 #[inline]
193 pub fn as_str(&self) -> &str {
194 match self {
195 HeaderId::Host => "host",
196 HeaderId::ContentLength => "content-length",
197 HeaderId::ContentType => "content-type",
198 HeaderId::TransferEncoding => "transfer-encoding",
199 HeaderId::Connection => "connection",
200 HeaderId::Expect => "expect",
201 HeaderId::Upgrade => "upgrade",
202 HeaderId::Date => "date",
203 HeaderId::Server => "server",
204 HeaderId::Accept => "accept",
205 HeaderId::AcceptEncoding => "accept-encoding",
206 HeaderId::AcceptLanguage => "accept-language",
207 HeaderId::Authorization => "authorization",
208 HeaderId::CacheControl => "cache-control",
209 HeaderId::Cookie => "cookie",
210 HeaderId::SetCookie => "set-cookie",
211 HeaderId::Etag => "etag",
212 HeaderId::IfNoneMatch => "if-none-match",
213 HeaderId::IfModifiedSince => "if-modified-since",
214 HeaderId::LastModified => "last-modified",
215 HeaderId::Location => "location",
216 HeaderId::Referer => "referer",
217 HeaderId::UserAgent => "user-agent",
218 HeaderId::Vary => "vary",
219 HeaderId::Allow => "allow",
220 HeaderId::Trailer => "trailer",
221 HeaderId::Te => "te",
222 HeaderId::ContentEncoding => "content-encoding",
223 HeaderId::ContentLanguage => "content-language",
224 HeaderId::Range => "range",
225 HeaderId::IfMatch => "if-match",
226 HeaderId::IfUnmodifiedSince => "if-unmodified-since",
227 HeaderId::Origin => "origin",
228 HeaderId::Other(s) => s.as_str(),
229 }
230 }
231
232 #[inline]
234 pub fn is_hop_by_hop(&self) -> bool {
235 matches!(
236 self,
237 HeaderId::Connection
238 | HeaderId::TransferEncoding
239 | HeaderId::Te
240 | HeaderId::Trailer
241 | HeaderId::Upgrade
242 )
243 }
244
245 #[inline]
251 pub fn forbidden_in_trailers(&self) -> bool {
252 matches!(
253 self,
254 HeaderId::TransferEncoding
255 | HeaderId::ContentLength
256 | HeaderId::Host
257 | HeaderId::Connection
258 | HeaderId::Expect
259 | HeaderId::Te
260 | HeaderId::Trailer
261 | HeaderId::Upgrade
262 | HeaderId::CacheControl
263 | HeaderId::Authorization
264 | HeaderId::SetCookie
265 )
266 }
267}
268
269pub type HeaderVec = SmallVec<[(HeaderId, Bytes); 16]>;
275
276#[inline]
281pub fn get<'a>(v: &'a HeaderVec, id: &HeaderId) -> Option<&'a Bytes> {
282 v.iter().find(|(k, _)| k == id).map(|(_, val)| val)
283}
284
285#[inline]
287pub fn get_str<'a>(v: &'a HeaderVec, id: &HeaderId) -> Option<&'a str> {
288 get(v, id).and_then(|b| std::str::from_utf8(b).ok())
289}
290
291#[inline]
293pub fn all<'a>(v: &'a HeaderVec, id: &'a HeaderId) -> impl Iterator<Item = &'a Bytes> + 'a {
294 v.iter().filter(move |(k, _)| k == id).map(|(_, val)| val)
295}
296
297#[inline]
302pub fn count(v: &HeaderVec, id: &HeaderId) -> usize {
303 v.iter().filter(|(k, _)| k == id).count()
304}
305
306#[inline]
312pub fn intern(name: &str) -> HeaderId {
313 if let Some(id) = HeaderId::from_bytes(name.as_bytes()) {
314 return id;
315 }
316 if name.bytes().any(|b| b.is_ascii_uppercase()) {
317 return HeaderId::Other(ByteStr::from(name.to_ascii_lowercase()));
318 }
319 HeaderId::Other(ByteStr::from(name))
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn well_known_names_intern_case_insensitively() {
328 assert_eq!(HeaderId::from_bytes(b"host"), Some(HeaderId::Host));
329 assert_eq!(HeaderId::from_bytes(b"Host"), Some(HeaderId::Host));
330 assert_eq!(HeaderId::from_bytes(b"HOST"), Some(HeaderId::Host));
331 assert_eq!(HeaderId::from_bytes(b"hOsT"), Some(HeaderId::Host));
332 assert_eq!(
333 HeaderId::from_bytes(b"content-length"),
334 Some(HeaderId::ContentLength)
335 );
336 assert_eq!(
337 HeaderId::from_bytes(b"Transfer-Encoding"),
338 Some(HeaderId::TransferEncoding)
339 );
340 assert_eq!(HeaderId::from_bytes(b"TE"), Some(HeaderId::Te));
341 }
342
343 #[test]
344 fn unknown_names_are_not_well_known() {
345 assert_eq!(HeaderId::from_bytes(b"x-request-id"), None);
346 assert_eq!(HeaderId::from_bytes(b""), None);
347 assert_eq!(HeaderId::from_bytes(b"hos"), None);
349 assert_eq!(HeaderId::from_bytes(b"hostx"), None);
350 }
351
352 #[test]
353 fn as_str_uses_canonical_casing_and_round_trips() {
354 assert_eq!(HeaderId::Host.as_str(), "host");
355 assert_eq!(HeaderId::ContentLength.as_str(), "content-length");
356 assert_eq!(HeaderId::Te.as_str(), "te");
357 for id in HeaderId::WELL_KNOWN {
358 assert_eq!(
359 HeaderId::from_bytes(id.as_str().as_bytes()).as_ref(),
360 Some(id),
361 "{} failed to round-trip",
362 id.as_str()
363 );
364 }
365 }
366
367 #[test]
368 fn hop_by_hop_classified() {
369 assert!(HeaderId::Connection.is_hop_by_hop());
370 assert!(HeaderId::TransferEncoding.is_hop_by_hop());
371 assert!(HeaderId::Te.is_hop_by_hop());
372 assert!(HeaderId::Trailer.is_hop_by_hop());
373 assert!(HeaderId::Upgrade.is_hop_by_hop());
374 assert!(!HeaderId::ContentType.is_hop_by_hop());
375 assert!(!HeaderId::Host.is_hop_by_hop());
376 }
377
378 #[test]
382 fn framing_headers_forbidden_in_trailers() {
383 assert!(HeaderId::TransferEncoding.forbidden_in_trailers());
384 assert!(HeaderId::ContentLength.forbidden_in_trailers());
385 assert!(HeaderId::Host.forbidden_in_trailers());
386 assert!(HeaderId::Connection.forbidden_in_trailers());
387 assert!(HeaderId::Expect.forbidden_in_trailers());
388 assert!(HeaderId::Te.forbidden_in_trailers());
389 assert!(HeaderId::Trailer.forbidden_in_trailers());
390 assert!(HeaderId::Upgrade.forbidden_in_trailers());
391 assert!(!HeaderId::Etag.forbidden_in_trailers());
392 assert!(!HeaderId::ContentType.forbidden_in_trailers());
393 }
394
395 fn vec_of(pairs: &[(HeaderId, &'static str)]) -> HeaderVec {
396 pairs
397 .iter()
398 .map(|(id, v)| (id.clone(), Bytes::from_static(v.as_bytes())))
399 .collect()
400 }
401
402 #[test]
403 fn get_returns_first_match() {
404 let v = vec_of(&[
405 (HeaderId::Host, "a.example"),
406 (HeaderId::ContentLength, "5"),
407 (HeaderId::Host, "b.example"),
408 ]);
409 assert_eq!(get_str(&v, &HeaderId::Host), Some("a.example"));
410 assert_eq!(get_str(&v, &HeaderId::ContentLength), Some("5"));
411 assert_eq!(get(&v, &HeaderId::ContentType), None);
412 }
413
414 #[test]
415 fn count_and_all_see_every_occurrence() {
416 let v = vec_of(&[
417 (HeaderId::Host, "a.example"),
418 (HeaderId::Host, "b.example"),
419 (HeaderId::ContentLength, "5"),
420 ]);
421 assert_eq!(count(&v, &HeaderId::Host), 2);
422 assert_eq!(count(&v, &HeaderId::ContentLength), 1);
423 assert_eq!(count(&v, &HeaderId::Date), 0);
424 let hosts: Vec<_> = all(&v, &HeaderId::Host).collect();
425 assert_eq!(hosts.len(), 2);
426 assert_eq!(&hosts[1][..], b"b.example");
427 }
428
429 #[test]
430 fn custom_names_compare_by_value() {
431 let x = HeaderId::Other(ByteStr::from_static("x-request-id"));
432 let mut v = HeaderVec::new();
433 v.push((x.clone(), Bytes::from_static(b"abc")));
434 assert_eq!(get_str(&v, &x), Some("abc"));
435 assert_eq!(
436 get(&v, &HeaderId::Other(ByteStr::from_static("x-other"))),
437 None
438 );
439 }
440
441 #[test]
442 fn get_str_rejects_non_utf8_values() {
443 let mut v = HeaderVec::new();
444 v.push((HeaderId::ContentType, Bytes::from_static(&[0xff, 0xfe])));
445 assert_eq!(get_str(&v, &HeaderId::ContentType), None);
446 assert!(get(&v, &HeaderId::ContentType).is_some());
447 }
448
449 #[test]
452 fn typical_request_stays_inline() {
453 let mut v = HeaderVec::new();
454 for _ in 0..16 {
455 v.push((HeaderId::Accept, Bytes::from_static(b"*/*")));
456 }
457 assert!(!v.spilled(), "16 headers must stay on the stack");
458 v.push((HeaderId::Accept, Bytes::from_static(b"*/*")));
459 assert!(v.spilled(), "17 headers is expected to spill");
460 }
461
462 #[test]
463 fn intern_prefers_well_known_and_lowercases_the_rest() {
464 assert_eq!(intern("Content-Length"), HeaderId::ContentLength);
465 assert_eq!(intern("content-length"), HeaderId::ContentLength);
466 assert_eq!(
469 intern("X-Tenant-Id"),
470 HeaderId::Other(ByteStr::from_static("x-tenant-id"))
471 );
472 assert_eq!(
474 intern("x-req-id"),
475 HeaderId::Other(ByteStr::from_static("x-req-id"))
476 );
477 }
478}