Skip to main content

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