mail_parser/parsers/fields/
address.rs1use crate::{Addr, Address, Group, HeaderValue, parsers::MessageStream};
8use std::borrow::Cow;
9
10#[derive(PartialEq, Clone, Copy, Debug)]
11enum AddressState {
12 Address,
13 Name,
14 Quote,
15 Comment,
16}
17
18pub struct AddressParser<'x> {
19 token_start: usize,
20 token_end: usize,
21
22 is_token_email: bool,
23 is_token_start: bool,
24 is_escaped: bool,
25
26 name_tokens: Vec<Cow<'x, str>>,
27 mail_tokens: Vec<Cow<'x, str>>,
28 comment_tokens: Vec<Cow<'x, str>>,
29
30 state: AddressState,
31 state_stack: Vec<AddressState>,
32
33 addresses: Vec<Addr<'x>>,
34 group_name: Option<Cow<'x, str>>,
35 group_comment: Option<Cow<'x, str>>,
36 result: Vec<Group<'x>>,
37}
38
39impl<'x> AddressParser<'x> {
40 pub fn add_token(&mut self, stream: &MessageStream<'x>, add_trail_space: bool) {
41 if self.token_start > 0 {
42 let token = String::from_utf8_lossy(&stream.data[self.token_start - 1..self.token_end]);
43 let mut add_space = false;
44 let list = match self.state {
45 AddressState::Address => &mut self.mail_tokens,
46 AddressState::Name => {
47 if self.is_token_email {
48 &mut self.mail_tokens
49 } else {
50 add_space = true;
51 &mut self.name_tokens
52 }
53 }
54 AddressState::Quote => &mut self.name_tokens,
55 AddressState::Comment => {
56 add_space = true;
57 &mut self.comment_tokens
58 }
59 };
60
61 if add_space && !list.is_empty() {
62 list.push(" ".into());
63 }
64
65 list.push(token);
66
67 if add_trail_space {
68 list.push(" ".into());
69 }
70
71 self.token_start = 0;
72 self.is_token_email = false;
73 self.is_token_start = true;
74 self.is_escaped = false;
75 }
76 }
77
78 pub fn add_address(&mut self) {
79 let has_mail = !self.mail_tokens.is_empty();
80 let has_name = !self.name_tokens.is_empty();
81 let has_comment = !self.comment_tokens.is_empty();
82
83 self.addresses.push(if has_mail && has_name && has_comment {
84 Addr {
85 name: Some(
86 format!(
87 "{} ({})",
88 concat_tokens(&mut self.name_tokens),
89 concat_tokens(&mut self.comment_tokens)
90 )
91 .into(),
92 ),
93 address: concat_tokens(&mut self.mail_tokens).into(),
94 }
95 } else if has_name && has_mail {
96 Addr {
97 name: concat_tokens(&mut self.name_tokens).into(),
98 address: concat_tokens(&mut self.mail_tokens).into(),
99 }
100 } else if has_mail && has_comment {
101 Addr {
102 name: concat_tokens(&mut self.comment_tokens).into(),
103 address: concat_tokens(&mut self.mail_tokens).into(),
104 }
105 } else if has_mail {
106 Addr {
107 name: None,
108 address: concat_tokens(&mut self.mail_tokens).into(),
109 }
110 } else if has_name && has_comment {
111 Addr {
112 name: concat_tokens(&mut self.comment_tokens).into(),
113 address: concat_tokens(&mut self.name_tokens).into(),
114 }
115 } else if has_name {
116 Addr {
117 name: concat_tokens(&mut self.name_tokens).into(),
118 address: None,
119 }
120 } else if has_comment {
121 Addr {
122 name: concat_tokens(&mut self.comment_tokens).into(),
123 address: None,
124 }
125 } else {
126 return;
127 });
128 }
129
130 pub fn add_group_details(&mut self) {
131 if !self.name_tokens.is_empty() {
132 self.group_name = concat_tokens(&mut self.name_tokens).into();
133 }
134
135 if !self.comment_tokens.is_empty() {
136 self.group_comment = concat_tokens(&mut self.comment_tokens).into();
137 }
138
139 if !self.mail_tokens.is_empty() {
140 if let Some(group_name) = &self.group_name {
141 self.group_name =
142 Some(format!("{} {}", group_name, concat_tokens(&mut self.mail_tokens)).into());
143 } else {
144 self.group_name = concat_tokens(&mut self.mail_tokens).into();
145 }
146 }
147 }
148
149 pub fn add_group(&mut self) {
150 let has_name = self.group_name.is_some();
151 let has_comment = self.group_comment.is_some();
152 let has_addresses = !self.addresses.is_empty();
153
154 self.result
155 .push(if has_name && has_addresses && has_comment {
156 Group {
157 name: Some(
158 format!(
159 "{} ({})",
160 self.group_name.take().unwrap(),
161 self.group_comment.take().unwrap()
162 )
163 .into(),
164 ),
165 addresses: std::mem::take(&mut self.addresses),
166 }
167 } else if has_addresses && has_name {
168 Group {
169 name: self.group_name.take(),
170 addresses: std::mem::take(&mut self.addresses),
171 }
172 } else if has_addresses {
173 Group {
174 name: self.group_comment.take(),
175 addresses: std::mem::take(&mut self.addresses),
176 }
177 } else if has_name {
178 Group {
179 name: self.group_name.take(),
180 addresses: Vec::new(),
181 }
182 } else {
183 return;
184 });
185 }
186}
187
188impl<'x> MessageStream<'x> {
189 pub fn parse_address(&mut self) -> HeaderValue<'x> {
190 let mut parser = AddressParser {
191 token_start: 0,
192 token_end: 0,
193
194 is_token_email: false,
195 is_token_start: true,
196 is_escaped: false,
197
198 name_tokens: Vec::with_capacity(3),
199 mail_tokens: Vec::with_capacity(3),
200 comment_tokens: Vec::with_capacity(3),
201
202 state: AddressState::Name,
203 state_stack: Vec::with_capacity(5),
204
205 addresses: Vec::new(),
206 group_name: None,
207 group_comment: None,
208 result: Vec::new(),
209 };
210
211 while let Some(ch) = self.next() {
212 match ch {
213 b'\n' => {
214 parser.add_token(self, false);
215 if parser.state == AddressState::Quote {
216 if self.peek_next_is_space() {
217 continue;
218 } else {
219 break;
220 }
221 }
222 if self.try_next_is_space() {
223 if !parser.is_token_start {
224 parser.is_token_start = true;
225 }
226 continue;
227 } else {
228 break;
229 }
230 }
231 b'\\' if parser.state != AddressState::Name && !parser.is_escaped => {
232 if parser.token_start > 0 {
233 if parser.state == AddressState::Quote {
234 parser.token_end = self.offset() - 1;
235 }
236 parser.add_token(self, false);
237 }
238 parser.is_escaped = true;
239 continue;
240 }
241 b',' if parser.state == AddressState::Name => {
242 parser.add_token(self, false);
243 parser.add_address();
244 continue;
245 }
246 b'<' if parser.state == AddressState::Name => {
247 parser.is_token_email = false;
248 parser.add_token(self, false);
249 parser.state_stack.push(AddressState::Name);
250 parser.state = AddressState::Address;
251 continue;
252 }
253 b'>' if parser.state == AddressState::Address => {
254 parser.add_token(self, false);
255 parser.state = parser.state_stack.pop().unwrap();
256 continue;
257 }
258 b'"' if !parser.is_escaped => match parser.state {
259 AddressState::Name => {
260 parser.state_stack.push(AddressState::Name);
261 parser.state = AddressState::Quote;
262 parser.add_token(self, false);
263 continue;
264 }
265 AddressState::Quote => {
266 parser.add_token(self, false);
267 parser.state = parser.state_stack.pop().unwrap();
268 continue;
269 }
270 _ => (),
271 },
272 b'@' if parser.state == AddressState::Name => {
273 parser.is_token_email = true;
274 }
275 b'=' if parser.is_token_start && !parser.is_escaped && self.peek_char(b'?') => {
276 self.checkpoint();
277 if let Some(token) = self.decode_rfc2047() {
278 let add_space = parser.state != AddressState::Quote; parser.add_token(self, add_space);
280 (if parser.state != AddressState::Comment {
281 &mut parser.name_tokens
282 } else {
283 &mut parser.comment_tokens
284 })
285 .push(token.into());
286 continue;
287 }
288 self.restore();
289 }
290 b' ' | b'\t' => {
291 if !parser.is_token_start {
292 parser.is_token_start = true;
293 }
294 if parser.is_escaped {
295 parser.is_escaped = false;
296 }
297 if parser.state == AddressState::Quote {
298 if parser.token_start == 0 {
299 parser.token_start = self.offset();
300 parser.token_end = parser.token_start;
301 } else {
302 parser.token_end = self.offset();
303 }
304 }
305 continue;
306 }
307 b'\r' => continue,
308 b'(' if parser.state != AddressState::Quote && !parser.is_escaped => {
309 parser.state_stack.push(parser.state);
310 if parser.state != AddressState::Comment {
311 parser.add_token(self, false);
312 parser.state = AddressState::Comment;
313 continue;
314 }
315 }
316 b')' if parser.state == AddressState::Comment && !parser.is_escaped => {
317 let new_state = parser.state_stack.pop().unwrap();
318 if parser.state != new_state {
319 parser.add_token(self, false);
320 parser.state = new_state;
321 continue;
322 }
323 }
324 b':' if parser.state == AddressState::Name && !parser.is_escaped => {
325 parser.add_group();
326 parser.add_token(self, false);
327 parser.add_group_details();
328 continue;
329 }
330 b';' if parser.state == AddressState::Name => {
331 parser.add_token(self, false);
332 parser.add_address();
333 parser.add_group();
334 continue;
335 }
336 _ => (),
337 }
338
339 if parser.is_escaped {
340 parser.is_escaped = false;
341 }
342
343 if parser.is_token_start {
344 parser.is_token_start = false;
345 }
346
347 if parser.token_start == 0 {
348 parser.token_start = self.offset();
349 parser.token_end = parser.token_start;
350 } else {
351 parser.token_end = self.offset();
352 }
353 }
354
355 parser.add_address();
356
357 if parser.group_name.is_some() || !parser.result.is_empty() {
358 parser.add_group();
359 HeaderValue::Address(Address::Group(parser.result))
360 } else if !parser.addresses.is_empty() {
361 HeaderValue::Address(Address::List(parser.addresses))
362 } else {
363 HeaderValue::Empty
364 }
365 }
366}
367
368fn concat_tokens<'x>(tokens: &mut Vec<Cow<'x, str>>) -> Cow<'x, str> {
369 if tokens.len() == 1 {
370 tokens.pop().unwrap()
371 } else {
372 let result = tokens.concat();
373 tokens.clear();
374 result.into()
375 }
376}
377
378pub fn parse_address_local_part(addr: &str) -> Option<&str> {
379 let addr = addr.as_bytes();
380 let mut iter = addr.iter().enumerate();
381 while let Some((pos, &ch)) = iter.next() {
382 if ch == b'@' {
383 return if pos > 0 && iter.next().is_some() {
384 std::str::from_utf8(addr.get(..pos)?).ok()
385 } else {
386 None
387 };
388 } else if !ch.is_ascii() {
389 return None;
390 }
391 }
392
393 None
394}
395
396pub fn parse_address_domain(addr: &str) -> Option<&str> {
397 let addr = addr.as_bytes();
398 for (pos, &ch) in addr.iter().enumerate() {
399 if ch == b'@' {
400 return if pos > 0 && pos + 1 < addr.len() {
401 std::str::from_utf8(addr.get(pos + 1..)?).ok()
402 } else {
403 None
404 };
405 } else if !ch.is_ascii() {
406 return None;
407 }
408 }
409
410 None
411}
412
413pub fn parse_address_user_part(addr: &str) -> Option<&str> {
414 let addr = addr.as_bytes();
415
416 let mut iter = addr.iter().enumerate();
417 while let Some((pos, &ch)) = iter.next() {
418 if ch == b'+' {
419 if pos > 0 {
420 while let Some((_, &ch)) = iter.next() {
421 if ch == b'@' && iter.next().is_some() {
422 return std::str::from_utf8(addr.get(..pos)?).ok();
423 }
424 }
425 }
426 return None;
427 } else if ch == b'@' {
428 return if pos > 0 && iter.next().is_some() {
429 std::str::from_utf8(addr.get(..pos)?).ok()
430 } else {
431 None
432 };
433 } else if !ch.is_ascii() {
434 return None;
435 }
436 }
437
438 None
439}
440
441pub fn parse_address_detail_part(addr: &str) -> Option<&str> {
442 let addr = addr.as_bytes();
443 let mut plus_pos = usize::MAX;
444
445 let mut iter = addr.iter().enumerate();
446 while let Some((pos, &ch)) = iter.next() {
447 if ch == b'+' {
448 plus_pos = pos + 1;
449 } else if ch == b'@' {
450 if plus_pos != usize::MAX && iter.next().is_some() {
451 return std::str::from_utf8(addr.get(plus_pos..pos)?).ok();
452 } else {
453 return None;
454 }
455 } else if !ch.is_ascii() {
456 return None;
457 }
458 }
459
460 None
461}
462
463#[cfg(test)]
464mod tests {
465 use crate::parsers::{MessageStream, fields::load_tests};
466
467 #[test]
468 fn parse_addresses() {
469 for test in load_tests("address.json") {
470 assert_eq!(
471 MessageStream::new(test.header.as_bytes())
472 .parse_address()
473 .unwrap_address(),
474 test.expected,
475 "failed for {:?}",
476 test.header
477 );
478 }
479 }
480}