mail_core_ng/
mail.rs

1//! Module containing all the parts for creating/encoding Mails.
2//!
3
4
5use std::{
6    ops::Deref,
7    fmt,
8    mem
9};
10
11use soft_ascii_string::SoftAsciiString;
12use futures::{
13    future,
14    Future,
15    Async,
16    Poll
17};
18use media_type::BOUNDARY;
19
20use internals::{
21    MailType,
22    encoder::EncodingBuffer
23};
24use headers::{
25    Header, HeaderKind,
26    HeaderMap,
27    headers::{
28        ContentType, _From,
29        ContentTransferEncoding,
30        Date, MessageId,
31        ContentDisposition,
32        ContentId
33    },
34    header_components::{
35        DateTime,
36        MediaType
37    },
38    error::{
39        HeaderValidationError,
40    }
41};
42
43use ::{
44    utils::SendBoxFuture,
45    mime::create_structured_random_boundary,
46    error::{
47        MailError,
48        OtherValidationError,
49        ResourceLoadingError
50    },
51    resource::*,
52    context::Context
53};
54
55/// A type representing a Mail.
56///
57/// This type is used to represent a mail including headers and body.
58/// It is also used for the bodies of multipart mime mail bodies as
59/// they can be seen as "sub-mails" or "hirachical nested mails", at
60/// last wrt. everything relevant on this type.
61///
62/// A mail can be created using the `Builder` or more specific either
63/// the `SinglepartBuilder` or the `MultipartBuilder` for a multipart
64/// mime mail.
65///
66/// # Example
67///
68/// This will create, encode and print a simple plain text mail.
69///
70/// ```
71/// # extern crate futures;
72/// # extern crate mail_core;
73/// # extern crate mail_internals;
74/// # #[macro_use] extern crate mail_headers as headers;
75/// # use futures::Future;
76/// # use mail_internals::MailType;
77/// use std::str;
78/// // either from `mail::headers` or from `mail_header as headers`
79/// use headers::{
80///     headers::*,
81///     header_components::Domain
82/// };
83/// use mail_core::{
84///     Mail, Resource,
85///     default_impl::simple_context
86/// };
87///
88/// # fn main() {
89/// // Domain will implement `from_str` in the future,
90/// // currently it doesn't have a validator/parser.
91/// let domain = Domain::from_unchecked("example.com".to_owned());
92/// // Normally you create this _once per application_.
93/// let ctx = simple_context::new(domain, "xqi93".parse().unwrap())
94///     .unwrap();
95///
96/// let mut mail = Mail::plain_text("Hy there!", &ctx);
97/// mail.insert_headers(headers! {
98///     _From: [("I'm Awesome", "bla@examle.com")],
99///     _To: ["unknow@example.com"],
100///     Subject: "Hy there message"
101/// }.unwrap());
102///
103/// // We don't added anythink which needs loading but we could have
104/// // and all of it would have been loaded concurrent and async.
105/// let encoded = mail.into_encodable_mail(ctx.clone())
106///     .wait().unwrap()
107///     .encode_into_bytes(MailType::Ascii).unwrap();
108///
109/// let mail_str = str::from_utf8(&encoded).unwrap();
110/// println!("{}", mail_str);
111/// # }
112/// ```
113///
114/// And here is an example to create the same mail using the
115/// builder:
116///
117/// ```
118/// # extern crate mail_core;
119/// # #[macro_use] extern crate mail_headers as headers;
120/// // either from `mail::headers` or from `mail_header as headers`
121/// use headers::{
122///     headers::*,
123/// #   header_components::Domain
124/// };
125/// use mail_core::{Mail,  Resource};
126/// # use mail_core::default_impl::simple_context;
127///
128/// # fn main() {
129/// # let domain = Domain::from_unchecked("example.com".to_owned());
130/// # let ctx = simple_context::new(domain, "xqi93".parse().unwrap()).unwrap();
131/// let resource = Resource::plain_text("Hy there!", &ctx);
132/// let mut mail = Mail::new_singlepart_mail(resource);
133/// mail.insert_headers(headers! {
134///     _From: [("I'm Awesome", "bla@examle.com")],
135///     _To: ["unknow@example.com"],
136///     Subject: "Hy there message"
137/// }.unwrap());
138/// # }
139/// ```
140///
141/// And here is an example creating a multipart mail
142/// with a made up `multipart` type.
143///
144/// ```
145/// # extern crate mail_core;
146/// # #[macro_use] extern crate mail_headers as headers;
147/// // either from `mail::headers` or from `mail_header as headers`
148/// use headers::{
149///     headers::*,
150///     header_components::{
151///         MediaType,
152/// #       Domain,
153///     }
154/// };
155/// use mail_core::{Mail, Resource};
156/// # use mail_core::default_impl::simple_context;
157///
158/// # fn main() {
159/// # let domain = Domain::from_unchecked("example.com".to_owned());
160/// # let ctx = simple_context::new(domain, "xqi93".parse().unwrap()).unwrap();
161/// let sub_body1 = Mail::plain_text("Body 1", &ctx);
162/// let sub_body2 = Mail::plain_text("Body 2, yay", &ctx);
163///
164/// // This will generate `multipart/x.made-up-think; boundary=randome_generate_boundary`
165/// let media_type = MediaType::new("multipart", "x.made-up-thing").unwrap();
166/// let mut mail = Mail::new_multipart_mail(media_type, vec![sub_body1, sub_body2]);
167/// mail.insert_headers(headers! {
168///     _From: [("I'm Awesome", "bla@examle.com")],
169///     _To: ["unknow@example.com"],
170///     Subject: "Hy there message"
171/// }.unwrap());
172/// # }
173/// ```
174#[derive(Clone, Debug)]
175pub struct Mail {
176    headers: HeaderMap,
177    body: MailBody,
178}
179
180/// A type which either represents a single body, or multiple modies.
181///
182/// Note that you could have a mime multipart body just containing a
183/// single body _and_ it being semantically important to be this way,
184/// so we have to differ between both kinds (instead of just having
185/// a `Vec` of mails)
186#[derive(Clone, Debug)]
187pub enum MailBody {
188    SingleBody {
189        body: Resource
190    },
191    MultipleBodies {
192        //TODO[now]: use Vec1
193        bodies: Vec<Mail>,
194        /// This is part of the standard! But we won't
195        /// make it public available for now. Through
196        /// there is a chance that we need to do so
197        /// in the future as some mechanisms might
198        /// misuse this, well unusual think.
199        hidden_text: SoftAsciiString
200    }
201}
202
203impl Mail {
204
205    /// Create a new plain text mail.
206    ///
207    /// This will
208    ///
209    /// - turn the `text` into a `String`
210    /// - generate a new ContentId using the context
211    /// - create a `Resource` from the `String`
212    ///   (with content type `text/plain; charset=utf-8`)
213    /// - create a mail from the resource
214    ///
215    pub fn plain_text(text: impl Into<String>, ctx: &impl Context) -> Self {
216        let resource = Resource::plain_text(text.into(), ctx);
217        Mail::new_singlepart_mail(resource)
218    }
219
220    /// Returns true if the body of the mail is a multipart body.
221    pub fn has_multipart_body(&self) -> bool {
222        self.body.is_multipart()
223    }
224
225    /// Create a new multipart mail with given content type and given bodies.
226    ///
227    /// Note that while the given `content_type` has to be a `multipart` content
228    /// type (when encoding the mail) it is not required nor expected to have the
229    /// boundary parameter. The boundary will always be automatically generated
230    /// independently of wether or not it was passed as media type.
231    pub fn new_multipart_mail(content_type: MediaType, bodies: Vec<Mail>) -> Self {
232        let mut headers = HeaderMap::new();
233        headers.insert(ContentType::body(content_type));
234        Mail {
235            headers,
236            body: MailBody::MultipleBodies {
237                bodies,
238                hidden_text: SoftAsciiString::new()
239            }
240        }
241    }
242
243    /// Create a new non-multipart mail for given `Resource` as body.
244    pub fn new_singlepart_mail(body: Resource) -> Self {
245        let headers = HeaderMap::new();
246        Mail {
247            headers,
248            body: MailBody::SingleBody { body }
249        }
250    }
251
252
253    /// Inserts a new header into the header map.
254    ///
255    /// This will call `insert` on the inner `HeaderMap`,
256    /// which means all behavior of `HeaderMap::insert`
257    /// does apply, like e.g. the "max one" behavior.
258    pub fn insert_header<H>(&mut self, header: Header<H>)
259        where H: HeaderKind
260    {
261        self.headers_mut().insert(header);
262    }
263
264    /// Inserts all headers into the inner header map.
265    ///
266    /// This will call `HeaderMap::insert_all` internally
267    /// which means all behavior of `HeaderMap::insert`
268    /// does apply, like e.g. the "max one" behavior.
269    pub fn insert_headers(&mut self, headers: HeaderMap) {
270        self.headers_mut().insert_all(headers);
271    }
272
273    /// Returns a reference to the currently set headers.
274    ///
275    /// Note that some headers namely `Content-Transfer-Encoding` as well
276    /// as `Content-Type` for singlepart mails are derived from the content
277    /// and _should not_ be set. If done so they are either ignored or an
278    /// error is caused by them in other parts of the crate (like e.g. encoding).
279    /// Also `Date` is auto-generated if not set and it is needed.
280    pub fn headers(&self) -> &HeaderMap {
281        &self.headers
282    }
283
284    /// Return a mutable reference to the currently set headers.
285    pub fn headers_mut(&mut self) -> &mut HeaderMap {
286        &mut self.headers
287    }
288
289    /// Returns a reference to the body/bodies.
290    pub fn body(&self) -> &MailBody {
291        &self.body
292    }
293
294    /// Return a mutable reference to the body/bodies.
295    pub fn body_mut(&mut self) -> &mut MailBody {
296        &mut self.body
297    }
298
299    /// Validate the mail.
300    ///
301    /// This will mainly validate the mail headers by
302    ///
303    /// - checking if no ContentTransferHeader is given
304    /// - (for mails with multipart bodies) checking if the content type
305    ///   is a `multipart` media type
306    /// - (for mail with non-multipart bodies) check if there is _no_
307    ///   content type header (as the content type header will be derived
308    ///   from he `Resource`)
309    /// - running all header validators (with `use_contextual_validators`) this
310    ///   also checks for "max one" consistency (see `HeaderMap`'s documentation
311    ///   for more details)
312    /// - doing this recursively with all contained mails
313    ///
314    /// Note that this will be called by `into_encodable_mail`, therefor
315    /// it is normally not required to call this function by yourself.
316    ///
317    /// **Be aware that this does a general validation applicable to both the
318    /// top level headers and headers from multipart mail sub bodies.** This
319    /// means it e.g. doesn't check if there are any of the required headers
320    /// (`Date` and `From`).
321    pub fn generally_validate_mail(&self) -> Result<(), MailError> {
322        if self.has_multipart_body() {
323            validate_multipart_headermap(self.headers())?;
324        } else {
325            validate_singlepart_headermap(self.headers())?;
326        }
327        match self.body() {
328            &MailBody::SingleBody { .. } => {},
329            &MailBody::MultipleBodies { ref bodies, .. } => {
330                for body in bodies {
331                    body.generally_validate_mail()?;
332                }
333            }
334        }
335        Ok(())
336    }
337
338    /// Turns the mail into a future with resolves to an `EncodableMail`.
339    ///
340    /// While this future resolves it will do following thinks:
341    ///
342    /// 1. Validate the mail.
343    ///    - This uses `generally_validate_mail`.
344    ///    - Additionally it does check for required top level headers
345    ///      which will not be auto-generated (the `From` header).
346    ///
347    /// 2. Make sure all resources are loaded and transfer encoded.
348    ///    - This will concurrently load + transfer encode all resources
349    ///      replacing the old resource instances with the new loaded and
350    ///      encoded ones once all of them had been loaded (and encoded)
351    ///      successfully.
352    ///
353    /// 3. Insert all auto generated headers (like e.g. `Date`).
354    ///
355    /// 4. Insert boundary parameters into all multipart media types
356    ///    (overriding any existing one).
357    ///
358    /// Use this if you want to encode a mail. This is needed as `Resource`
359    /// instances used in the mail are loaded "on-demand", i.e. if you attach
360    /// two images but never turn the mail into an encodable mail the images
361    /// are never loaded from disk.
362    ///
363    pub fn into_encodable_mail<C: Context>(self, ctx: C) -> MailFuture<C> {
364        MailFuture::new(self, ctx)
365    }
366
367    /// Visit all mail bodies, the visiting order is deterministic.
368    ///
369    /// This function guarantees to have the same visiting order as
370    /// `visit_mail_bodies_mut` as long as the mail has not been changed.
371    ///
372    /// So the 3rd visit in a `visit_mail_bodies` and the 3rd visit in a later
373    /// `visit_mail_bodies_mut` are guaranteed to pass in a reference **to the
374    /// same Resource` (assuming the mail had not been modified in it's structure
375    /// in between).
376    fn visit_mail_bodies<FN>(&self, use_it_fn: &mut FN)
377        where FN: FnMut(&Resource)
378    {
379        use self::MailBody::*;
380        match self.body {
381            SingleBody { ref  body } =>
382                use_it_fn(body),
383            MultipleBodies { ref  bodies, .. } =>
384                for body in bodies {
385                    body.visit_mail_bodies(use_it_fn)
386                }
387        }
388    }
389
390    /// Visit all mail bodies, the visiting order is deterministic.
391    ///
392    /// See `visit_mail_bodies` for a listing of **visiting order guarantees** given
393    /// by this function.
394    fn visit_mail_bodies_mut<FN>(&mut self, use_it_fn: &mut FN)
395        where FN: FnMut(&mut Resource)
396    {
397        use self::MailBody::*;
398        match self.body {
399            SingleBody { ref mut body } =>
400                use_it_fn(body),
401            MultipleBodies { ref mut bodies, .. } =>
402                for body in bodies {
403                    body.visit_mail_bodies_mut(use_it_fn)
404                }
405        }
406    }
407}
408
409
410impl MailBody {
411
412    /// Returns `true` if it's an multipart body.
413    pub fn is_multipart(&self) -> bool {
414        use self::MailBody::*;
415        match *self {
416            SingleBody { .. } => false,
417            MultipleBodies { .. } => true
418        }
419    }
420}
421
422/// A future resolving to an encodable mail.
423pub struct MailFuture<C: Context> {
424    inner: InnerMailFuture<C>
425}
426
427enum InnerMailFuture<C: Context> {
428    New { mail: Mail, ctx: C },
429    Loading {
430        mail: Mail,
431        pending: future::JoinAll<Vec<SendBoxFuture<EncData, ResourceLoadingError>>>,
432        ctx: C
433    },
434    Poison
435}
436
437impl<C> MailFuture<C>
438    where C: Context
439{
440    fn new(mail: Mail, ctx: C) -> Self {
441        MailFuture { inner: InnerMailFuture::New { mail, ctx } }
442    }
443}
444
445impl<T> Future for MailFuture<T>
446    where T: Context,
447{
448    type Item = EncodableMail;
449    type Error = MailError;
450
451    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
452        use self::InnerMailFuture::*;
453        loop {
454            let state = mem::replace(&mut self.inner, InnerMailFuture::Poison);
455            match state {
456                New { mail, ctx } => {
457                    mail.generally_validate_mail()?;
458                    top_level_validation(&mail)?;
459
460                    let mut futures = Vec::new();
461                    mail.visit_mail_bodies(&mut |resource: &Resource| {
462                        let fut = ctx.load_transfer_encoded_resource(resource);
463                        futures.push(fut);
464                    });
465
466                    mem::replace(
467                        &mut self.inner,
468                        InnerMailFuture::Loading {
469                            mail, ctx,
470                            pending: future::join_all(futures)
471                        }
472                    );
473                },
474                Loading { mut mail, mut pending, ctx } => {
475                    match pending.poll() {
476                        Err(err) => return Err(err.into()),
477                        Ok(Async::NotReady) => {
478                            mem::replace(
479                                &mut self.inner,
480                                InnerMailFuture::Loading { mail, pending, ctx }
481                            );
482                            return Ok(Async::NotReady);
483                        },
484                        Ok(Async::Ready(encoded_bodies)) => {
485                            auto_gen_headers(&mut mail, encoded_bodies, &ctx);
486                            return Ok(Async::Ready(EncodableMail(mail)));
487                        }
488                    }
489                },
490                Poison => panic!("called again after completion (through value, error or panic)")
491            }
492        }
493    }
494}
495
496/// a mail with all contained futures resolved, so that it can be encoded
497#[derive(Clone)]
498pub struct EncodableMail(Mail);
499
500impl EncodableMail {
501
502    /// Encode the mail using the given encoding buffer.
503    ///
504    /// After encoding succeeded the buffer should contain
505    /// a fully encoded mail including all attachments, embedded
506    /// images alternate bodies etc.
507    ///
508    /// # Error
509    ///
510    /// This can fail for a large number of reasons, e.g. some
511    /// input can not be encoded with the given mail type or
512    /// some headers/resources breack the mails hard line length limit.
513    pub fn encode(&self, encoder: &mut EncodingBuffer) -> Result<(), MailError> {
514        ::encode::encode_mail(self, true, encoder)
515    }
516
517    /// A wrapper for `encode` which will create a buffer, enocde the mail and then returns the buffers content.
518    pub fn encode_into_bytes(&self, mail_type: MailType) -> Result<Vec<u8>, MailError> {
519        let mut buffer = EncodingBuffer::new(mail_type);
520        self.encode(&mut buffer)?;
521        Ok(buffer.into())
522    }
523}
524
525fn top_level_validation(mail: &Mail) -> Result<(), HeaderValidationError> {
526    if mail.headers().contains(_From) {
527        Ok(())
528    } else {
529        Err(OtherValidationError::NoFrom.into())
530    }
531}
532
533/// insert auto-generated headers like `Date`, `Message-Id` and `Content-Id`
534fn auto_gen_headers<C: Context>(
535    mail: &mut Mail,
536    encoded_resources: Vec<EncData>,
537    ctx: &C
538) {
539    {
540        let headers = mail.headers_mut();
541        if !headers.contains(Date) {
542            headers.insert(Date::body(DateTime::now()));
543        }
544
545        if !headers.contains(MessageId) {
546            headers.insert(MessageId::body(ctx.generate_message_id()));
547        }
548    }
549
550    let mut iter = encoded_resources.into_iter();
551    mail.visit_mail_bodies_mut(&mut move |resource: &mut Resource| {
552        let enc_data = iter.next()
553            .expect("[BUG] mail structure modified while turing it into encoded mail");
554        mem::replace(resource, Resource::EncData(enc_data));
555    });
556
557    let mut boundary_count = 0;
558    recursive_auto_gen_headers(mail, &mut boundary_count, ctx);
559
560    // Make sure no **top-level** body has a content-id field, as it already has a Message-Id
561    mail.headers_mut().remove(ContentId);
562}
563
564/// returns the `EncData` from a resource
565///
566/// # Panics
567///
568/// Panics if the resource is not transfer encoded
569pub(crate) fn assume_encoded(resource: &Resource) -> &EncData {
570    match resource {
571        &Resource::EncData(ref ed) => ed,
572        _ => panic!("[BUG] auto gen/encode should only be called on all resources are loaded")
573    }
574}
575
576/// Auto-generates some headers for any body including non top-level mail bodies.
577///
578/// For mails which are not multipart mails this does:
579/// - set metadata for the `Content-Disposition` header (e.g. `file-name`, `read-date`, ...)
580/// - insert a `Content-Id` header
581///   - this overwrites any already contained content-id header
582///
583/// For multipart mails this does:
584/// - create/overwrite the boundary for the `Content-Type` header
585/// - call this method for all bodies in the multipart body
586fn recursive_auto_gen_headers<C: Context>(mail: &mut Mail, boundary_count: &mut usize, ctx: &C) {
587    let &mut Mail { ref mut headers, ref mut body } = mail;
588    match body {
589        &mut MailBody::SingleBody { ref mut body } => {
590            let data = assume_encoded(body);
591
592            if let Some(Ok(disposition)) = headers.get_single_mut(ContentDisposition) {
593                let current_file_meta_mut = disposition.file_meta_mut();
594                current_file_meta_mut.replace_empty_fields_with(data.file_meta())
595            }
596
597            headers.insert(ContentId::body(data.content_id().clone()));
598        },
599        &mut MailBody::MultipleBodies { ref mut bodies, .. } => {
600            let mut headers: &mut HeaderMap = headers;
601            let content_type: &mut Header<ContentType> = headers
602                .get_single_mut(ContentType)
603                .expect("[BUG] mail was already validated")
604                .expect("[BUG] mail was already validated");
605
606            let boundary = create_structured_random_boundary(*boundary_count);
607            *boundary_count += 1;
608            content_type.set_param(BOUNDARY, boundary);
609
610            for sub_mail in bodies {
611                recursive_auto_gen_headers(sub_mail, boundary_count, ctx);
612            }
613        }
614    }
615}
616
617pub(crate) fn validate_multipart_headermap(headers: &HeaderMap)
618    -> Result<(), MailError>
619{
620
621    if headers.contains(ContentTransferEncoding) {
622        return Err(OtherValidationError::ContentTransferEncodingHeaderGiven.into());
623    }
624    if let Some(header) = headers.get_single(ContentType) {
625        let header_with_right_type = header?;
626        if !header_with_right_type.is_multipart() {
627            return Err(OtherValidationError::SingleMultipartMixup.into());
628        }
629    } else {
630        return Err(OtherValidationError::MissingContentTypeHeader.into());
631    }
632    headers.use_contextual_validators()?;
633    Ok(())
634}
635
636
637pub(crate) fn validate_singlepart_headermap(headers: &HeaderMap)
638    -> Result<(), HeaderValidationError>
639{
640    if headers.contains(ContentTransferEncoding) {
641        return Err(OtherValidationError::ContentTransferEncodingHeaderGiven.into());
642    }
643    if headers.contains(ContentType) {
644        return Err(OtherValidationError::ContentTypeHeaderGiven.into());
645    }
646    headers.use_contextual_validators()?;
647    Ok(())
648}
649
650impl Deref for EncodableMail {
651
652    type Target = Mail;
653    fn deref( &self ) -> &Self::Target {
654        &self.0
655    }
656}
657
658impl Into<Mail> for EncodableMail {
659    fn into(self) -> Mail {
660        let EncodableMail(mail) = self;
661        mail
662    }
663}
664
665
666impl fmt::Debug for EncodableMail {
667    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
668        write!(fter, "EncodableMail {{ .. }}")
669    }
670}
671
672
673#[cfg(test)]
674mod test {
675    use std::fmt::Debug;
676
677    trait AssertDebug: Debug {}
678    trait AssertSend: Send {}
679    trait AssertSync: Sync {}
680
681    mod Mail {
682        #![allow(non_snake_case)]
683        use headers::{
684            headers::{
685                Subject,
686                Comments
687            }
688        };
689        use default_impl::test_context;
690        use super::super::*;
691        use super::{AssertDebug, AssertSend, AssertSync};
692
693        impl AssertDebug for Mail {}
694        impl AssertSend for Mail {}
695        impl AssertSync for Mail {}
696
697
698        #[test]
699        fn visit_mail_bodies_does_not_skip() {
700            let ctx = test_context();
701            let mail = Mail {
702                headers: HeaderMap::new(),
703                body: MailBody::MultipleBodies {
704                    bodies: vec! [
705                        Mail {
706                            headers: HeaderMap::new(),
707                            body: MailBody::MultipleBodies {
708                                bodies: vec! [
709                                    Mail {
710                                        headers: HeaderMap::new(),
711                                        body: MailBody::SingleBody {
712                                            body: Resource::plain_text("r1", &ctx)
713                                        }
714                                    },
715                                    Mail {
716                                        headers: HeaderMap::new(),
717                                        body: MailBody::SingleBody {
718                                            body: Resource::plain_text("r2", &ctx)
719                                        }
720                                    }
721                                ],
722                                hidden_text: Default::default()
723                            }
724                        },
725                        Mail {
726                            headers: HeaderMap::new(),
727                            body: MailBody::SingleBody {
728                                body: Resource::plain_text("r3", &ctx)
729                            }
730                        }
731
732                    ],
733                    hidden_text: Default::default()
734                }
735            };
736
737            let mut body_count = 0;
738            mail.visit_mail_bodies(&mut |body: &Resource| {
739                if let &Resource::Data(ref body) = body {
740                    assert_eq!(
741                        [ "r1", "r2", "r3"][body_count].as_bytes(),
742                        body.buffer().as_ref()
743                    )
744                } else {
745                    panic!("unexpected body: {:?}", body);
746                }
747                body_count += 1;
748            });
749
750            assert_eq!(body_count, 3);
751        }
752
753        test!(insert_header_set_a_header, {
754            let ctx = test_context();
755            let mut mail = Mail::plain_text("r0", &ctx);
756            mail.insert_header(Subject::auto_body("hy")?);
757            assert!(mail.headers().contains(Subject));
758        });
759
760
761
762        test!(insert_headers_sets_all_headers, {
763            let ctx = test_context();
764            let mut mail = Mail::plain_text("r0", &ctx);
765            mail.insert_headers(headers! {
766                Subject: "yes",
767                Comments: "so much"
768            }?);
769
770            assert!(mail.headers().contains(Subject));
771            assert!(mail.headers().contains(Comments));
772        });
773
774    }
775
776    mod EncodableMail {
777        #![allow(non_snake_case)]
778        use chrono::{Utc, TimeZone};
779        use headers::{
780            headers::{
781                _From, ContentType, ContentTransferEncoding,
782                Date, Subject
783            }
784        };
785        use default_impl::test_context;
786        use super::super::*;
787        use super::{AssertDebug, AssertSend, AssertSync};
788
789        impl AssertDebug for EncodableMail {}
790        impl AssertSend for EncodableMail {}
791        impl AssertSync for EncodableMail {}
792
793        #[test]
794        fn sets_generated_headers_for_outer_mail() {
795            let ctx = test_context();
796            let resource = Resource::plain_text("r9", &ctx);
797            let mail = Mail {
798                headers: headers!{
799                    _From: ["random@this.is.no.mail"],
800                    Subject: "hoho"
801                }.unwrap(),
802                body: MailBody::SingleBody { body: resource }
803            };
804
805            let enc_mail = assert_ok!(mail.into_encodable_mail(ctx).wait());
806
807            let headers: &HeaderMap = enc_mail.headers();
808            assert!(headers.contains(_From));
809            assert!(headers.contains(Subject));
810            assert!(headers.contains(Date));
811            // ContenType/TransferEncoding are added on the fly when encoding
812            // for leaf bodies
813            assert_not!(headers.contains(ContentType));
814            assert_not!(headers.contains(ContentTransferEncoding));
815            assert!(headers.contains(MessageId));
816            assert_eq!(headers.len(), 4);
817        }
818
819        #[test]
820        fn sets_generated_headers_for_sub_mails() {
821            let ctx = test_context();
822            let resource = Resource::plain_text("r9", &ctx);
823            let mail = Mail {
824                headers: headers!{
825                    _From: ["random@this.is.no.mail"],
826                    Subject: "hoho",
827                    ContentType: "multipart/mixed"
828                }.unwrap(),
829                body: MailBody::MultipleBodies {
830                    bodies: vec![
831                        Mail {
832                            headers: HeaderMap::new(),
833                            body: MailBody::SingleBody { body: resource }
834                        }
835                    ],
836                    hidden_text: Default::default()
837                }
838            };
839
840            let mail = mail.into_encodable_mail(ctx).wait().unwrap();
841
842            assert!(mail.headers().contains(_From));
843            assert!(mail.headers().contains(Subject));
844            assert!(mail.headers().contains(Date));
845            assert!(mail.headers().contains(ContentType));
846            assert_not!(mail.headers().contains(ContentTransferEncoding));
847
848            if let MailBody::MultipleBodies { ref bodies, ..} = mail.body {
849                let headers = bodies[0].headers();
850                assert_not!(headers.contains(Date));
851            } else {
852                unreachable!()
853            }
854        }
855
856        #[test]
857        fn runs_contextual_validators() {
858            let ctx = test_context();
859            let mail = Mail {
860                headers: headers!{
861                    _From: ["random@this.is.no.mail", "u.p.s@s.p.u"],
862                    Subject: "hoho"
863                }.unwrap(),
864                body: MailBody::SingleBody { body: Resource::plain_text("r9", &ctx) }
865            };
866
867            assert_err!(mail.into_encodable_mail(ctx).wait());
868        }
869
870        #[test]
871        fn checks_there_is_from() {
872            let ctx = test_context();
873            let mail = Mail {
874                headers: headers!{
875                    Subject: "hoho"
876                }.unwrap(),
877                body: MailBody::SingleBody { body: Resource::plain_text("r9", &ctx) }
878            };
879
880            assert_err!(mail.into_encodable_mail(ctx).wait());
881        }
882
883        test!(does_not_override_date_if_set, {
884            let ctx = test_context();
885            let provided_date = Utc.ymd(1992, 5, 25).and_hms(23, 41, 12);
886            let mut mail = Mail::plain_text("r9", &ctx);
887            mail.insert_headers(headers! {
888                _From: ["random@this.is.no.mail"],
889                Subject: "hoho",
890                Date: provided_date.clone()
891            }?);
892
893            let enc_mail = assert_ok!(mail.into_encodable_mail(ctx).wait());
894            let used_date = enc_mail.headers()
895                .get_single(Date)
896                .unwrap()
897                .unwrap();
898
899            assert_eq!(&**used_date.body(), &provided_date);
900        });
901
902    }
903
904}