mail_parser/parsers/fields/
received.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 std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8
9use crate::{
10    parsers::MessageStream, DateTime, Greeting, HeaderValue, Host, Protocol, Received, TlsVersion,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14enum Token {
15    BracketOpen,
16    BracketClose,
17    AngleOpen,
18    AngleClose,
19    ParenthesisOpen,
20    ParenthesisClose,
21    Semicolon,
22    Colon,
23    Equal,
24    Slash,
25    Quote,
26    Comma,
27    IpAddr(IpAddr),
28    Integer(i64),
29    Text,
30    Domain,
31    Email,
32    Month(Month),
33    Protocol(Protocol),
34    Greeting(Greeting),
35    TlsVersion(TlsVersion),
36    Cipher,
37    By,
38    For,
39    From,
40    Id,
41    Via,
42    With,
43    Ident,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47struct TokenData<'x> {
48    token: Token,
49    text: &'x str,
50    comment_depth: u32,
51    bracket_depth: u32,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55enum Month {
56    Jan,
57    Feb,
58    Mar,
59    Apr,
60    May,
61    Jun,
62    Jul,
63    Aug,
64    Sep,
65    Oct,
66    Nov,
67    Dec,
68}
69
70struct Tokenizer<'x, 'y> {
71    stream: &'y mut MessageStream<'x>,
72    next_token: Option<TokenData<'x>>,
73    eof: bool,
74    in_quote: bool,
75    bracket_depth: u32,
76    comment_depth: u32,
77    in_date: bool,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81enum State {
82    From,
83    By,
84    For,
85    Id,
86    With,
87    Via,
88    Date,
89    None,
90}
91
92impl<'x> MessageStream<'x> {
93    pub fn parse_received(&mut self) -> HeaderValue<'x> {
94        //let c = print!("-> {}", std::str::from_utf8(self.data).unwrap());
95
96        let mut tokenizer = Tokenizer::new(self).peekable();
97        let mut received = Received::default();
98
99        let mut state = State::None;
100        let mut date = [i64::MAX; 7];
101        let mut date_part = date.iter_mut();
102
103        while let Some(token) = tokenizer.next() {
104            match token.token {
105                Token::From if received.from.is_none() => {
106                    // Try obtaining hostname
107                    while let Some(token) = tokenizer.peek() {
108                        match token.token {
109                            Token::BracketOpen => {
110                                tokenizer.next();
111                            }
112                            Token::IpAddr(ip) => {
113                                tokenizer.next();
114                                received.from = Some(Host::IpAddr(ip));
115                                break;
116                            }
117                            _ => {
118                                if !token.token.is_separator() {
119                                    received.from =
120                                        Some(Host::Name(tokenizer.next().unwrap().text.into()));
121                                }
122                                break;
123                            }
124                        }
125                    }
126                    state = State::From;
127                }
128                Token::By if token.comment_depth == 0 => {
129                    // Try obtaining hostname
130                    while let Some(token) = tokenizer.peek() {
131                        match token.token {
132                            Token::BracketOpen | Token::AngleOpen => {
133                                tokenizer.next();
134                            }
135                            Token::IpAddr(ip) => {
136                                tokenizer.next();
137                                received.by = Some(Host::IpAddr(ip));
138                                break;
139                            }
140                            _ => {
141                                if !token.token.is_separator() {
142                                    received.by =
143                                        Some(Host::Name(tokenizer.next().unwrap().text.into()));
144                                }
145                                break;
146                            }
147                        }
148                    }
149                    state = State::By;
150                }
151                Token::For if token.comment_depth == 0 => {
152                    while let Some(token) = tokenizer.peek() {
153                        match token.token {
154                            Token::Equal | Token::AngleOpen => {
155                                tokenizer.next();
156                            }
157                            Token::Email => {
158                                received.for_ = Some(tokenizer.next().unwrap().text.into());
159                                break;
160                            }
161                            _ => {
162                                break;
163                            }
164                        }
165                    }
166                    state = State::For;
167                }
168                Token::Semicolon if token.comment_depth == 0 => {
169                    state = State::Date;
170                }
171                Token::Id if token.comment_depth == 0 => {
172                    while let Some(token) = tokenizer.peek() {
173                        match token.token {
174                            Token::Equal | Token::AngleOpen | Token::BracketOpen | Token::Colon => {
175                                tokenizer.next();
176                            }
177                            _ => {
178                                if !token.token.is_separator() {
179                                    received.id = Some(tokenizer.next().unwrap().text.into());
180                                }
181                                break;
182                            }
183                        }
184                    }
185                    state = State::Id;
186                }
187                Token::With if token.comment_depth == 0 => {
188                    while let Some(token) = tokenizer.peek() {
189                        match token.token {
190                            Token::Protocol(proto) => {
191                                tokenizer.next();
192                                received.with = Some(proto);
193                                break;
194                            }
195                            Token::Semicolon
196                            | Token::TlsVersion(_)
197                            | Token::By
198                            | Token::For
199                            | Token::From
200                            | Token::Id
201                            | Token::Via
202                            | Token::With => {
203                                break;
204                            }
205                            _ => {
206                                tokenizer.next();
207                            }
208                        }
209                    }
210                    state = State::With;
211                }
212                Token::Via if token.comment_depth == 0 => {
213                    while let Some(token) = tokenizer.peek() {
214                        match token.token {
215                            Token::Equal => {
216                                tokenizer.next();
217                            }
218                            _ => {
219                                if !token.token.is_separator() {
220                                    received.via = Some(tokenizer.next().unwrap().text.into());
221                                }
222                                break;
223                            }
224                        }
225                    }
226                    state = State::Via;
227                }
228                Token::Ident if token.comment_depth > 0 => {
229                    while let Some(token) = tokenizer.peek() {
230                        match token.token {
231                            Token::Equal | Token::AngleOpen | Token::BracketOpen | Token::Colon => {
232                                tokenizer.next();
233                            }
234                            _ => {
235                                if !token.token.is_separator() {
236                                    received.ident = Some(tokenizer.next().unwrap().text.into());
237                                }
238                                break;
239                            }
240                        }
241                    }
242                }
243                Token::Greeting(greeting) if state == State::From && token.comment_depth > 0 => {
244                    // Try obtaining hostname
245                    received.helo_cmd = Some(greeting);
246                    while let Some(token) = tokenizer.peek() {
247                        match token.token {
248                            Token::Equal | Token::BracketOpen | Token::Colon => {
249                                tokenizer.next();
250                            }
251                            Token::IpAddr(ip) => {
252                                tokenizer.next();
253                                received.helo = Some(Host::IpAddr(ip));
254                                break;
255                            }
256                            _ => {
257                                if !token.token.is_separator() {
258                                    received.helo =
259                                        Some(Host::Name(tokenizer.next().unwrap().text.into()));
260                                }
261                                break;
262                            }
263                        }
264                    }
265                }
266                Token::IpAddr(ip) => {
267                    if state == State::From
268                        && (token.bracket_depth > 0
269                            || (token.comment_depth > 0 && received.from_ip.is_none()))
270                    {
271                        received.from_ip = Some(ip);
272                    }
273                }
274                Token::Domain => {
275                    if state == State::From && token.comment_depth > 0 {
276                        received.from_iprev = Some(token.text.into());
277                    }
278                }
279                Token::Email => {
280                    if state == State::From {
281                        received.ident =
282                            Some(token.text.strip_suffix('@').unwrap_or(token.text).into());
283                    }
284                }
285                Token::Integer(num) => {
286                    if state == State::Date {
287                        if let Some(part) = date_part.next() {
288                            *part = num;
289                        }
290                    }
291                }
292                Token::Month(month) => {
293                    if state == State::Date {
294                        if let Some(part) = date_part.next() {
295                            *part = month.to_number();
296                        }
297                    }
298                }
299                Token::Cipher => {
300                    if token.comment_depth > 0 || received.tls_cipher.is_none() {
301                        received.tls_cipher = Some(token.text.into());
302                    }
303                }
304                Token::TlsVersion(tls)
305                    if token.comment_depth > 0 && received.tls_version.is_none() =>
306                {
307                    received.tls_version = Some(tls);
308                }
309                _ => (),
310            }
311        }
312
313        if date[5] != i64::MAX {
314            let (tz, is_plus) = if date[6] != i64::MAX {
315                if date[6] < 0 {
316                    (date[6].abs(), false)
317                } else {
318                    (date[6], true)
319                }
320            } else {
321                (0, false)
322            };
323            received.date = DateTime {
324                year: if (1..=99).contains(&date[2]) {
325                    date[2] + 1900
326                } else {
327                    date[2]
328                } as u16,
329                month: date[1] as u8,
330                day: date[0] as u8,
331                hour: date[3] as u8,
332                minute: date[4] as u8,
333                second: date[5] as u8,
334                tz_hour: (tz / 100) as u8,
335                tz_minute: (tz % 100) as u8,
336                tz_before_gmt: !is_plus,
337            }
338            .into();
339        }
340
341        if received.from.is_some()
342            || received.from_ip.is_some()
343            || received.from_iprev.is_some()
344            || received.by.is_some()
345            || received.for_.is_some()
346            || received.with.is_some()
347            || received.tls_version.is_some()
348            || received.tls_cipher.is_some()
349            || received.id.is_some()
350            || received.ident.is_some()
351            || received.helo.is_some()
352            || received.helo_cmd.is_some()
353            || received.via.is_some()
354            || received.date.is_some()
355        {
356            HeaderValue::Received(Box::new(received))
357        } else {
358            HeaderValue::Empty
359        }
360    }
361}
362
363impl<'x> Iterator for Tokenizer<'x, '_> {
364    type Item = TokenData<'x>;
365
366    fn next(&mut self) -> Option<Self::Item> {
367        if let Some(next_token) = self.next_token.take() {
368            return Some(next_token);
369        } else if self.eof {
370            return None;
371        }
372        let mut n_alpha = 0; // 1
373        let mut n_digit = 0; // 2
374        let mut n_hex = 0; // 3
375        let mut n_dot = 0; // 4
376        let mut n_at = 0; // 5
377        let mut n_other = 0; // 6
378        let mut n_colon = 0; // 7
379        let mut n_plus = 0; // 8
380        let mut n_minus = 0; // 9
381        let mut n_utf = 0; // 10
382        let mut n_uppercase = 0;
383        let mut n_underscore = 0;
384
385        let mut n_total = 0;
386
387        let mut hash: u128 = 0;
388        let mut hash_shift = 0;
389
390        let comment_depth = self.comment_depth;
391        let bracket_depth = self.bracket_depth;
392
393        let mut start_pos = self.stream.offset();
394
395        while let Some(ch) = self.stream.next() {
396            match ch {
397                b'0'..=b'9' => {
398                    n_digit += 1;
399                    if hash_shift < 128 {
400                        hash |= (*ch as u128) << hash_shift;
401                        hash_shift += 8;
402                    }
403                }
404                b'a'..=b'f' => {
405                    n_hex += 1;
406                    if hash_shift < 128 {
407                        hash |= (*ch as u128) << hash_shift;
408                        hash_shift += 8;
409                    }
410                }
411                b'g'..=b'z' => {
412                    n_alpha += 1;
413                    if hash_shift < 128 {
414                        hash |= (*ch as u128) << hash_shift;
415                        hash_shift += 8;
416                    }
417                }
418                b'A'..=b'F' => {
419                    n_hex += 1;
420                    n_uppercase += 1;
421                    if hash_shift < 128 {
422                        hash |= ((*ch - b'A' + b'a') as u128) << hash_shift;
423                        hash_shift += 8;
424                    }
425                }
426                b'G'..=b'Z' => {
427                    n_alpha += 1;
428                    n_uppercase += 1;
429                    if hash_shift < 128 {
430                        hash |= ((*ch - b'A' + b'a') as u128) << hash_shift;
431                        hash_shift += 8;
432                    }
433                }
434                b'@' => {
435                    n_at += 1;
436                }
437                b'.' => {
438                    n_dot += 1;
439                }
440                b'+' => {
441                    n_plus += 1;
442                }
443                b'-' => {
444                    n_minus += 1;
445                }
446                b'\n' => {
447                    if !self.stream.try_next_is_space() {
448                        self.eof = true;
449                        break;
450                    } else if n_total > 0 {
451                        break;
452                    } else {
453                        start_pos += 1;
454                    }
455                }
456                b'(' => {
457                    if !self.in_quote {
458                        self.comment_depth = self.comment_depth.saturating_add(1);
459                    }
460                    self.next_token = Some(Token::ParenthesisOpen.into());
461                    break;
462                }
463                b')' => {
464                    if !self.in_quote {
465                        self.comment_depth = self.comment_depth.saturating_sub(1);
466                    }
467                    self.next_token = Some(Token::ParenthesisClose.into());
468                    break;
469                }
470                b'<' => {
471                    self.next_token = Some(Token::AngleOpen.into());
472                    break;
473                }
474                b'>' => {
475                    self.next_token = Some(Token::AngleClose.into());
476                    break;
477                }
478                b'[' => {
479                    if !self.in_quote {
480                        self.bracket_depth = self.comment_depth.saturating_add(1);
481                    }
482                    self.next_token = Some(Token::BracketOpen.into());
483                    break;
484                }
485                b']' => {
486                    if !self.in_quote {
487                        self.bracket_depth = self.comment_depth.saturating_sub(1);
488                    }
489                    self.next_token = Some(Token::BracketClose.into());
490                    break;
491                }
492                b':' => {
493                    // The current token might an IPv6 address
494                    if self.in_date
495                        || n_at > 0
496                        || n_dot > 0
497                        || n_alpha > 0
498                        || n_other > 0
499                        || n_plus > 0
500                        || n_minus > 0
501                        || n_utf > 0
502                        || n_colon == 7
503                    {
504                        self.next_token = Some(Token::Colon.into());
505                        break;
506                    } else {
507                        n_colon += 1;
508                    }
509                }
510                b'=' => {
511                    self.next_token = Some(Token::Equal.into());
512                    break;
513                }
514                b';' => {
515                    if self.comment_depth == 0 {
516                        self.in_date = true;
517                    }
518                    self.next_token = Some(Token::Semicolon.into());
519                    break;
520                }
521                b'/' => {
522                    self.next_token = Some(Token::Slash.into());
523                    break;
524                }
525                b'"' => {
526                    self.in_quote = !self.in_quote;
527                    self.next_token = Some(Token::Quote.into());
528                    break;
529                }
530                b',' => {
531                    self.next_token = Some(Token::Comma.into());
532                    break;
533                }
534                b' ' | b'\t' | b'\r' => {
535                    if n_total > 0 {
536                        break;
537                    } else {
538                        start_pos += 1;
539                        continue;
540                    }
541                }
542                0x7f..=u8::MAX => {
543                    n_utf += 1;
544                }
545                b'_' => {
546                    n_underscore += 1;
547                    n_other += 1;
548                }
549                _ => {
550                    n_other += 1;
551                }
552            }
553
554            n_total += 1;
555        }
556
557        if n_total == 0 {
558            return self.next_token.take();
559        }
560
561        let text = std::str::from_utf8(self.stream.bytes(start_pos..self.stream.offset() - 1))
562            .unwrap_or_default();
563
564        let token = match (
565            n_alpha, n_digit, n_hex, n_dot, n_at, n_other, n_colon, n_plus, n_minus, n_utf, hash,
566        ) {
567            (0, 4..=12, 0, 3, 0, 0, 0, 0, 0, 0, _) => {
568                // IPv4 address
569                text.parse::<Ipv4Addr>()
570                    .map(|ip| Token::IpAddr(IpAddr::V4(ip)))
571                    .unwrap_or(Token::Text)
572            }
573            (0, _, 1..=32, 0, 0, 0, 2.., 0, 0, 0, _)
574            | (0, 1..=32, _, 0, 0, 0, 2.., 0, 0, 0, _)
575            | (0, 4..=12, 4, 3, 0, 0, 3, 0, 0, 0, _) => {
576                // IPv6 address
577                text.parse::<Ipv6Addr>()
578                    .map(|ip| Token::IpAddr(IpAddr::V6(ip)))
579                    .unwrap_or(Token::Text)
580            }
581            (0, 1.., 0, 0, 0, 0, 0, 0, 0, 0, _)
582            | (0, 1.., 0, 0, 0, 0, 0, 0, 1, 0, _)
583            | (0, 1.., 0, 0, 0, 0, 0, 1, 0, 0, _) => {
584                // Integer
585                text.parse::<i64>()
586                    .map(Token::Integer)
587                    .unwrap_or(Token::Text)
588            }
589            (1.., _, _, _, 1, _, _, _, _, _, _) | (_, _, 1.., _, 1, _, _, _, _, _, _) => {
590                // E-mail address
591                Token::Email
592            }
593            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x727061) => Token::Month(Month::Apr),
594            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7362) => Token::Protocol(Protocol::SMTP),
595            (1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7962) => Token::By,
596            (0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0x636564) => Token::Month(Month::Dec),
597            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6f6c6865) => Token::Greeting(Greeting::Ehlo),
598            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7365) => Token::Protocol(Protocol::ESMTP),
599            (4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d7365) => Token::Protocol(Protocol::ESMTPA),
600            (5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d7365) => Token::Protocol(Protocol::ESMTPS),
601            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x726f66) => Token::For,
602            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6d6f7266) => Token::From,
603            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6f6c6568) => Token::Greeting(Greeting::Helo),
604            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70747468) => Token::Protocol(Protocol::HTTP),
605            (7, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7473657270747468) => Token::Protocol(Protocol::HTTP),
606            (1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6469) => Token::Id,
607            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70616d69) => Token::Protocol(Protocol::IMAP),
608            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6e616a) => Token::Month(Month::Jan),
609            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6c756a) => Token::Month(Month::Jul),
610            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6e756a) => Token::Month(Month::Jun),
611            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6f6c686c) => Token::Greeting(Greeting::Lhlo),
612            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d6c) => Token::Protocol(Protocol::LMTP),
613            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6170746d6c) => Token::Protocol(Protocol::LMTPA),
614            (3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x6c61636f6c) => Token::Protocol(Protocol::Local),
615            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d736c) => Token::Protocol(Protocol::LMTP),
616            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x72616d) => Token::Month(Month::Mar),
617            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x79616d) => Token::Month(Month::May),
618            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x766f6e) => Token::Month(Month::Nov),
619            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x33706f70) => Token::Protocol(Protocol::POP3),
620            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x706573) => Token::Month(Month::Sep),
621            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d73) => Token::Protocol(Protocol::SMTP),
622            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6470746d73) => Token::Protocol(Protocol::SMTP),
623            (6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x63767370746d73) => Token::Protocol(Protocol::SMTP),
624            (4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x74656b636f73) => Token::Protocol(Protocol::Local),
625            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6e69647473) => Token::Protocol(Protocol::Local),
626            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x616976) => Token::Via,
627            (0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0x626566) => Token::Month(Month::Feb),
628            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x677561) => Token::Month(Month::Aug),
629            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x74636f) => Token::Month(Month::Oct),
630            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x68746977) => Token::With,
631            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7361) => Token::Protocol(Protocol::ESMTPA),
632            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7570747468) => Token::Protocol(Protocol::HTTP),
633            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7370747468) => Token::Protocol(Protocol::HTTPS),
634            (3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x746e656469) => Token::Ident,
635            (5, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d7365) => Token::Protocol(Protocol::ESMTPSA),
636            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7370746d6c) => Token::Protocol(Protocol::LMTPS),
637            (5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x617370746d6c) => Token::Protocol(Protocol::LMTPSA),
638            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x736d6d) => Token::Protocol(Protocol::MMS),
639            (6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7338667475) => {
640                Token::Protocol(Protocol::UTF8SMTP)
641            }
642            (6, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d7338667475) => {
643                Token::Protocol(Protocol::UTF8SMTPA)
644            }
645            (7, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d7338667475) => {
646                Token::Protocol(Protocol::UTF8SMTPS)
647            }
648            (7, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d7338667475) => {
649                Token::Protocol(Protocol::UTF8SMTPSA)
650            }
651            (6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d6c38667475) => {
652                Token::Protocol(Protocol::UTF8LMTP)
653            }
654            (6, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d6c38667475) => {
655                Token::Protocol(Protocol::UTF8LMTPA)
656            }
657            (7, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d6c38667475) => {
658                Token::Protocol(Protocol::UTF8LMTPS)
659            }
660            (7, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d6c38667475) => {
661                Token::Protocol(Protocol::UTF8LMTPSA)
662            }
663            (7, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x70746d73656c61636f6c) => {
664                Token::Protocol(Protocol::ESMTP)
665            }
666            (8, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x7370746d73656c61636f6c) => {
667                Token::Protocol(Protocol::ESMTPS)
668            }
669            (7, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x70746d73626c61636f6c) => {
670                Token::Protocol(Protocol::SMTP)
671            }
672            (7, 0, 1, 0, 0, 0, 0, 0, _, 0, 0x736c7470746d7365) => Token::Protocol(Protocol::ESMTPS),
673            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3031736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
674            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3131736c74) => Token::TlsVersion(TlsVersion::TLSv1_1),
675            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3231736c74) => Token::TlsVersion(TlsVersion::TLSv1_2),
676            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3331736c74) => Token::TlsVersion(TlsVersion::TLSv1_3),
677            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x303176736c74) => {
678                Token::TlsVersion(TlsVersion::TLSv1_0)
679            }
680            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x313176736c74) => {
681                Token::TlsVersion(TlsVersion::TLSv1_1)
682            }
683            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x323176736c74) => {
684                Token::TlsVersion(TlsVersion::TLSv1_2)
685            }
686            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x333176736c74) => {
687                Token::TlsVersion(TlsVersion::TLSv1_3)
688            }
689            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x326c7373) => Token::TlsVersion(TlsVersion::SSLv2),
690            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x336c7373) => Token::TlsVersion(TlsVersion::SSLv3),
691            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x32766c7373) => Token::TlsVersion(TlsVersion::SSLv2),
692            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x33766c7373) => Token::TlsVersion(TlsVersion::SSLv3),
693            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x31736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
694            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x3176736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
695            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x303176736c7464) => {
696                Token::TlsVersion(TlsVersion::DTLSv1_0)
697            }
698            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x323176736c7464) => {
699                Token::TlsVersion(TlsVersion::DTLSv1_2)
700            }
701            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x333176736c7464) => {
702                Token::TlsVersion(TlsVersion::DTLSv1_3)
703            }
704            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3031736c7464) => {
705                Token::TlsVersion(TlsVersion::DTLSv1_0)
706            }
707            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3231736c7464) => {
708                Token::TlsVersion(TlsVersion::DTLSv1_2)
709            }
710            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3331736c7464) => {
711                Token::TlsVersion(TlsVersion::DTLSv1_3)
712            }
713            (1.., _, _, 1.., 0, 0, 0, 0, _, _, _) | (_, _, 1.., 1.., 0, 0, 0, 0, _, _, _) => {
714                // Domain name
715                Token::Domain
716            }
717            _ => {
718                // Try ciphersuite
719                if n_alpha + n_hex == n_uppercase
720                    && n_total > 6
721                    && (n_underscore > 0 || n_minus > 0)
722                    && n_digit > 0
723                    && n_dot == 0
724                    && n_at == 0
725                    && n_plus == 0
726                    && (n_other == 0 || n_other == n_underscore)
727                    && n_colon == 0
728                    && n_utf == 0
729                    && [
730                        0x617372, 0x646365, 0x646365, 0x656864, 0x6b7370, 0x707273, 0x736561,
731                        0x736564, 0x736c74,
732                    ]
733                    .contains(&(hash & 0xffffff))
734                {
735                    Token::Cipher
736                } else {
737                    Token::Text
738                }
739            }
740        };
741
742        TokenData {
743            text,
744            token,
745            comment_depth,
746            bracket_depth,
747        }
748        .into()
749    }
750}
751
752impl<'x, 'y> Tokenizer<'x, 'y> {
753    fn new(stream: &'y mut MessageStream<'x>) -> Self {
754        Self {
755            stream,
756            next_token: None,
757            eof: false,
758            in_quote: false,
759            bracket_depth: 0,
760            comment_depth: 0,
761            in_date: false,
762        }
763    }
764}
765
766impl From<Token> for TokenData<'_> {
767    fn from(token: Token) -> Self {
768        Self {
769            token,
770            text: "",
771            comment_depth: 0,
772            bracket_depth: 0,
773        }
774    }
775}
776
777impl Month {
778    fn to_number(self) -> i64 {
779        match self {
780            Month::Jan => 1,
781            Month::Feb => 2,
782            Month::Mar => 3,
783            Month::Apr => 4,
784            Month::May => 5,
785            Month::Jun => 6,
786            Month::Jul => 7,
787            Month::Aug => 8,
788            Month::Sep => 9,
789            Month::Oct => 10,
790            Month::Nov => 11,
791            Month::Dec => 12,
792        }
793    }
794}
795
796impl Token {
797    fn is_separator(&self) -> bool {
798        matches!(
799            self,
800            Token::BracketOpen
801                | Token::BracketClose
802                | Token::AngleOpen
803                | Token::AngleClose
804                | Token::ParenthesisOpen
805                | Token::ParenthesisClose
806                | Token::Semicolon
807                | Token::Colon
808                | Token::Equal
809                | Token::Slash
810                | Token::Quote
811                | Token::Comma
812        )
813    }
814}
815
816#[cfg(test)]
817mod tests {
818
819    use crate::parsers::{fields::load_tests, MessageStream};
820
821    #[test]
822    fn parse_received() {
823        for test in load_tests("received.json") {
824            assert_eq!(
825                MessageStream::new(test.header.as_bytes())
826                    .parse_received()
827                    .unwrap_received(),
828                test.expected,
829                "failed for {:?}",
830                test.header
831            );
832        }
833    }
834}