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    DateTime, Greeting, HeaderValue, Host, Protocol, Received, TlsVersion, parsers::MessageStream,
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        let mut end_pos = start_pos;
383
384        while let Some(ch) = self.stream.next() {
385            match ch {
386                b'0'..=b'9' => {
387                    n_digit += 1;
388                    if hash_shift < 128 {
389                        hash |= (*ch as u128) << hash_shift;
390                        hash_shift += 8;
391                    }
392                }
393                b'a'..=b'f' => {
394                    n_hex += 1;
395                    if hash_shift < 128 {
396                        hash |= (*ch as u128) << hash_shift;
397                        hash_shift += 8;
398                    }
399                }
400                b'g'..=b'z' => {
401                    n_alpha += 1;
402                    if hash_shift < 128 {
403                        hash |= (*ch as u128) << hash_shift;
404                        hash_shift += 8;
405                    }
406                }
407                b'A'..=b'F' => {
408                    n_hex += 1;
409                    n_uppercase += 1;
410                    if hash_shift < 128 {
411                        hash |= ((*ch - b'A' + b'a') as u128) << hash_shift;
412                        hash_shift += 8;
413                    }
414                }
415                b'G'..=b'Z' => {
416                    n_alpha += 1;
417                    n_uppercase += 1;
418                    if hash_shift < 128 {
419                        hash |= ((*ch - b'A' + b'a') as u128) << hash_shift;
420                        hash_shift += 8;
421                    }
422                }
423                b'@' => {
424                    n_at += 1;
425                }
426                b'.' => {
427                    n_dot += 1;
428                }
429                b'+' => {
430                    n_plus += 1;
431                }
432                b'-' => {
433                    n_minus += 1;
434                }
435                b'\n' => {
436                    if !self.stream.try_next_is_space() {
437                        self.eof = true;
438                        break;
439                    } else if n_total > 0 {
440                        break;
441                    } else {
442                        start_pos = self.stream.offset();
443                        end_pos = start_pos;
444                        continue;
445                    }
446                }
447                b'(' => {
448                    if !self.in_quote {
449                        self.comment_depth = self.comment_depth.saturating_add(1);
450                    }
451                    self.next_token = Some(Token::ParenthesisOpen.into());
452                    break;
453                }
454                b')' => {
455                    if !self.in_quote {
456                        self.comment_depth = self.comment_depth.saturating_sub(1);
457                    }
458                    self.next_token = Some(Token::ParenthesisClose.into());
459                    break;
460                }
461                b'<' => {
462                    self.next_token = Some(Token::AngleOpen.into());
463                    break;
464                }
465                b'>' => {
466                    self.next_token = Some(Token::AngleClose.into());
467                    break;
468                }
469                b'[' => {
470                    if !self.in_quote {
471                        self.bracket_depth = self.comment_depth.saturating_add(1);
472                    }
473                    self.next_token = Some(Token::BracketOpen.into());
474                    break;
475                }
476                b']' => {
477                    if !self.in_quote {
478                        self.bracket_depth = self.comment_depth.saturating_sub(1);
479                    }
480                    self.next_token = Some(Token::BracketClose.into());
481                    break;
482                }
483                b':' => {
484                    // The current token might an IPv6 address
485                    if self.in_date
486                        || n_at > 0
487                        || n_dot > 0
488                        || n_alpha > 0
489                        || n_other > 0
490                        || n_plus > 0
491                        || n_minus > 0
492                        || n_utf > 0
493                        || n_colon == 7
494                    {
495                        self.next_token = Some(Token::Colon.into());
496                        break;
497                    } else {
498                        n_colon += 1;
499                    }
500                }
501                b'=' => {
502                    self.next_token = Some(Token::Equal.into());
503                    break;
504                }
505                b';' => {
506                    if self.comment_depth == 0 {
507                        self.in_date = true;
508                    }
509                    self.next_token = Some(Token::Semicolon.into());
510                    break;
511                }
512                b'/' => {
513                    self.next_token = Some(Token::Slash.into());
514                    break;
515                }
516                b'"' => {
517                    self.in_quote = !self.in_quote;
518                    self.next_token = Some(Token::Quote.into());
519                    break;
520                }
521                b',' => {
522                    self.next_token = Some(Token::Comma.into());
523                    break;
524                }
525                b' ' | b'\t' | b'\r' => {
526                    if n_total > 0 {
527                        break;
528                    } else {
529                        start_pos += 1;
530                        end_pos = start_pos;
531                        continue;
532                    }
533                }
534                0x7f..=u8::MAX => {
535                    n_utf += 1;
536                }
537                b'_' => {
538                    n_underscore += 1;
539                    n_other += 1;
540                }
541                _ => {
542                    n_other += 1;
543                }
544            }
545
546            n_total += 1;
547            end_pos = self.stream.offset();
548        }
549
550        if n_total == 0 {
551            return self.next_token.take();
552        }
553
554        let text = std::str::from_utf8(self.stream.bytes(start_pos..end_pos)).unwrap_or_default();
555
556        let token = match (
557            n_alpha, n_digit, n_hex, n_dot, n_at, n_other, n_colon, n_plus, n_minus, n_utf, hash,
558        ) {
559            (0, 4..=12, 0, 3, 0, 0, 0, 0, 0, 0, _) => {
560                // IPv4 address
561                text.parse::<Ipv4Addr>()
562                    .map(|ip| Token::IpAddr(IpAddr::V4(ip)))
563                    .unwrap_or(Token::Text)
564            }
565            (0, _, 1..=32, 0, 0, 0, 2.., 0, 0, 0, _)
566            | (0, 1..=32, _, 0, 0, 0, 2.., 0, 0, 0, _)
567            | (0, 4..=12, 4, 3, 0, 0, 3, 0, 0, 0, _) => {
568                // IPv6 address
569                text.parse::<Ipv6Addr>()
570                    .map(|ip| Token::IpAddr(IpAddr::V6(ip)))
571                    .unwrap_or(Token::Text)
572            }
573            (0, 1.., 0, 0, 0, 0, 0, 0, 0, 0, _)
574            | (0, 1.., 0, 0, 0, 0, 0, 0, 1, 0, _)
575            | (0, 1.., 0, 0, 0, 0, 0, 1, 0, 0, _) => {
576                // Integer
577                text.parse::<i64>()
578                    .map(Token::Integer)
579                    .unwrap_or(Token::Text)
580            }
581            (1.., _, _, _, 1, _, _, _, _, _, _) | (_, _, 1.., _, 1, _, _, _, _, _, _) => {
582                // E-mail address
583                Token::Email
584            }
585            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x727061) => Token::Month(Month::Apr),
586            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7362) => Token::Protocol(Protocol::SMTP),
587            (1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7962) => Token::By,
588            (0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0x636564) => Token::Month(Month::Dec),
589            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6f6c6865) => Token::Greeting(Greeting::Ehlo),
590            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7365) => Token::Protocol(Protocol::ESMTP),
591            (4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d7365) => Token::Protocol(Protocol::ESMTPA),
592            (5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d7365) => Token::Protocol(Protocol::ESMTPS),
593            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x726f66) => Token::For,
594            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6d6f7266) => Token::From,
595            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6f6c6568) => Token::Greeting(Greeting::Helo),
596            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70747468) => Token::Protocol(Protocol::HTTP),
597            (7, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x7473657270747468) => Token::Protocol(Protocol::HTTP),
598            (1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6469) => Token::Id,
599            (3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70616d69) => Token::Protocol(Protocol::IMAP),
600            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6e616a) => Token::Month(Month::Jan),
601            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6c756a) => Token::Month(Month::Jul),
602            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6e756a) => Token::Month(Month::Jun),
603            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x6f6c686c) => Token::Greeting(Greeting::Lhlo),
604            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d6c) => Token::Protocol(Protocol::LMTP),
605            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6170746d6c) => Token::Protocol(Protocol::LMTPA),
606            (3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x6c61636f6c) => Token::Protocol(Protocol::Local),
607            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d736c) => Token::Protocol(Protocol::LMTP),
608            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x72616d) => Token::Month(Month::Mar),
609            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x79616d) => Token::Month(Month::May),
610            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x766f6e) => Token::Month(Month::Nov),
611            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x33706f70) => Token::Protocol(Protocol::POP3),
612            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x706573) => Token::Month(Month::Sep),
613            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x70746d73) => Token::Protocol(Protocol::SMTP),
614            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6470746d73) => Token::Protocol(Protocol::SMTP),
615            (6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x63767370746d73) => Token::Protocol(Protocol::SMTP),
616            (4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x74656b636f73) => Token::Protocol(Protocol::Local),
617            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x6e69647473) => Token::Protocol(Protocol::Local),
618            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x616976) => Token::Via,
619            (0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0x626566) => Token::Month(Month::Feb),
620            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x677561) => Token::Month(Month::Aug),
621            (2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x74636f) => Token::Month(Month::Oct),
622            (4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x68746977) => Token::With,
623            (4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7361) => Token::Protocol(Protocol::ESMTPA),
624            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7570747468) => Token::Protocol(Protocol::HTTP),
625            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7370747468) => Token::Protocol(Protocol::HTTPS),
626            (3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x746e656469) => Token::Ident,
627            (5, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d7365) => Token::Protocol(Protocol::ESMTPSA),
628            (5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7370746d6c) => Token::Protocol(Protocol::LMTPS),
629            (5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0x617370746d6c) => Token::Protocol(Protocol::LMTPSA),
630            (3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x736d6d) => Token::Protocol(Protocol::MMS),
631            (6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d7338667475) => {
632                Token::Protocol(Protocol::UTF8SMTP)
633            }
634            (6, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d7338667475) => {
635                Token::Protocol(Protocol::UTF8SMTPA)
636            }
637            (7, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d7338667475) => {
638                Token::Protocol(Protocol::UTF8SMTPS)
639            }
640            (7, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d7338667475) => {
641                Token::Protocol(Protocol::UTF8SMTPSA)
642            }
643            (6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x70746d6c38667475) => {
644                Token::Protocol(Protocol::UTF8LMTP)
645            }
646            (6, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x6170746d6c38667475) => {
647                Token::Protocol(Protocol::UTF8LMTPA)
648            }
649            (7, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0x7370746d6c38667475) => {
650                Token::Protocol(Protocol::UTF8LMTPS)
651            }
652            (7, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0x617370746d6c38667475) => {
653                Token::Protocol(Protocol::UTF8LMTPSA)
654            }
655            (7, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x70746d73656c61636f6c) => {
656                Token::Protocol(Protocol::ESMTP)
657            }
658            (8, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x7370746d73656c61636f6c) => {
659                Token::Protocol(Protocol::ESMTPS)
660            }
661            (7, 0, 3, 0, 0, 0, 0, 0, _, 0, 0x70746d73626c61636f6c) => {
662                Token::Protocol(Protocol::SMTP)
663            }
664            (7, 0, 1, 0, 0, 0, 0, 0, _, 0, 0x736c7470746d7365) => Token::Protocol(Protocol::ESMTPS),
665            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3031736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
666            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3131736c74) => Token::TlsVersion(TlsVersion::TLSv1_1),
667            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3231736c74) => Token::TlsVersion(TlsVersion::TLSv1_2),
668            (3, 2, 0, _, 0, _, 0, 0, _, 0, 0x3331736c74) => Token::TlsVersion(TlsVersion::TLSv1_3),
669            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x303176736c74) => {
670                Token::TlsVersion(TlsVersion::TLSv1_0)
671            }
672            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x313176736c74) => {
673                Token::TlsVersion(TlsVersion::TLSv1_1)
674            }
675            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x323176736c74) => {
676                Token::TlsVersion(TlsVersion::TLSv1_2)
677            }
678            (4, 2, 0, _, 0, _, 0, 0, 0, 0, 0x333176736c74) => {
679                Token::TlsVersion(TlsVersion::TLSv1_3)
680            }
681            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x326c7373) => Token::TlsVersion(TlsVersion::SSLv2),
682            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x336c7373) => Token::TlsVersion(TlsVersion::SSLv3),
683            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x32766c7373) => Token::TlsVersion(TlsVersion::SSLv2),
684            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x33766c7373) => Token::TlsVersion(TlsVersion::SSLv3),
685            (3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x31736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
686            (4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0x3176736c74) => Token::TlsVersion(TlsVersion::TLSv1_0),
687            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x303176736c7464) => {
688                Token::TlsVersion(TlsVersion::DTLSv1_0)
689            }
690            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x323176736c7464) => {
691                Token::TlsVersion(TlsVersion::DTLSv1_2)
692            }
693            (4, 2, 1, _, 0, _, 0, 0, 0, 0, 0x333176736c7464) => {
694                Token::TlsVersion(TlsVersion::DTLSv1_3)
695            }
696            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3031736c7464) => {
697                Token::TlsVersion(TlsVersion::DTLSv1_0)
698            }
699            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3231736c7464) => {
700                Token::TlsVersion(TlsVersion::DTLSv1_2)
701            }
702            (3, 2, 1, _, 0, _, 0, 0, 0, 0, 0x3331736c7464) => {
703                Token::TlsVersion(TlsVersion::DTLSv1_3)
704            }
705            (1.., _, _, 1.., 0, 0, 0, 0, _, _, _) | (_, _, 1.., 1.., 0, 0, 0, 0, _, _, _) => {
706                // Domain name
707                Token::Domain
708            }
709            _ => {
710                // Try ciphersuite
711                if n_alpha + n_hex == n_uppercase
712                    && n_total > 6
713                    && (n_underscore > 0 || n_minus > 0)
714                    && n_digit > 0
715                    && n_dot == 0
716                    && n_at == 0
717                    && n_plus == 0
718                    && (n_other == 0 || n_other == n_underscore)
719                    && n_colon == 0
720                    && n_utf == 0
721                    && [
722                        0x617372, 0x646365, 0x646365, 0x656864, 0x6b7370, 0x707273, 0x736561,
723                        0x736564, 0x736c74,
724                    ]
725                    .contains(&(hash & 0xffffff))
726                {
727                    Token::Cipher
728                } else {
729                    Token::Text
730                }
731            }
732        };
733
734        TokenData {
735            text,
736            token,
737            comment_depth,
738            bracket_depth,
739        }
740        .into()
741    }
742}
743
744impl<'x, 'y> Tokenizer<'x, 'y> {
745    fn new(stream: &'y mut MessageStream<'x>) -> Self {
746        Self {
747            stream,
748            next_token: None,
749            eof: false,
750            in_quote: false,
751            bracket_depth: 0,
752            comment_depth: 0,
753            in_date: false,
754        }
755    }
756}
757
758impl From<Token> for TokenData<'_> {
759    fn from(token: Token) -> Self {
760        Self {
761            token,
762            text: "",
763            comment_depth: 0,
764            bracket_depth: 0,
765        }
766    }
767}
768
769impl Month {
770    fn to_number(self) -> i64 {
771        match self {
772            Month::Jan => 1,
773            Month::Feb => 2,
774            Month::Mar => 3,
775            Month::Apr => 4,
776            Month::May => 5,
777            Month::Jun => 6,
778            Month::Jul => 7,
779            Month::Aug => 8,
780            Month::Sep => 9,
781            Month::Oct => 10,
782            Month::Nov => 11,
783            Month::Dec => 12,
784        }
785    }
786}
787
788impl Token {
789    fn is_separator(&self) -> bool {
790        matches!(
791            self,
792            Token::BracketOpen
793                | Token::BracketClose
794                | Token::AngleOpen
795                | Token::AngleClose
796                | Token::ParenthesisOpen
797                | Token::ParenthesisClose
798                | Token::Semicolon
799                | Token::Colon
800                | Token::Equal
801                | Token::Slash
802                | Token::Quote
803                | Token::Comma
804        )
805    }
806}
807
808#[cfg(test)]
809mod tests {
810
811    use crate::{
812        MessageParser,
813        parsers::{MessageStream, fields::load_tests},
814    };
815
816    #[test]
817    fn parse_received() {
818        for test in load_tests("received.json") {
819            assert_eq!(
820                MessageStream::new(test.header.as_bytes())
821                    .parse_received()
822                    .unwrap_received(),
823                test.expected,
824                "failed for {:?}",
825                test.header
826            );
827        }
828    }
829
830    #[test]
831    fn parse_received_truncated_fold() {
832        for message in [
833            &b"Received:\n\t"[..],
834            b"Received:\r\n ",
835            b"Received: x\r\n ",
836            b"Received: x\r\n\t",
837            b"Received: x\n ",
838            b"Received: x\n\t",
839            b"Received: x\r\n  ",
840            b"Received: x\r\n \t",
841            b"Received: x\r\n \t ",
842            b"Received: x\r\n \r",
843            b"Received: x\r\n \r\n",
844            b"Received: x\r\n \r\nbody",
845            b"Received: x\r\n y\r\n ",
846            b"Received: x;\r\n ",
847            b"To: a@b\r\nReceived: x\r\n ",
848            b"Received: from x (y)\r\n\t",
849            b"Received: from x by y;\r\n\t",
850            b"Received: ",
851            b"Received:",
852            b"Received: x",
853        ] {
854            MessageParser::default().parse(message);
855            MessageParser::default().parse_headers(message);
856        }
857    }
858}