Skip to main content

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    /// Sends a message to the server, signing it with DKIM2.
102    #[cfg(feature = "dkim")]
103    pub async fn send_signed_dkim2<'x>(
104        &mut self,
105        message: impl IntoMessage<'x>,
106        signer: &mail_auth::dkim2::Dkim2Signer<mail_auth::dkim2::Done>,
107    ) -> crate::Result<()> {
108        use mail_auth::common::headers::HeaderWriter;
109        use mail_auth::dkim2::Hop;
110
111        // Send mail-from
112        let message = message.into_message()?;
113        self.mail_from(
114            message.mail_from.email.as_ref(),
115            &message.mail_from.parameters,
116        )
117        .await?;
118
119        // Send rcpt-to
120        for rcpt in &message.rcpt_to {
121            self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?;
122        }
123
124        // Sign message, binding the signature to this SMTP hop
125        let signed = signer
126            .sign(
127                message.body.as_ref(),
128                Hop::real(
129                    message.mail_from.email.as_ref(),
130                    message.rcpt_to.iter().map(|rcpt| rcpt.email.as_ref()),
131                ),
132            )
133            .map_err(|_| crate::Error::MissingCredentials)?;
134        let mut signed_message = Vec::with_capacity(message.body.len() + 64);
135        signed.signature.write_header(&mut signed_message);
136        if let Some(instance) = &signed.message_instance {
137            instance.write_header(&mut signed_message);
138        }
139        signed_message.extend_from_slice(message.body.as_ref());
140
141        // Send message
142        self.data(&signed_message).await
143    }
144
145    pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> {
146        // Transparency procedure
147        let mut is_cr_or_lf = false;
148
149        // As per RFC 5322bis, section 2.3:
150        // CR and LF MUST only occur together as CRLF; they MUST NOT appear
151        // independently in the body.
152        // For this reason, we apply the transparency procedure when there is
153        // a CR or LF followed by a dot.
154
155        let mut last_pos = 0;
156        for (pos, byte) in message.iter().enumerate() {
157            if *byte == b'.' && is_cr_or_lf {
158                if let Some(bytes) = message.get(last_pos..pos) {
159                    self.stream.write_all(bytes).await?;
160                    self.stream.write_all(b".").await?;
161                    last_pos = pos;
162                }
163                is_cr_or_lf = false;
164            } else {
165                is_cr_or_lf = *byte == b'\n' || *byte == b'\r';
166            }
167        }
168        if let Some(bytes) = message.get(last_pos..) {
169            self.stream.write_all(bytes).await?;
170        }
171        self.stream.write_all("\r\n.\r\n".as_bytes()).await?;
172        self.stream.flush().await
173    }
174}
175
176impl<'x> Message<'x> {
177    /// Create a new message
178    pub fn new<T, U, V>(from: T, to: U, body: V) -> Self
179    where
180        T: Into<Address<'x>>,
181        U: IntoIterator<Item = T>,
182        V: Into<Cow<'x, [u8]>>,
183    {
184        Message {
185            mail_from: from.into(),
186            rcpt_to: to.into_iter().map(Into::into).collect(),
187            body: body.into(),
188        }
189    }
190
191    /// Create a new empty message.
192    pub fn empty() -> Self {
193        Message {
194            mail_from: Address::default(),
195            rcpt_to: Vec::new(),
196            body: Default::default(),
197        }
198    }
199
200    /// Set the sender of the message.
201    pub fn from(mut self, address: impl Into<Address<'x>>) -> Self {
202        self.mail_from = address.into();
203        self
204    }
205
206    /// Add a message recipient.
207    pub fn to(mut self, address: impl Into<Address<'x>>) -> Self {
208        self.rcpt_to.push(address.into());
209        self
210    }
211
212    /// Set the message body.
213    pub fn body(mut self, body: impl Into<Cow<'x, [u8]>>) -> Self {
214        self.body = body.into();
215        self
216    }
217}
218
219impl<'x> From<&'x str> for Address<'x> {
220    fn from(email: &'x str) -> Self {
221        Address {
222            email: email.into(),
223            parameters: Parameters::default(),
224        }
225    }
226}
227
228impl From<String> for Address<'_> {
229    fn from(email: String) -> Self {
230        Address {
231            email: email.into(),
232            parameters: Parameters::default(),
233        }
234    }
235}
236
237impl<'x> Address<'x> {
238    pub fn new(email: impl Into<Cow<'x, str>>, parameters: Parameters<'x>) -> Self {
239        Address {
240            email: email.into(),
241            parameters,
242        }
243    }
244}
245
246impl<'x> Parameters<'x> {
247    pub fn new() -> Self {
248        Self { params: Vec::new() }
249    }
250
251    pub fn add(&mut self, param: impl Into<Parameter<'x>>) -> &mut Self {
252        self.params.push(param.into());
253        self
254    }
255}
256
257impl<'x> From<&'x str> for Parameter<'x> {
258    fn from(value: &'x str) -> Self {
259        Parameter {
260            key: value.into(),
261            value: None,
262        }
263    }
264}
265
266impl<'x> From<(&'x str, &'x str)> for Parameter<'x> {
267    fn from(value: (&'x str, &'x str)) -> Self {
268        Parameter {
269            key: value.0.into(),
270            value: Some(value.1.into()),
271        }
272    }
273}
274
275impl From<(String, String)> for Parameter<'_> {
276    fn from(value: (String, String)) -> Self {
277        Parameter {
278            key: value.0.into(),
279            value: Some(value.1.into()),
280        }
281    }
282}
283
284impl From<String> for Parameter<'_> {
285    fn from(value: String) -> Self {
286        Parameter {
287            key: value.into(),
288            value: None,
289        }
290    }
291}
292
293impl Display for Parameters<'_> {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        if !self.params.is_empty() {
296            for param in &self.params {
297                f.write_str(" ")?;
298                Display::fmt(&param, f)?;
299            }
300        }
301        Ok(())
302    }
303}
304
305impl Display for Parameter<'_> {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        if let Some(value) = &self.value {
308            write!(f, "{}={}", self.key, value)
309        } else {
310            f.write_str(&self.key)
311        }
312    }
313}
314
315pub trait IntoMessage<'x> {
316    fn into_message(self) -> crate::Result<Message<'x>>;
317}
318
319impl<'x> IntoMessage<'x> for Message<'x> {
320    fn into_message(self) -> crate::Result<Message<'x>> {
321        Ok(self)
322    }
323}
324
325#[cfg(feature = "builder")]
326impl<'x> IntoMessage<'x> for MessageBuilder<'_> {
327    fn into_message(self) -> crate::Result<Message<'x>> {
328        let mut mail_from = None;
329        let mut rcpt_to = std::collections::HashSet::new();
330
331        for (key, value) in self.headers.iter() {
332            if key.eq_ignore_ascii_case("from") {
333                if let HeaderType::Address(address::Address::Address(addr)) = value {
334                    let email = addr.email.trim();
335                    if !email.is_empty() {
336                        mail_from = email.to_string().into();
337                    }
338                }
339            } else if (key.eq_ignore_ascii_case("to")
340                || key.eq_ignore_ascii_case("cc")
341                || key.eq_ignore_ascii_case("bcc"))
342                && let HeaderType::Address(addr) = value
343            {
344                match addr {
345                    address::Address::Address(addr) => {
346                        let email = addr.email.trim();
347                        if !email.is_empty() {
348                            rcpt_to.insert(email.to_string());
349                        }
350                    }
351                    address::Address::Group(group) => {
352                        for addr in &group.addresses {
353                            if let address::Address::Address(addr) = addr {
354                                let email = addr.email.trim();
355                                if !email.is_empty() {
356                                    rcpt_to.insert(email.to_string());
357                                }
358                            }
359                        }
360                    }
361                    address::Address::List(list) => {
362                        for addr in list {
363                            if let address::Address::Address(addr) = addr {
364                                let email = addr.email.trim();
365                                if !email.is_empty() {
366                                    rcpt_to.insert(email.to_string());
367                                }
368                            }
369                        }
370                    }
371                }
372            }
373        }
374
375        if rcpt_to.is_empty() {
376            return Err(crate::Error::MissingRcptTo);
377        }
378
379        Ok(Message {
380            mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
381            rcpt_to: rcpt_to
382                .into_iter()
383                .map(|email| Address {
384                    email: email.into(),
385                    parameters: Parameters::default(),
386                })
387                .collect(),
388            body: self.write_to_vec()?.into(),
389        })
390    }
391}
392
393#[cfg(feature = "parser")]
394impl<'x> IntoMessage<'x> for mail_parser::Message<'x> {
395    fn into_message(self) -> crate::Result<Message<'x>> {
396        let mut mail_from = None;
397        let mut rcpt_to = std::collections::HashSet::new();
398
399        let find_address = |addr: &mail_parser::Addr| -> Option<String> {
400            addr.address
401                .as_ref()
402                .filter(|address| !address.trim().is_empty())
403                .map(|address| address.trim().to_string())
404        };
405
406        for header in self.headers() {
407            match &header.name {
408                HeaderName::From => match header.value() {
409                    HeaderValue::Address(mail_parser::Address::List(addrs)) => {
410                        if let Some(email) = addrs.iter().find_map(find_address) {
411                            mail_from = email.to_string().into();
412                        }
413                    }
414                    HeaderValue::Address(mail_parser::Address::Group(groups)) => {
415                        if let Some(grps) = groups.first() {
416                            if let Some(email) = grps.addresses.iter().find_map(find_address) {
417                                mail_from = email.to_string().into();
418                            }
419                        }
420                    }
421                    _ => (),
422                },
423                HeaderName::To | HeaderName::Cc | HeaderName::Bcc => match header.value() {
424                    HeaderValue::Address(mail_parser::Address::List(addrs)) => {
425                        rcpt_to.extend(addrs.iter().filter_map(find_address));
426                    }
427                    HeaderValue::Address(mail_parser::Address::Group(grps)) => {
428                        rcpt_to.extend(
429                            grps.iter()
430                                .flat_map(|grp| grp.addresses.iter())
431                                .filter_map(find_address),
432                        );
433                    }
434                    _ => (),
435                },
436                _ => (),
437            };
438        }
439
440        if rcpt_to.is_empty() {
441            return Err(crate::Error::MissingRcptTo);
442        }
443
444        Ok(Message {
445            mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
446            rcpt_to: rcpt_to
447                .into_iter()
448                .map(|email| Address {
449                    email: email.into(),
450                    parameters: Parameters::default(),
451                })
452                .collect(),
453            body: self.raw_message,
454        })
455    }
456}