eml_codec/imf/
trace.rs

1use chrono::{DateTime, FixedOffset};
2use nom::{
3    branch::alt,
4    bytes::complete::{is_a, tag},
5    combinator::{map, not, opt},
6    multi::many0,
7    sequence::{terminated, tuple},
8    IResult,
9};
10
11use crate::imf::{datetime, mailbox};
12use crate::text::{ascii, misc_token, whitespace};
13
14#[derive(Debug, PartialEq)]
15pub enum ReceivedLogToken<'a> {
16    Addr(mailbox::AddrSpec<'a>),
17    Domain(mailbox::Domain<'a>),
18    Word(misc_token::Word<'a>),
19}
20
21#[derive(Debug, PartialEq)]
22pub struct ReceivedLog<'a> {
23    pub log: Vec<ReceivedLogToken<'a>>,
24    pub date: Option<DateTime<FixedOffset>>,
25}
26
27/*
28impl<'a> TryFrom<&'a lazy::ReceivedLog<'a>> for ReceivedLog<'a> {
29    type Error = IMFError<'a>;
30
31    fn try_from(input: &'a lazy::ReceivedLog<'a>) -> Result<Self, Self::Error> {
32        received_body(input.0)
33            .map_err(|e| IMFError::ReceivedLog(e))
34            .map(|(_, v)| ReceivedLog(v))
35    }
36}*/
37
38pub fn received_log(input: &[u8]) -> IResult<&[u8], ReceivedLog> {
39    map(
40        tuple((many0(received_tokens), tag(";"), datetime::section)),
41        |(tokens, _, dt)| ReceivedLog {
42            log: tokens,
43            date: dt,
44        },
45    )(input)
46}
47
48pub fn return_path(input: &[u8]) -> IResult<&[u8], Option<mailbox::AddrSpec>> {
49    alt((map(mailbox::angle_addr, Some), empty_path))(input)
50}
51
52fn empty_path(input: &[u8]) -> IResult<&[u8], Option<mailbox::AddrSpec>> {
53    let (input, _) = tuple((
54        opt(whitespace::cfws),
55        tag(&[ascii::LT]),
56        opt(whitespace::cfws),
57        tag(&[ascii::GT]),
58        opt(whitespace::cfws),
59    ))(input)?;
60    Ok((input, None))
61}
62
63fn received_tokens(input: &[u8]) -> IResult<&[u8], ReceivedLogToken> {
64    alt((
65        terminated(
66            map(misc_token::word, ReceivedLogToken::Word),
67            not(is_a([ascii::PERIOD, ascii::AT])),
68        ),
69        map(mailbox::angle_addr, ReceivedLogToken::Addr),
70        map(mailbox::addr_spec, ReceivedLogToken::Addr),
71        map(mailbox::obs_domain, ReceivedLogToken::Domain),
72    ))(input)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use crate::imf::trace::misc_token::Word;
79    use chrono::TimeZone;
80
81    #[test]
82    fn test_received_body() {
83        let hdrs = r#"from smtp.example.com ([10.83.2.2])
84    by server with LMTP
85    id xxxxxxxxx
86    (envelope-from <gitlab@example.com>)
87    for <me@example.com>; Tue, 13 Jun 2023 19:01:08 +0000"#
88            .as_bytes();
89
90        assert_eq!(
91            received_log(hdrs),
92            Ok((
93                &b""[..],
94                ReceivedLog {
95                    date: Some(
96                        FixedOffset::east_opt(0)
97                            .unwrap()
98                            .with_ymd_and_hms(2023, 06, 13, 19, 1, 8)
99                            .unwrap()
100                    ),
101                    log: vec![
102                        ReceivedLogToken::Word(Word::Atom(&b"from"[..])),
103                        ReceivedLogToken::Domain(mailbox::Domain::Atoms(vec![
104                            &b"smtp"[..],
105                            &b"example"[..],
106                            &b"com"[..]
107                        ])),
108                        ReceivedLogToken::Word(Word::Atom(&b"by"[..])),
109                        ReceivedLogToken::Word(Word::Atom(&b"server"[..])),
110                        ReceivedLogToken::Word(Word::Atom(&b"with"[..])),
111                        ReceivedLogToken::Word(Word::Atom(&b"LMTP"[..])),
112                        ReceivedLogToken::Word(Word::Atom(&b"id"[..])),
113                        ReceivedLogToken::Word(Word::Atom(&b"xxxxxxxxx"[..])),
114                        ReceivedLogToken::Word(Word::Atom(&b"for"[..])),
115                        ReceivedLogToken::Addr(mailbox::AddrSpec {
116                            local_part: mailbox::LocalPart(vec![mailbox::LocalPartToken::Word(
117                                Word::Atom(&b"me"[..])
118                            )]),
119                            domain: mailbox::Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
120                        })
121                    ],
122                }
123            ))
124        );
125    }
126}