mail_send/smtp/
message.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use std::{
8    borrow::Cow,
9    fmt::{Debug, Display},
10};
11
12#[cfg(feature = "builder")]
13use mail_builder::{
14    MessageBuilder,
15    headers::{HeaderType, address},
16};
17#[cfg(feature = "parser")]
18use mail_parser::{HeaderName, HeaderValue};
19use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
20
21use crate::SmtpClient;
22
23#[derive(Debug, Default, Clone)]
24pub struct Message<'x> {
25    pub mail_from: Address<'x>,
26    pub rcpt_to: Vec<Address<'x>>,
27    pub body: Cow<'x, [u8]>,
28}
29
30#[derive(Debug, Default, Clone)]
31pub struct Address<'x> {
32    pub email: Cow<'x, str>,
33    pub parameters: Parameters<'x>,
34}
35
36#[derive(Debug, Default, Clone)]
37pub struct Parameters<'x> {
38    params: Vec<Parameter<'x>>,
39}
40
41#[derive(Debug, Default, Clone)]
42pub struct Parameter<'x> {
43    key: Cow<'x, str>,
44    value: Option<Cow<'x, str>>,
45}
46
47impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
48    /// Sends a message to the server.
49    pub async fn send<'x>(&mut self, message: impl IntoMessage<'x>) -> crate::Result<()> {
50        // Send mail-from
51        let message = message.into_message()?;
52        self.mail_from(
53            message.mail_from.email.as_ref(),
54            &message.mail_from.parameters,
55        )
56        .await?;
57
58        // Send rcpt-to
59        for rcpt in &message.rcpt_to {
60            self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?;
61        }
62
63        // Send message
64        self.data(message.body.as_ref()).await
65    }
66
67    /// Sends a message to the server.
68    #[cfg(feature = "dkim")]
69    pub async fn send_signed<'x, V: mail_auth::common::crypto::SigningKey>(
70        &mut self,
71        message: impl IntoMessage<'x>,
72        signer: &mail_auth::dkim::DkimSigner<V, mail_auth::dkim::Done>,
73    ) -> crate::Result<()> {
74        // Send mail-from
75
76        use mail_auth::common::headers::HeaderWriter;
77        let message = message.into_message()?;
78        self.mail_from(
79            message.mail_from.email.as_ref(),
80            &message.mail_from.parameters,
81        )
82        .await?;
83
84        // Send rcpt-to
85        for rcpt in &message.rcpt_to {
86            self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?;
87        }
88
89        // Sign message
90        let signature = signer
91            .sign(message.body.as_ref())
92            .map_err(|_| crate::Error::MissingCredentials)?;
93        let mut signed_message = Vec::with_capacity(message.body.len() + 64);
94        signature.write_header(&mut signed_message);
95        signed_message.extend_from_slice(message.body.as_ref());
96
97        // Send message
98        self.data(&signed_message).await
99    }
100
101    pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> {
102        // Transparency procedure
103        let mut is_cr_or_lf = false;
104
105        // As per RFC 5322bis, section 2.3:
106        // CR and LF MUST only occur together as CRLF; they MUST NOT appear
107        // independently in the body.
108        // For this reason, we apply the transparency procedure when there is
109        // a CR or LF followed by a dot.
110
111        let mut last_pos = 0;
112        for (pos, byte) in message.iter().enumerate() {
113            if *byte == b'.' && is_cr_or_lf {
114                if let Some(bytes) = message.get(last_pos..pos) {
115                    self.stream.write_all(bytes).await?;
116                    self.stream.write_all(b".").await?;
117                    last_pos = pos;
118                }
119                is_cr_or_lf = false;
120            } else {
121                is_cr_or_lf = *byte == b'\n' || *byte == b'\r';
122            }
123        }
124        if let Some(bytes) = message.get(last_pos..) {
125            self.stream.write_all(bytes).await?;
126        }
127        self.stream.write_all("\r\n.\r\n".as_bytes()).await?;
128        self.stream.flush().await
129    }
130}
131
132impl<'x> Message<'x> {
133    /// Create a new message
134    pub fn new<T, U, V>(from: T, to: U, body: V) -> Self
135    where
136        T: Into<Address<'x>>,
137        U: IntoIterator<Item = T>,
138        V: Into<Cow<'x, [u8]>>,
139    {
140        Message {
141            mail_from: from.into(),
142            rcpt_to: to.into_iter().map(Into::into).collect(),
143            body: body.into(),
144        }
145    }
146
147    /// Create a new empty message.
148    pub fn empty() -> Self {
149        Message {
150            mail_from: Address::default(),
151            rcpt_to: Vec::new(),
152            body: Default::default(),
153        }
154    }
155
156    /// Set the sender of the message.
157    pub fn from(mut self, address: impl Into<Address<'x>>) -> Self {
158        self.mail_from = address.into();
159        self
160    }
161
162    /// Add a message recipient.
163    pub fn to(mut self, address: impl Into<Address<'x>>) -> Self {
164        self.rcpt_to.push(address.into());
165        self
166    }
167
168    /// Set the message body.
169    pub fn body(mut self, body: impl Into<Cow<'x, [u8]>>) -> Self {
170        self.body = body.into();
171        self
172    }
173}
174
175impl<'x> From<&'x str> for Address<'x> {
176    fn from(email: &'x str) -> Self {
177        Address {
178            email: email.into(),
179            parameters: Parameters::default(),
180        }
181    }
182}
183
184impl From<String> for Address<'_> {
185    fn from(email: String) -> Self {
186        Address {
187            email: email.into(),
188            parameters: Parameters::default(),
189        }
190    }
191}
192
193impl<'x> Address<'x> {
194    pub fn new(email: impl Into<Cow<'x, str>>, parameters: Parameters<'x>) -> Self {
195        Address {
196            email: email.into(),
197            parameters,
198        }
199    }
200}
201
202impl<'x> Parameters<'x> {
203    pub fn new() -> Self {
204        Self { params: Vec::new() }
205    }
206
207    pub fn add(&mut self, param: impl Into<Parameter<'x>>) -> &mut Self {
208        self.params.push(param.into());
209        self
210    }
211}
212
213impl<'x> From<&'x str> for Parameter<'x> {
214    fn from(value: &'x str) -> Self {
215        Parameter {
216            key: value.into(),
217            value: None,
218        }
219    }
220}
221
222impl<'x> From<(&'x str, &'x str)> for Parameter<'x> {
223    fn from(value: (&'x str, &'x str)) -> Self {
224        Parameter {
225            key: value.0.into(),
226            value: Some(value.1.into()),
227        }
228    }
229}
230
231impl From<(String, String)> for Parameter<'_> {
232    fn from(value: (String, String)) -> Self {
233        Parameter {
234            key: value.0.into(),
235            value: Some(value.1.into()),
236        }
237    }
238}
239
240impl From<String> for Parameter<'_> {
241    fn from(value: String) -> Self {
242        Parameter {
243            key: value.into(),
244            value: None,
245        }
246    }
247}
248
249impl Display for Parameters<'_> {
250    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251        if !self.params.is_empty() {
252            for param in &self.params {
253                f.write_str(" ")?;
254                Display::fmt(&param, f)?;
255            }
256        }
257        Ok(())
258    }
259}
260
261impl Display for Parameter<'_> {
262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263        if let Some(value) = &self.value {
264            write!(f, "{}={}", self.key, value)
265        } else {
266            f.write_str(&self.key)
267        }
268    }
269}
270
271pub trait IntoMessage<'x> {
272    fn into_message(self) -> crate::Result<Message<'x>>;
273}
274
275impl<'x> IntoMessage<'x> for Message<'x> {
276    fn into_message(self) -> crate::Result<Message<'x>> {
277        Ok(self)
278    }
279}
280
281#[cfg(feature = "builder")]
282impl<'x> IntoMessage<'x> for MessageBuilder<'_> {
283    fn into_message(self) -> crate::Result<Message<'x>> {
284        let mut mail_from = None;
285        let mut rcpt_to = std::collections::HashSet::new();
286
287        for (key, value) in self.headers.iter() {
288            if key.eq_ignore_ascii_case("from") {
289                if let HeaderType::Address(address::Address::Address(addr)) = value {
290                    let email = addr.email.trim();
291                    if !email.is_empty() {
292                        mail_from = email.to_string().into();
293                    }
294                }
295            } else if (key.eq_ignore_ascii_case("to")
296                || key.eq_ignore_ascii_case("cc")
297                || key.eq_ignore_ascii_case("bcc"))
298                && let HeaderType::Address(addr) = value
299            {
300                match addr {
301                    address::Address::Address(addr) => {
302                        let email = addr.email.trim();
303                        if !email.is_empty() {
304                            rcpt_to.insert(email.to_string());
305                        }
306                    }
307                    address::Address::Group(group) => {
308                        for addr in &group.addresses {
309                            if let address::Address::Address(addr) = addr {
310                                let email = addr.email.trim();
311                                if !email.is_empty() {
312                                    rcpt_to.insert(email.to_string());
313                                }
314                            }
315                        }
316                    }
317                    address::Address::List(list) => {
318                        for addr in list {
319                            if let address::Address::Address(addr) = addr {
320                                let email = addr.email.trim();
321                                if !email.is_empty() {
322                                    rcpt_to.insert(email.to_string());
323                                }
324                            }
325                        }
326                    }
327                }
328            }
329        }
330
331        if rcpt_to.is_empty() {
332            return Err(crate::Error::MissingRcptTo);
333        }
334
335        Ok(Message {
336            mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
337            rcpt_to: rcpt_to
338                .into_iter()
339                .map(|email| Address {
340                    email: email.into(),
341                    parameters: Parameters::default(),
342                })
343                .collect(),
344            body: self.write_to_vec()?.into(),
345        })
346    }
347}
348
349#[cfg(feature = "parser")]
350impl<'x> IntoMessage<'x> for mail_parser::Message<'x> {
351    fn into_message(self) -> crate::Result<Message<'x>> {
352        let mut mail_from = None;
353        let mut rcpt_to = std::collections::HashSet::new();
354
355        let find_address = |addr: &mail_parser::Addr| -> Option<String> {
356            addr.address
357                .as_ref()
358                .filter(|address| !address.trim().is_empty())
359                .map(|address| address.trim().to_string())
360        };
361
362        for header in self.headers() {
363            match &header.name {
364                HeaderName::From => match header.value() {
365                    HeaderValue::Address(mail_parser::Address::List(addrs)) => {
366                        if let Some(email) = addrs.iter().find_map(find_address) {
367                            mail_from = email.to_string().into();
368                        }
369                    }
370                    HeaderValue::Address(mail_parser::Address::Group(groups)) => {
371                        if let Some(grps) = groups.first() {
372                            if let Some(email) = grps.addresses.iter().find_map(find_address) {
373                                mail_from = email.to_string().into();
374                            }
375                        }
376                    }
377                    _ => (),
378                },
379                HeaderName::To | HeaderName::Cc | HeaderName::Bcc => match header.value() {
380                    HeaderValue::Address(mail_parser::Address::List(addrs)) => {
381                        rcpt_to.extend(addrs.iter().filter_map(find_address));
382                    }
383                    HeaderValue::Address(mail_parser::Address::Group(grps)) => {
384                        rcpt_to.extend(
385                            grps.iter()
386                                .flat_map(|grp| grp.addresses.iter())
387                                .filter_map(find_address),
388                        );
389                    }
390                    _ => (),
391                },
392                _ => (),
393            };
394        }
395
396        if rcpt_to.is_empty() {
397            return Err(crate::Error::MissingRcptTo);
398        }
399
400        Ok(Message {
401            mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
402            rcpt_to: rcpt_to
403                .into_iter()
404                .map(|email| Address {
405                    email: email.into(),
406                    parameters: Parameters::default(),
407                })
408                .collect(),
409            body: self.raw_message,
410        })
411    }
412}