1#[derive(Debug, Default, Clone)]
14pub struct RawHeaders {
15 fields: Vec<(String, String)>,
16}
17
18impl RawHeaders {
19 pub fn parse(raw: &[u8]) -> Self {
21 let text = String::from_utf8_lossy(header_block(raw));
22 let mut fields: Vec<(String, String)> = Vec::new();
23 for line in text.split('\n') {
24 push_line(&mut fields, line.strip_suffix('\r').unwrap_or(line));
25 }
26 RawHeaders { fields }
27 }
28
29 pub fn all<'a>(&'a self, name: &str) -> impl Iterator<Item = &'a str> + use<'a> {
31 let name = name.to_string();
32 self.fields
33 .iter()
34 .filter(move |(n, _)| n.eq_ignore_ascii_case(&name))
35 .map(|(_, v)| v.as_str())
36 }
37
38 pub fn first(&self, name: &str) -> Option<&str> {
40 self.all(name).next()
41 }
42
43 pub fn has(&self, name: &str) -> bool {
45 self.first(name).is_some()
46 }
47
48 pub fn fields(&self) -> impl Iterator<Item = (&str, &str)> {
50 self.fields.iter().map(|(name, value)| (name.as_str(), value.as_str()))
51 }
52
53 pub fn top(&self, n: usize) -> RawHeaders {
55 RawHeaders { fields: self.fields.iter().take(n).cloned().collect() }
56 }
57}
58
59fn header_block(raw: &[u8]) -> &[u8] {
63 let mut start = 0;
64 for line in raw.split(|&b| b == b'\n') {
65 if line.is_empty() || line == b"\r" {
66 return &raw[..start];
67 }
68 start += line.len() + 1;
69 }
70 raw
71}
72
73fn push_line(fields: &mut Vec<(String, String)>, line: &str) {
75 let continues = line.starts_with(' ') || line.starts_with('\t');
76 if let (true, Some(last)) = (continues, fields.last_mut()) {
77 last.1.push(' ');
78 last.1.push_str(line.trim());
79 } else if let Some((name, value)) = line.split_once(':') {
80 fields.push((name.trim().to_string(), value.trim().to_string()));
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn unfolds_and_keeps_order() {
90 let raw = b"Received: from a\r\n by b\r\nReceived: from c\r\nSubject: hi\r\n\r\nbody: not a header\r\n";
91 let h = RawHeaders::parse(raw);
92 let received: Vec<&str> = h.all("received").collect();
93 assert_eq!(received, vec!["from a by b", "from c"]);
94 assert_eq!(h.first("Subject"), Some("hi"));
95 assert!(!h.has("body"));
96 let raw = b"Subject: hi\n\nAuthentication-Results: mx.example.net; dmarc=pass\r\n\r\nbye\r\n";
98 assert!(!RawHeaders::parse(raw).has("Authentication-Results"));
99 }
100}