mail_parser/parsers/
header.rs1use super::MessageStream;
8use crate::{Header, HeaderName, MessageParser, header_map};
9use std::borrow::Cow;
10const MAX_HEADER_NAME_LEN: usize = 34;
11
12impl<'x> MessageStream<'x> {
13 pub fn parse_headers(&mut self, conf: &MessageParser, headers: &mut Vec<Header<'x>>) -> bool {
14 loop {
15 loop {
16 match self.peek() {
17 Some(b'\n') => {
18 self.next();
19 return true;
20 }
21 None => return false,
22 Some(ch) if !ch.is_ascii_whitespace() => {
23 break;
24 }
25 _ => {
26 self.next();
27 }
28 }
29 }
30
31 let offset_field = self.offset();
32
33 if let Some(header_name) = self.parse_header_name() {
34 let from_offset = self.offset();
35 let value = if conf.header_map.is_empty() {
36 match &header_name {
37 HeaderName::Subject
38 | HeaderName::Comments
39 | HeaderName::ContentDescription
40 | HeaderName::ContentLocation
41 | HeaderName::ContentTransferEncoding => self.parse_unstructured(),
42 HeaderName::From
43 | HeaderName::To
44 | HeaderName::Cc
45 | HeaderName::Bcc
46 | HeaderName::ReplyTo
47 | HeaderName::Sender
48 | HeaderName::ResentTo
49 | HeaderName::ResentFrom
50 | HeaderName::ResentBcc
51 | HeaderName::ResentCc
52 | HeaderName::ResentSender
53 | HeaderName::ListArchive
54 | HeaderName::ListHelp
55 | HeaderName::ListId
56 | HeaderName::ListOwner
57 | HeaderName::ListPost
58 | HeaderName::ListSubscribe
59 | HeaderName::ListUnsubscribe => self.parse_address(),
60 HeaderName::Date | HeaderName::ResentDate => self.parse_date(),
61 HeaderName::MessageId
62 | HeaderName::References
63 | HeaderName::InReplyTo
64 | HeaderName::ReturnPath
65 | HeaderName::ContentId
66 | HeaderName::ResentMessageId => self.parse_id(),
67 HeaderName::Keywords | HeaderName::ContentLanguage => {
68 self.parse_comma_separared()
69 }
70 HeaderName::Received => self.parse_received(),
71 HeaderName::MimeVersion => self.parse_raw(),
72 HeaderName::ContentType | HeaderName::ContentDisposition => {
73 self.parse_content_type()
74 }
75 _ => self.parse_raw(),
76 }
77 } else {
78 (conf
79 .header_map
80 .get(&header_name)
81 .unwrap_or(&conf.def_hdr_parse_fnc))(self)
82 };
83
84 headers.push(Header {
85 name: header_name,
86 value,
87 offset_field: offset_field as u32,
88 offset_start: from_offset as u32,
89 offset_end: self.offset() as u32,
90 });
91 } else if self.is_eof() {
92 return false;
93 }
94 }
95 }
96
97 pub fn parse_header_name(&mut self) -> Option<HeaderName<'x>> {
98 let mut token_start: usize = 0;
99 let mut token_end: usize = 0;
100 let mut token_len: usize = 0;
101
102 let mut header = [0u8; MAX_HEADER_NAME_LEN];
103
104 while let Some(&ch) = self.next() {
105 match ch {
106 b':' => {
107 if token_start != 0 {
108 break;
109 }
110 }
111 b'\n' => {
112 return None;
113 }
114 _ => {
115 if !ch.is_ascii_whitespace() {
116 if token_start == 0 {
117 token_start = self.offset();
118 token_end = token_start;
119 } else {
120 token_end = self.offset();
121 }
122
123 if let Some(header) = header.get_mut(token_len) {
124 *header = ch.to_ascii_lowercase();
125 }
126 token_len += 1;
127 }
128 }
129 }
130 }
131
132 if token_start != 0 {
133 header
134 .get(..token_len)
135 .and_then(header_map)
136 .unwrap_or_else(|| {
137 HeaderName::Other(String::from_utf8_lossy(
138 self.bytes(token_start - 1..token_end),
139 ))
140 })
141 .into()
142 } else {
143 None
144 }
145 }
146}
147
148impl<'x> HeaderName<'x> {
149 pub fn parse(data: impl Into<Cow<'x, str>>) -> Option<HeaderName<'x>> {
151 let data = data.into();
152
153 if !data.is_empty() {
154 let mut data_lc = String::with_capacity(data.len());
155 for ch in data.chars() {
156 match ch {
157 'A'..='Z' => data_lc.push(ch.to_ascii_lowercase()),
158 'a'..='z' | '0'..='9' | '-' | '_' => data_lc.push(ch),
159 _ => return None,
160 }
161 }
162 header_map(data_lc.as_bytes())
163 .unwrap_or(HeaderName::Other(data))
164 .into()
165 } else {
166 None
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use crate::{HeaderName, parsers::MessageStream};
174
175 #[test]
176 fn header_name_parse() {
177 let inputs = [
178 ("From: ", HeaderName::From),
179 ("receiVED: ", HeaderName::Received),
180 (" subject : ", HeaderName::Subject),
181 (
182 "X-Custom-Field : ",
183 HeaderName::Other("X-Custom-Field".into()),
184 ),
185 (" T : ", HeaderName::Other("T".into())),
186 ("mal formed: ", HeaderName::Other("mal formed".into())),
187 ("MIME-version : ", HeaderName::MimeVersion),
188 ("Delivered-To: ", HeaderName::DeliveredTo),
189 ("archived-AT: ", HeaderName::ArchivedAt),
190 ("X-Face: ", HeaderName::Other("X-Face".into())),
191 (
192 "Disposition-Notification-Options: ",
193 HeaderName::DispositionNotificationOptions,
194 ),
195 (
196 "MMHS-Other-Recipients-Indicator-To: ",
197 HeaderName::MmhsOtherRecipientsIndicatorTo,
198 ),
199 (
200 "Original-Encoded-Information-Types-Extra: ",
201 HeaderName::Other("Original-Encoded-Information-Types-Extra".into()),
202 ),
203 ];
204
205 for (input, expected_result) in inputs {
206 assert_eq!(
207 expected_result,
208 MessageStream::new(input.as_bytes())
209 .parse_header_name()
210 .unwrap(),
211 "Failed to parse '{input:?}'",
212 );
213 }
214 }
215}