Skip to main content

mail_smtp/
request.rs

1use std::mem;
2
3use new_tokio_smtp::send_mail::{
4    self as smtp,
5    MailAddress,
6    EnvelopData
7};
8
9use mail_internals::{
10    MailType,
11    encoder::{EncodingBuffer, EncodableInHeader},
12    error::EncodingError
13};
14use headers::{
15    headers::{Sender, _From, _To},
16    header_components::Mailbox,
17    error::{BuildInValidationError}
18};
19use mail::{
20    Mail,
21    error::{MailError, OtherValidationError}
22};
23
24use ::error::{ OtherValidationError as AnotherOtherValidationError };
25
26/// This type contains a mail and potentially some envelop data.
27///
28/// It is needed as in some edge cases the smtp envelop data (i.e.
29/// smtp from and smtp recipient) can not be correctly derived
30/// from the mail.
31///
32/// The default usage is to directly turn a `Mail` into a `MailRequest`
33/// by either using  `MailRequest::new`, `MailRequest::from` or `Mail::into`.
34///
35#[derive(Clone, Debug)]
36pub struct MailRequest {
37    mail: Mail,
38    envelop_data: Option<EnvelopData>
39}
40
41impl From<Mail> for MailRequest {
42    fn from(mail: Mail) -> Self {
43        MailRequest::new(mail)
44    }
45}
46
47
48
49impl MailRequest {
50
51    /// creates a new `MailRequest` from a `Mail` instance
52    pub fn new(mail: Mail) -> Self {
53        MailRequest { mail, envelop_data: None }
54    }
55
56    /// create a new `MailRequest` and use custom smtp `EnvelopData`
57    ///
58    /// Note that envelop data comes from `new-tokio-smtp::send_mail` and
59    /// is not re-exported so if you happen to run into one of the view
60    /// cases where you need to set it manually just import it from
61    /// `new-tokio-smtp`.
62    pub fn new_with_envelop(mail: Mail, envelop: EnvelopData) -> Self {
63        MailRequest { mail, envelop_data: Some(envelop) }
64    }
65
66    /// replace the smtp `EnvelopData`
67    pub fn override_envelop(&mut self, envelop: EnvelopData) -> Option<EnvelopData> {
68        mem::replace(&mut self.envelop_data, Some(envelop))
69    }
70
71    pub fn _into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
72        let envelop =
73            if let Some(envelop) = self.envelop_data { envelop }
74            else { derive_envelop_data_from_mail(&self.mail)? };
75
76        Ok((self.mail, envelop))
77    }
78
79    #[cfg(not(feature="extended-api"))]
80    #[inline(always)]
81    pub(crate) fn into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
82        self._into_mail_with_envelop()
83    }
84
85    /// Turns this type into the contained mail an associated envelop data.
86    ///
87    /// If envelop data was explicitly set it is returned.
88    /// If no envelop data was explicitly given it is derived from the
89    /// Mail header fields using `derive_envelop_data_from_mail`.
90    #[cfg(feature="extended-api")]
91    #[inline(always)]
92    pub fn into_mail_with_envelop(self) -> Result<(Mail, EnvelopData), MailError> {
93        self._into_mail_with_envelop()
94    }
95}
96
97fn mailaddress_from_mailbox(mailbox: &Mailbox) -> Result<MailAddress, EncodingError> {
98    let email = &mailbox.email;
99    let needs_smtputf8 = email.check_if_internationalized();
100    let mt = if needs_smtputf8 { MailType::Internationalized } else { MailType::Ascii };
101    let mut buffer = EncodingBuffer::new(mt);
102     {
103        let mut writer = buffer.writer();
104        email.encode(&mut writer)?;
105        writer.commit_partial_header();
106    }
107    let raw: Vec<u8> = buffer.into();
108    let address = String::from_utf8(raw).expect("[BUG] encoding Email produced non utf8 data");
109    Ok(MailAddress::new_unchecked(address, needs_smtputf8))
110}
111
112/// Generates envelop data based on the given Mail.
113///
114/// If a sender header is given smtp will use this
115/// as smtp from else the single mailbox in from
116/// is used as smtp from.
117///
118/// All `To`'s are used as smtp recipients.
119///
120/// **`Cc`/`Bcc` is currently no supported/has no
121/// special handling**
122///
123/// # Error
124///
125/// An error is returned if there is:
126///
127/// - No From header
128/// - No To header
129/// - A From header with multiple addresses but no Sender header
130///
131pub fn derive_envelop_data_from_mail(mail: &Mail)
132    -> Result<smtp::EnvelopData, MailError>
133{
134    let headers = mail.headers();
135    let smtp_from =
136        if let Some(sender) = headers.get_single(Sender) {
137            let sender = sender?;
138            //TODO double check with from field
139            mailaddress_from_mailbox(sender)?
140        } else {
141            let from = headers.get_single(_From)
142                .ok_or(OtherValidationError::NoFrom)??;
143
144            if from.len() > 1 {
145                return Err(BuildInValidationError::MultiMailboxFromWithoutSender.into());
146            }
147
148            mailaddress_from_mailbox(from.first())?
149        };
150
151    let smtp_to =
152        if let Some(to) = headers.get_single(_To) {
153            let to = to?;
154            to.try_mapped_ref(mailaddress_from_mailbox)?
155        } else {
156            return Err(AnotherOtherValidationError::NoTo.into());
157        };
158
159    //TODO Cc, Bcc
160
161    Ok(EnvelopData {
162        from: Some(smtp_from),
163        to: smtp_to
164    })
165}
166
167#[cfg(test)]
168mod test {
169
170    mod derive_envelop_data_from_mail {
171        use super::super::derive_envelop_data_from_mail;
172        use mail::{
173            Mail,
174            Resource,
175            test_utils::CTX
176        };
177        use headers::{
178            headers::{_From, _To, Sender},
179        };
180
181
182        fn mock_resource() -> Resource {
183            Resource::plain_text("abcd↓efg", CTX.unwrap())
184        }
185
186        #[test]
187        fn use_sender_if_given() {
188            let mut mail = Mail::new_singlepart_mail(mock_resource());
189
190            mail.insert_headers(headers! {
191                Sender: "strange@caffe.test",
192                _From: ["ape@caffe.test", "epa@caffe.test"],
193                _To: ["das@ding.test"]
194            }.unwrap());
195
196            let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
197
198            assert_eq!(
199                envelop_data.from.as_ref().unwrap().as_str(),
200                "strange@caffe.test"
201            );
202        }
203
204        #[test]
205        fn use_from_if_no_sender_given() {
206            let mut mail = Mail::new_singlepart_mail(mock_resource());
207            mail.insert_headers(headers! {
208                _From: ["ape@caffe.test"],
209                _To: ["das@ding.test"]
210            }.unwrap());
211
212            let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
213
214            assert_eq!(
215                envelop_data.from.as_ref().unwrap().as_str(),
216                "ape@caffe.test"
217            );
218        }
219
220        #[test]
221        fn fail_if_no_sender_but_multi_mailbox_from() {
222            let mut mail = Mail::new_singlepart_mail(mock_resource());
223            mail.insert_headers(headers! {
224                _From: ["ape@caffe.test", "a@b.test"],
225                _To: ["das@ding.test"]
226            }.unwrap());
227
228            let envelop_data = derive_envelop_data_from_mail(&mail);
229
230            //assert is_err
231            envelop_data.unwrap_err();
232        }
233
234        #[test]
235        fn use_to() {
236            let mut mail = Mail::new_singlepart_mail(mock_resource());
237            mail.insert_headers(headers! {
238                _From: ["ape@caffe.test"],
239                _To: ["das@ding.test"]
240            }.unwrap());
241
242            let envelop_data = derive_envelop_data_from_mail(&mail).unwrap();
243
244            assert_eq!(
245                envelop_data.to.first().as_str(),
246                "das@ding.test"
247            );
248        }
249    }
250
251    mod mailaddress_from_mailbox {
252        use headers::{
253            HeaderTryFrom,
254            header_components::{Mailbox, Email}
255        };
256        use super::super::mailaddress_from_mailbox;
257
258        #[test]
259        #[cfg_attr(not(feature="test-with-traceing"), ignore)]
260        fn does_not_panic_with_tracing_enabled() {
261            let mb = Mailbox::try_from("hy@b").unwrap();
262            mailaddress_from_mailbox(&mb).unwrap();
263        }
264
265        #[test]
266        fn correctly_converts_mailbox() {
267            let mb = Mailbox::from(Email::new("tast@tost.test").unwrap());
268            let address = mailaddress_from_mailbox(&mb).unwrap();
269            assert_eq!(address.as_str(), "tast@tost.test");
270            assert_eq!(address.needs_smtputf8(), false);
271        }
272
273        #[test]
274        fn tracks_if_smtputf8_is_needed() {
275            let mb = Mailbox::from(Email::new("tüst@tost.test").unwrap());
276            let address = mailaddress_from_mailbox(&mb).unwrap();
277            assert_eq!(address.as_str(), "tüst@tost.test");
278            assert_eq!(address.needs_smtputf8(), true);
279        }
280
281        #[test]
282        fn puny_encodes_domain_if_smtputf8_is_not_needed() {
283            let mb = Mailbox::from(Email::new("tast@tüst.test").unwrap());
284            let address = mailaddress_from_mailbox(&mb).unwrap();
285            assert_eq!(address.as_str(), "tast@xn--tst-hoa.test");
286            assert_eq!(address.needs_smtputf8(), false);
287        }
288
289        #[test]
290        fn does_not_puny_encodes_domain_if_smtputf8_is_needed() {
291            let mb = Mailbox::from(Email::new("töst@tüst.test").unwrap());
292            let address = mailaddress_from_mailbox(&mb).unwrap();
293            assert_eq!(address.as_str(), "töst@tüst.test");
294            assert_eq!(address.needs_smtputf8(), true);
295        }
296    }
297}