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}