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 #[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 #[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}