eml-codec 0.4.0

Email enCOder DECoder in Rust. Support Internet Message Format and MIME (RFC 822, 5322, 2045, 2046, 2047, 2048, 2049, 6532).
Documentation
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use bounded_static::ToStatic;
use nom::{
    branch::alt,
    bytes::complete::tag,
    combinator::{consumed, map, opt},
    sequence::tuple,
    IResult,
};
#[cfg(feature = "tracing")]
use tracing::warn;

#[cfg(feature = "arbitrary")]
use crate::fuzz_eq::FuzzEq;
use crate::i18n::ContainsUtf8;
use crate::imf::mailbox;
use crate::print::{Formatter, Print, ToStringFromPrint};
use crate::text::{ascii, whitespace};
#[cfg(feature = "tracing-recover")]
use crate::utils::bytes_to_trace_string;
use eml_codec_derives::instrument_input;

#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic, ToStringFromPrint)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
pub struct ReturnPath<'a>(pub Option<mailbox::AddrSpec<'a>>);

impl<'a> Print for ReturnPath<'a> {
    fn print(&self, fmt: &mut impl Formatter) {
        match &self.0 {
            Some(a) => {
                fmt.write_bytes(b"<");
                a.print(fmt);
                fmt.write_bytes(b">");
            }
            None => fmt.write_bytes(b"<>"),
        }
    }
}

#[instrument_input("tracing")]
pub fn return_path(input: &[u8]) -> IResult<&[u8], ReturnPath<'_>> {
    alt((
        map(mailbox::angle_addr, |a| ReturnPath(Some(a))),
        map(consumed(mailbox::addr_spec), |(_i, a)| {
            // This is not allowed by the RFC but happens in real-world emails
            #[cfg(feature = "tracing-recover")]
            warn!(input = %bytes_to_trace_string(_i), "bare addr-spec in return-path");
            ReturnPath(Some(a))
        }),
        map(consumed(mailbox::mailbox), |(_i, m)| {
            // This is not allowed by the RFC but happens in some real-world emails
            #[cfg(feature = "tracing-recover")]
            warn!(input = %bytes_to_trace_string(_i), "mailbox in return-path");
            ReturnPath(Some(m.addrspec))
        }),
        empty_path,
    ))(input)
}

#[instrument_input("tracing")]
fn empty_path(input: &[u8]) -> IResult<&[u8], ReturnPath<'_>> {
    let (input, _) = tuple((
        opt(whitespace::cfws),
        tag(&[ascii::LT]),
        opt(whitespace::cfws),
        tag(&[ascii::GT]),
        opt(whitespace::cfws),
    ))(input)?;
    Ok((input, ReturnPath(None)))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::imf::mailbox::*;
    use crate::text::misc_token::Word;
    use crate::text::words::Atom;

    #[test]
    fn test_return_path() {
        assert_eq!(
            return_path(b" <foo@example.com>"),
            Ok((
                &b""[..],
                ReturnPath(Some(AddrSpec {
                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
                        "foo"[..].into()
                    )))]),
                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
                }))
            ))
        );
    }

    #[test]
    fn test_return_path_bare() {
        assert_eq!(
            return_path(b" foo@example.com "),
            Ok((
                &b""[..],
                ReturnPath(Some(AddrSpec {
                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
                        "foo"[..].into()
                    )))]),
                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
                }))
            ))
        );
    }

    #[test]
    fn test_return_path_mailbox() {
        assert_eq!(
            return_path(b"abcdef <foo@example.com> "),
            Ok((
                &b""[..],
                ReturnPath(Some(AddrSpec {
                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(Atom(
                        "foo"[..].into()
                    )))]),
                    domain: Domain::Atoms(vec![Atom("example"[..].into()), Atom("com"[..].into())]),
                }))
            ))
        );
    }
}