eggserve_core/primitives/
header_block.rs1use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum HeaderError {
12 InvalidName,
14 InvalidValue,
16 NameTooLong,
18}
19
20impl fmt::Display for HeaderError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::InvalidName => write!(f, "invalid header name"),
24 Self::InvalidValue => write!(f, "invalid header value (contains CR/LF/NUL)"),
25 Self::NameTooLong => write!(f, "header name too long"),
26 }
27 }
28}
29
30impl std::error::Error for HeaderError {}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct DuplicateHeaderError {
35 name: String,
37 count: usize,
39}
40
41impl DuplicateHeaderError {
42 pub fn name(&self) -> &str {
44 &self.name
45 }
46
47 pub fn count(&self) -> usize {
49 self.count
50 }
51}
52
53impl fmt::Display for DuplicateHeaderError {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(
56 f,
57 "header '{}' has {} values; use get_all() to access all",
58 self.name, self.count
59 )
60 }
61}
62
63impl std::error::Error for DuplicateHeaderError {}
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub struct HeaderName(String);
68
69impl HeaderName {
70 pub fn new(name: impl Into<String>) -> Result<Self, HeaderError> {
78 let s = name.into();
79 if s.is_empty() {
80 return Err(HeaderError::InvalidName);
81 }
82 if s.len() > 256 {
83 return Err(HeaderError::NameTooLong);
84 }
85 if !is_valid_header_name(&s) {
86 return Err(HeaderError::InvalidName);
87 }
88 Ok(Self(s))
89 }
90
91 pub fn as_str(&self) -> &str {
93 &self.0
94 }
95}
96
97impl fmt::Display for HeaderName {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 f.write_str(&self.0)
100 }
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Hash)]
105pub struct HeaderValue(String);
106
107impl HeaderValue {
108 pub fn new(value: impl Into<String>) -> Result<Self, HeaderError> {
115 let s = value.into();
116 if s.bytes().any(|b| b == b'\r' || b == b'\n' || b == 0) {
117 return Err(HeaderError::InvalidValue);
118 }
119 Ok(Self(s.trim_matches([' ', '\t']).to_owned()))
120 }
121
122 pub fn as_str(&self) -> &str {
124 &self.0
125 }
126}
127
128impl fmt::Display for HeaderValue {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 f.write_str(&self.0)
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct HeaderField {
137 pub name: HeaderName,
138 pub value: HeaderValue,
139}
140
141#[derive(Debug, Clone, Default, PartialEq, Eq)]
153pub struct HeaderBlock {
154 fields: Vec<HeaderField>,
155}
156
157impl HeaderBlock {
158 pub fn new() -> Self {
160 Self { fields: Vec::new() }
161 }
162
163 pub fn with_capacity(capacity: usize) -> Self {
165 Self {
166 fields: Vec::with_capacity(capacity),
167 }
168 }
169
170 pub fn push(&mut self, name: HeaderName, value: HeaderValue) {
172 self.fields.push(HeaderField { name, value });
173 }
174
175 pub fn push_str(
181 &mut self,
182 name: impl Into<String>,
183 value: impl Into<String>,
184 ) -> Result<(), HeaderError> {
185 let name = HeaderName::new(name)?;
186 let value = HeaderValue::new(value)?;
187 self.push(name, value);
188 Ok(())
189 }
190
191 pub fn get_first(&self, name: &str) -> Option<&HeaderValue> {
193 self.fields
194 .iter()
195 .find(|f| f.name.as_str().eq_ignore_ascii_case(name))
196 .map(|f| &f.value)
197 }
198
199 pub fn get_all(&self, name: &str) -> Vec<&HeaderValue> {
202 self.fields
203 .iter()
204 .filter(|f| f.name.as_str().eq_ignore_ascii_case(name))
205 .map(|f| &f.value)
206 .collect()
207 }
208
209 pub fn get_unique(&self, name: &str) -> Result<Option<&HeaderValue>, DuplicateHeaderError> {
216 let values = self.get_all(name);
217 match values.len() {
218 0 => Ok(None),
219 1 => Ok(Some(values[0])),
220 _ => Err(DuplicateHeaderError {
221 name: name.to_string(),
222 count: values.len(),
223 }),
224 }
225 }
226
227 pub fn contains(&self, name: &str) -> bool {
230 self.fields
231 .iter()
232 .any(|f| f.name.as_str().eq_ignore_ascii_case(name))
233 }
234
235 pub fn iter(&self) -> impl Iterator<Item = &HeaderField> {
237 self.fields.iter()
238 }
239
240 pub fn len(&self) -> usize {
242 self.fields.len()
243 }
244
245 pub fn is_empty(&self) -> bool {
247 self.fields.is_empty()
248 }
249
250 pub fn retain<F>(&mut self, f: F)
252 where
253 F: FnMut(&HeaderField) -> bool,
254 {
255 self.fields.retain(f);
256 }
257}
258
259impl fmt::Display for HeaderBlock {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 for field in &self.fields {
262 writeln!(f, "{}: {}", field.name, field.value)?;
263 }
264 Ok(())
265 }
266}
267
268fn is_valid_header_name(s: &str) -> bool {
271 s.bytes().all(|b| matches!(b, 0x21 | 0x23..=0x27 | 0x2A | 0x2B | 0x2D..=0x2E | 0x30..=0x39 | 0x41..=0x5A | 0x5E..=0x7A | 0x7C | 0x7E))
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn header_name_valid() {
280 assert!(HeaderName::new("Content-Type").is_ok());
281 assert!(HeaderName::new("x-custom-header").is_ok());
282 assert!(HeaderName::new("X").is_ok());
283 }
284
285 #[test]
286 fn header_name_empty_rejected() {
287 assert_eq!(HeaderName::new("").unwrap_err(), HeaderError::InvalidName);
288 }
289
290 #[test]
291 fn header_name_invalid_chars_rejected() {
292 assert_eq!(
293 HeaderName::new("foo bar").unwrap_err(),
294 HeaderError::InvalidName
295 );
296 assert_eq!(
297 HeaderName::new("foo\tbar").unwrap_err(),
298 HeaderError::InvalidName
299 );
300 }
301
302 #[test]
303 fn header_name_too_long_rejected() {
304 let long_name = "x".repeat(257);
305 assert_eq!(
306 HeaderName::new(long_name).unwrap_err(),
307 HeaderError::NameTooLong
308 );
309 }
310
311 #[test]
312 fn header_value_valid() {
313 assert!(HeaderValue::new("text/html").is_ok());
314 assert!(HeaderValue::new("").is_ok());
315 assert!(HeaderValue::new("hello world").is_ok());
316 }
317
318 #[test]
319 fn header_value_trims_optional_whitespace() {
320 assert_eq!(HeaderValue::new(" \tvalue\t ").unwrap().as_str(), "value");
321 }
322
323 #[test]
324 fn header_value_cr_rejected() {
325 assert_eq!(
326 HeaderValue::new("foo\rbar").unwrap_err(),
327 HeaderError::InvalidValue
328 );
329 }
330
331 #[test]
332 fn header_value_lf_rejected() {
333 assert_eq!(
334 HeaderValue::new("foo\nbar").unwrap_err(),
335 HeaderError::InvalidValue
336 );
337 }
338
339 #[test]
340 fn header_value_nul_rejected() {
341 assert_eq!(
342 HeaderValue::new("foo\0bar").unwrap_err(),
343 HeaderError::InvalidValue
344 );
345 }
346
347 #[test]
348 fn empty_header_block() {
349 let block = HeaderBlock::new();
350 assert!(block.is_empty());
351 assert_eq!(block.len(), 0);
352 assert!(block.get_first("foo").is_none());
353 }
354
355 #[test]
356 fn push_and_get_first() {
357 let mut block = HeaderBlock::new();
358 block.push_str("content-type", "text/html").unwrap();
359 assert_eq!(
360 block.get_first("content-type").unwrap().as_str(),
361 "text/html"
362 );
363 assert_eq!(
364 block.get_first("Content-Type").unwrap().as_str(),
365 "text/html"
366 );
367 assert!(block.get_first("missing").is_none());
368 }
369
370 #[test]
371 fn duplicates_preserved() {
372 let mut block = HeaderBlock::new();
373 block.push_str("set-cookie", "a=1").unwrap();
374 block.push_str("set-cookie", "b=2").unwrap();
375 assert_eq!(block.len(), 2);
376 assert_eq!(block.get_first("Set-Cookie").unwrap().as_str(), "a=1");
377 }
378
379 #[test]
380 fn get_all_returns_all_values() {
381 let mut block = HeaderBlock::new();
382 block.push_str("set-cookie", "a=1").unwrap();
383 block.push_str("set-cookie", "b=2").unwrap();
384 block.push_str("set-cookie", "c=3").unwrap();
385 let all = block.get_all("set-cookie");
386 assert_eq!(all.len(), 3);
387 assert_eq!(all[0].as_str(), "a=1");
388 assert_eq!(all[1].as_str(), "b=2");
389 assert_eq!(all[2].as_str(), "c=3");
390 }
391
392 #[test]
393 fn get_unique_single() {
394 let mut block = HeaderBlock::new();
395 block.push_str("content-type", "text/html").unwrap();
396 let result = block.get_unique("content-type").unwrap();
397 assert_eq!(result.unwrap().as_str(), "text/html");
398 }
399
400 #[test]
401 fn get_unique_absent() {
402 let block = HeaderBlock::new();
403 assert!(block.get_unique("content-type").unwrap().is_none());
404 }
405
406 #[test]
407 fn get_unique_duplicate_error() {
408 let mut block = HeaderBlock::new();
409 block.push_str("set-cookie", "a=1").unwrap();
410 block.push_str("set-cookie", "b=2").unwrap();
411 let err = block.get_unique("set-cookie").unwrap_err();
412 assert_eq!(err.name(), "set-cookie");
413 assert_eq!(err.count(), 2);
414 }
415
416 #[test]
417 fn contains_case_insensitive() {
418 let mut block = HeaderBlock::new();
419 block.push_str("Content-Type", "text/html").unwrap();
420 assert!(block.contains("content-type"));
421 assert!(block.contains("CONTENT-TYPE"));
422 assert!(block.contains("Content-Type"));
423 assert!(!block.contains("missing"));
424 }
425
426 #[test]
427 fn iteration_order() {
428 let mut block = HeaderBlock::new();
429 block.push_str("a", "1").unwrap();
430 block.push_str("b", "2").unwrap();
431 block.push_str("c", "3").unwrap();
432 let names: Vec<&str> = block.iter().map(|f| f.name.as_str()).collect();
433 assert_eq!(names, vec!["a", "b", "c"]);
434 }
435
436 #[test]
437 fn display() {
438 let mut block = HeaderBlock::new();
439 block.push_str("a", "1").unwrap();
440 let display = format!("{}", block);
441 assert!(display.contains("a: 1"));
442 }
443
444 #[test]
445 fn header_name_display() {
446 assert_eq!(format!("{}", HeaderName::new("foo").unwrap()), "foo");
447 }
448
449 #[test]
450 fn header_value_display() {
451 assert_eq!(format!("{}", HeaderValue::new("bar").unwrap()), "bar");
452 }
453
454 #[test]
455 fn duplicate_header_error_display() {
456 let err = DuplicateHeaderError {
457 name: "set-cookie".to_string(),
458 count: 3,
459 };
460 let msg = err.to_string();
461 assert!(msg.contains("set-cookie"));
462 assert!(msg.contains("3"));
463 }
464
465 #[test]
466 fn duplicate_header_error_is_error() {
467 let err: &dyn std::error::Error = &DuplicateHeaderError {
468 name: "x".to_string(),
469 count: 2,
470 };
471 assert!(!err.to_string().is_empty());
472 }
473
474 #[test]
475 fn header_error_display() {
476 assert!(!HeaderError::InvalidName.to_string().is_empty());
477 assert!(!HeaderError::InvalidValue.to_string().is_empty());
478 assert!(!HeaderError::NameTooLong.to_string().is_empty());
479 }
480
481 #[test]
482 fn header_error_is_error() {
483 let err: &dyn std::error::Error = &HeaderError::InvalidName;
484 assert!(!err.to_string().is_empty());
485 }
486
487 #[test]
488 fn with_capacity() {
489 let block = HeaderBlock::with_capacity(10);
490 assert!(block.is_empty());
491 }
492}