Skip to main content

eml_codec/imf/
trace.rs

1#[cfg(feature = "arbitrary")]
2use arbitrary::Arbitrary;
3use bounded_static::ToStatic;
4use nom::{
5    branch::alt,
6    bytes::complete::tag,
7    combinator::{consumed, map, opt},
8    sequence::tuple,
9    IResult,
10};
11#[cfg(feature = "tracing")]
12use tracing::warn;
13
14#[cfg(feature = "arbitrary")]
15use crate::fuzz_eq::FuzzEq;
16use crate::i18n::ContainsUtf8;
17use crate::imf::mailbox;
18use crate::print::{Formatter, Print, ToStringFromPrint};
19use crate::text::{ascii, whitespace};
20#[cfg(feature = "tracing-recover")]
21use crate::utils::bytes_to_trace_string;
22use eml_codec_derives::instrument_input;
23
24#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic, ToStringFromPrint)]
25#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
26pub struct ReturnPath<'a>(pub Option<mailbox::AddrSpec<'a>>);
27
28impl<'a> Print for ReturnPath<'a> {
29    fn print(&self, fmt: &mut impl Formatter) {
30        match &self.0 {
31            Some(a) => {
32                fmt.write_bytes(b"<");
33                a.print(fmt);
34                fmt.write_bytes(b">");
35            }
36            None => fmt.write_bytes(b"<>"),
37        }
38    }
39}
40
41#[instrument_input("tracing")]
42pub fn return_path(input: &[u8]) -> IResult<&[u8], ReturnPath<'_>> {
43    alt((
44        map(mailbox::angle_addr, |a| ReturnPath(Some(a))),
45        map(consumed(mailbox::addr_spec), |(_i, a)| {
46            // This is not allowed by the RFC but happens in real-world emails
47            #[cfg(feature = "tracing-recover")]
48            warn!(input = %bytes_to_trace_string(_i), "bare addr-spec in return-path");
49            ReturnPath(Some(a))
50        }),
51        map(consumed(mailbox::mailbox), |(_i, m)| {
52            // This is not allowed by the RFC but happens in some real-world emails
53            #[cfg(feature = "tracing-recover")]
54            warn!(input = %bytes_to_trace_string(_i), "mailbox in return-path");
55            ReturnPath(Some(m.addrspec))
56        }),
57        empty_path,
58    ))(input)
59}
60
61#[instrument_input("tracing")]
62fn empty_path(input: &[u8]) -> IResult<&[u8], ReturnPath<'_>> {
63    let (input, _) = tuple((
64        opt(whitespace::cfws),
65        tag(&[ascii::LT]),
66        opt(whitespace::cfws),
67        tag(&[ascii::GT]),
68        opt(whitespace::cfws),
69    ))(input)?;
70    Ok((input, ReturnPath(None)))
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use crate::imf::mailbox::*;
77    use crate::text::misc_token::Word;
78    use crate::text::words::Atom;
79
80    #[test]
81    fn test_return_path() {
82        assert_eq!(
83            return_path(b" <foo@example.com>"),
84            Ok((
85                &b""[..],
86                ReturnPath(Some(AddrSpec {
87                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
88                        "foo"[..].into()
89                    )))]),
90                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
91                }))
92            ))
93        );
94    }
95
96    #[test]
97    fn test_return_path_bare() {
98        assert_eq!(
99            return_path(b" foo@example.com "),
100            Ok((
101                &b""[..],
102                ReturnPath(Some(AddrSpec {
103                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
104                        "foo"[..].into()
105                    )))]),
106                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
107                }))
108            ))
109        );
110    }
111
112    #[test]
113    fn test_return_path_mailbox() {
114        assert_eq!(
115            return_path(b"abcdef <foo@example.com> "),
116            Ok((
117                &b""[..],
118                ReturnPath(Some(AddrSpec {
119                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
120                        "foo"[..].into()
121                    )))]),
122                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
123                }))
124            ))
125        );
126    }
127}