Skip to main content

mail_parser/parsers/fields/
address.rs

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