miltr_common/commands/
recipient.rs

1use std::borrow::Cow;
2
3use bytes::{BufMut, BytesMut};
4
5use crate::decoding::Parsable;
6use crate::encoding::Writable;
7use crate::{InvalidData, ProtocolError};
8use miltr_utils::ByteParsing;
9
10/// An smtp recipient
11#[derive(Clone, PartialEq, Debug, Default)]
12pub struct Recipient {
13    recipient: BytesMut,
14    esmtp_args: Option<BytesMut>,
15}
16
17impl From<&[u8]> for Recipient {
18    fn from(value: &[u8]) -> Self {
19        Self {
20            recipient: BytesMut::from_iter(value),
21            esmtp_args: None,
22        }
23    }
24}
25
26impl Recipient {
27    const CODE: u8 = b'R';
28    /// The recipient as received by the milter client
29    #[must_use]
30    pub fn recipient(&self) -> Cow<str> {
31        String::from_utf8_lossy(&self.recipient)
32    }
33
34    /// Optional esmtp arguments regarding the recipients.
35    ///
36    /// Returns an empty `Vec` if no esmtp args where received
37    pub fn esmtp_args(&self) -> Vec<Cow<str>> {
38        let Some(args) = &self.esmtp_args else {
39            return Vec::new();
40        };
41
42        args[..]
43            .split(|&b| b == 0)
44            .map(String::from_utf8_lossy)
45            .collect()
46    }
47}
48
49impl Parsable for Recipient {
50    const CODE: u8 = Self::CODE;
51
52    fn parse(mut buffer: BytesMut) -> Result<Self, ProtocolError> {
53        let Some(recipient) = buffer.delimited(0) else {
54            return Err(InvalidData::new(
55                "Received recipient package without recipient terminated by null byte in it",
56                buffer,
57            )
58            .into());
59        };
60
61        let esmtp_args = {
62            if buffer.is_empty() {
63                None
64            } else {
65                Some(buffer)
66            }
67        };
68
69        Ok(Self {
70            recipient,
71            esmtp_args,
72        })
73    }
74}
75
76impl Writable for Recipient {
77    fn write(&self, buffer: &mut BytesMut) {
78        buffer.extend_from_slice(&self.recipient);
79        buffer.put_u8(0);
80
81        if let Some(b) = &self.esmtp_args {
82            buffer.extend_from_slice(b);
83        }
84    }
85
86    fn len(&self) -> usize {
87        self.recipient.len()
88            + 1
89            + self
90                .esmtp_args
91                .as_ref()
92                .map(BytesMut::len)
93                .unwrap_or_default()
94    }
95
96    fn code(&self) -> u8 {
97        Self::CODE
98    }
99
100    fn is_empty(&self) -> bool {
101        self.recipient.is_empty() && self.esmtp_args.is_some()
102    }
103}
104
105#[cfg(test)]
106mod test {
107    use super::*;
108    use crate::decoding::Parsable;
109    use rstest::rstest;
110
111    #[rstest]
112    #[case(BytesMut::from("recipient1 recipient2\0arg1\0arg2"), Ok( Recipient {recipient: BytesMut::from("recipient1 recipient2"), esmtp_args: Some(BytesMut::from("arg1\0arg2"))}))]
113    #[case(
114        BytesMut::from("recipient1 arg1 arg2"),
115        Err(InvalidData::new(
116            "Received recipient package without recipient terminated by null byte in it",
117            BytesMut::new(),
118        ))
119    )]
120    fn test_recipient(#[case] input: BytesMut, #[case] expected: Result<Recipient, InvalidData>) {
121        let parsed_recp = Recipient::parse(input);
122
123        match parsed_recp {
124            Ok(recp) => {
125                let expected_recp = expected.unwrap();
126                assert_eq!(recp.recipient, expected_recp.recipient);
127                assert_eq!(recp.esmtp_args, expected_recp.esmtp_args);
128
129                //test function mail.esmtp_args()
130                let vec: Vec<Cow<'_, str>> = vec![
131                    String::from_utf8_lossy(b"arg1"),
132                    String::from_utf8_lossy(b"arg2"),
133                ];
134                assert_eq!(recp.esmtp_args(), vec);
135            }
136            Err(ProtocolError::InvalidData(e)) => {
137                assert_eq!(e.msg, expected.unwrap_err().msg);
138            }
139            _ => panic!("Wrong error received"),
140        }
141    }
142
143    #[cfg(feature = "count-allocations")]
144    #[test]
145    fn test_parse_recipient() {
146        use super::Recipient;
147
148        let buffer = BytesMut::from("rcpt\0arg1\0arg2");
149        let info = allocation_counter::measure(|| {
150            let res = Recipient::parse(buffer);
151            allocation_counter::opt_out(|| {
152                println!("{res:?}");
153                assert!(res.is_ok());
154            });
155        });
156        //2 allocation
157        assert!((0..2).contains(&info.count_total));
158    }
159}