summaryrefslogtreecommitdiffstats
path: root/core/src/mail.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/mail.rs')
-rw-r--r--core/src/mail.rs889
1 files changed, 889 insertions, 0 deletions
diff --git a/core/src/mail.rs b/core/src/mail.rs
new file mode 100644
index 0000000..4c2e130
--- /dev/null
+++ b/core/src/mail.rs
@@ -0,0 +1,889 @@
+//! Module containing all the parts for creating/encoding Mails.
+//!
+
+
+use std::{
+ ops::Deref,
+ fmt,
+ mem
+};
+
+use soft_ascii_string::SoftAsciiString;
+use futures::{
+ future,
+ Future,
+ Async,
+ Poll
+};
+use media_type::BOUNDARY;
+
+use internals::{
+ MailType,
+ encoder::EncodingBuffer
+};
+use headers::{
+ Header, HeaderKind,
+ HeaderMap,
+ headers::{
+ ContentType, _From,
+ ContentTransferEncoding,
+ Date, MessageId,
+ ContentDisposition
+ },
+ header_components::{
+ DateTime,
+ MediaType
+ },
+ error::{
+ HeaderValidationError,
+ }
+};
+
+use ::{
+ utils::SendBoxFuture,
+ mime::create_structured_random_boundary,
+ error::{
+ MailError,
+ OtherValidationError,
+ ResourceLoadingError
+ },
+ resource::*,
+ context::Context
+};
+
+/// A type representing a Mail.
+///
+/// This type is used to represent a mail including headers and body.
+/// It is also used for the bodies of multipart mime mail bodies as
+/// they can be seen as "sub-mails" or "hirachical nested mails", at
+/// last wrt. everything relevant on this type.
+///
+/// A mail can be created using the `Builder` or more specific either
+/// the `SinglepartBuilder` or the `MultipartBuilder` for a multipart
+/// mime mail.
+///
+/// # Example
+///
+/// This will create, encode and print a simple plain text mail.
+///
+/// ```
+/// # extern crate futures;
+/// # extern crate mail_core;
+/// # extern crate mail_internals;
+/// # #[macro_use] extern crate mail_headers as headers;
+/// # use futures::Future;
+/// # use mail_internals::MailType;
+/// use std::str;
+/// // either from `mail::headers` or from `mail_header as headers`
+/// use headers::{
+/// headers::*,
+/// header_components::Domain
+/// };
+/// use mail_core::{
+/// Mail, Resource,
+/// default_impl::simple_context
+/// };
+///
+/// # fn main() {
+/// // Domain will implement `from_str` in the future,
+/// // currently it doesn't have a validator/parser.
+/// let domain = Domain::from_unchecked("example.com".to_owned());
+/// // Normally you create this _once per application_.
+/// let ctx = simple_context::new(domain, "xqi93".parse().unwrap())
+/// .unwrap();
+///
+/// let mut mail = Mail::plain_text("Hy there!", &ctx);
+/// mail.insert_headers(headers! {
+/// _From: [("I'm Awesome", "bla@examle.com")],
+/// _To: ["unknow@example.com"],
+/// Subject: "Hy there message"
+/// }.unwrap());
+///
+/// // We don't added anythink which needs loading but we could have
+/// // and all of it would have been loaded concurrent and async.
+/// let encoded = mail.into_encodable_mail(ctx.clone())
+/// .wait().unwrap()
+/// .encode_into_bytes(MailType::Ascii).unwrap();
+///
+/// let mail_str = str::from_utf8(&encoded).unwrap();
+/// println!("{}", mail_str);
+/// # }
+/// ```
+///
+/// And here is an example to create the same mail using the
+/// builder:
+///
+/// ```
+/// # extern crate mail_core;
+/// # #[macro_use] extern crate mail_headers as headers;
+/// // either from `mail::headers` or from `mail_header as headers`
+/// use headers::{
+/// headers::*,
+/// # header_components::Domain
+/// };
+/// use mail_core::{Mail, Resource};
+/// # use mail_core::default_impl::simple_context;
+///
+/// # fn main() {
+/// # let domain = Domain::from_unchecked("example.com".to_owned());
+/// # let ctx = simple_context::new(domain, "xqi93".parse().unwrap()).unwrap();
+/// let resource = Resource::plain_text("Hy there!", &ctx);
+/// let mut mail = Mail::new_singlepart_mail(resource);
+/// mail.insert_headers(headers! {
+/// _From: [("I'm Awesome", "bla@examle.com")],
+/// _To: ["unknow@example.com"],
+/// Subject: "Hy there message"
+/// }.unwrap());
+/// # }
+/// ```
+///
+/// And here is an example creating a multipart mail
+/// with a made up `multipart` type.
+///
+/// ```
+/// # extern crate mail_core;
+/// # #[macro_use] extern crate mail_headers as headers;
+/// // either from `mail::headers` or from `mail_header as headers`
+/// use headers::{
+/// headers::*,
+/// header_components::{
+/// MediaType,
+/// # Domain,
+/// }
+/// };
+/// use mail_core::{Mail, Resource};
+/// # use mail_core::default_impl::simple_context;
+///
+/// # fn main() {
+/// # let domain = Domain::from_unchecked("example.com".to_owned());
+/// # let ctx = simple_context::new(domain, "xqi93".parse().unwrap()).unwrap();
+/// let sub_body1 = Mail::plain_text("Body 1", &ctx);
+/// let sub_body2 = Mail::plain_text("Body 2, yay", &ctx);
+///
+/// // This will generate `multipart/x.made-up-think; boundary=randome_generate_boundary`
+/// let media_type = MediaType::new("multipart", "x.made-up-thing").unwrap();
+/// let mut mail = Mail::new_multipart_mail(media_type, vec![sub_body1, sub_body2]);
+/// mail.insert_headers(headers! {
+/// _From: [("I'm Awesome", "bla@examle.com")],
+/// _To: ["unknow@example.com"],
+/// Subject: "Hy there message"
+/// }.unwrap());
+/// # }
+/// ```
+#[derive(Clone, Debug)]
+pub struct Mail {
+ headers: HeaderMap,
+ body: MailBody,
+}
+
+/// A type which either represents a single body, or multiple modies.
+///
+/// Note that you could have a mime multipart body just containing a
+/// single body _and_ it being semantically important to be this way,
+/// so we have to differ between both kinds (instead of just having
+/// a `Vec` of mails)
+#[derive(Clone, Debug)]
+pub enum MailBody {
+ SingleBody {
+ body: Resource
+ },
+ MultipleBodies {
+ //TODO[now]: use Vec1
+ bodies: Vec<Mail>,
+ /// This is part of the standard! But we won't
+ /// make it public available for now. Through
+ /// there is a chance that we need to do so
+ /// in the future as some mechanisms might
+ /// misuse this, well unusual think.
+ hidden_text: SoftAsciiString
+ }
+}
+
+impl Mail {
+
+ /// Create a new plain text mail.
+ ///
+ /// This will
+ ///
+ /// - turn the `text` into a `String`
+ /// - generate a new ContentId using the context
+ /// - create a `Resource` from the `String`
+ /// (with content type `text/plain; charset=utf-8`)
+ /// - create a mail from the resource
+ ///
+ pub fn plain_text(text: impl Into<String>, ctx: &impl Context) -> Self {
+ let resource = Resource::plain_text(text.into(), ctx);
+ Mail::new_singlepart_mail(resource)
+ }
+
+ /// Returns true if the body of the mail is a multipart body.
+ pub fn has_multipart_body(&self) -> bool {
+ self.body.is_multipart()
+ }
+
+ /// Create a new multipart mail with given content type and given bodies.
+ ///
+ /// Note that while the given `content_type` has to be a `multipart` content
+ /// type (when encoding the mail) it is not required nor expected to have the
+ /// boundary parameter. The boundary will always be automatically generated
+ /// independently of wether or not it was passed as media type.
+ pub fn new_multipart_mail(content_type: MediaType, bodies: Vec<Mail>) -> Self {
+ let mut headers = HeaderMap::new();
+ headers.insert(ContentType::body(content_type));
+ Mail {
+ headers,
+ body: MailBody::MultipleBodies {
+ bodies,
+ hidden_text: SoftAsciiString::new()
+ }
+ }
+ }
+
+ /// Create a new non-multipart mail for given `Resource` as body.
+ pub fn new_singlepart_mail(body: Resource) -> Self {
+ let headers = HeaderMap::new();
+ Mail {
+ headers,
+ body: MailBody::SingleBody { body }
+ }
+ }
+
+
+ /// Inserts a new header into the header map.
+ ///
+ /// This will call `insert` on the inner `HeaderMap`,
+ /// which means all behavior of `HeaderMap::insert`
+ /// does apply, like e.g. the "max one" behavior.
+ pub fn insert_header<H>(&mut self, header: Header<H>)
+ where H: HeaderKind
+ {
+ self.headers_mut().insert(header);
+ }
+
+ /// Inserts all headers into the inner header map.
+ ///
+ /// This will call `HeaderMap::insert_all` internally
+ /// which means all behavior of `HeaderMap::insert`
+ /// does apply, like e.g. the "max one" behavior.
+ pub fn insert_headers(&mut self, headers: HeaderMap) {
+ self.headers_mut().insert_all(headers);
+ }
+
+ /// Returns a reference to the currently set headers.
+ ///
+ /// Note that some headers namely `Content-Transfer-Encoding` as well
+ /// as `Content-Type` for singlepart mails are derived from the content
+ /// and _should not_ be set. If done so they are either ignored or an
+ /// error is caused by them in other parts of the crate (like e.g. encoding).
+ /// Also `Date` is auto-generated if not set and it is needed.
+ pub fn headers(&self) -> &HeaderMap {
+ &self.headers
+ }
+
+ /// Return a mutable reference to the currently set headers.
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ &mut self.headers
+ }
+
+ /// Returns a reference to the body/bodies.
+ pub fn body(&self) -> &MailBody {
+ &self.body
+ }
+
+ /// Return a mutable reference to the body/bodies.
+ pub fn body_mut(&mut self) -> &mut MailBody {
+ &mut self.body
+ }
+
+ /// Validate the mail.
+ ///
+ /// This will mainly validate the mail headers by
+ ///
+ /// - checking if no ContentTransferHeader is given
+ /// - (for mails with multipart bodies) checking if the content type
+ /// is a `multipart` media type
+ /// - (for mail with non-multipart bodies) check if there is _no_
+ /// content type header (as the content type header will be derived
+ /// from he `Resource`)
+ /// - running all header validators (with `use_contextual_validators`) this
+ /// also checks for "max one" consistency (see `HeaderMap`'s documentation
+ /// for more details)
+ /// - doing this recursively with all contained mails
+ ///
+ /// Note that this will be called by `into_encodable_mail`, therefor
+ /// it is normally not required to call this function by yourself.
+ ///
+ /// **Be aware that this does a general validation applicable to both the
+ /// top level headers and headers from multipart mail sub bodies.** This
+ /// means it e.g. doesn't check if there are any of the required headers
+ /// (`Date` and `From`).
+ pub fn generally_validate_mail(&self) -> Result<(), MailError> {
+ if self.has_multipart_body() {
+ validate_multipart_headermap(self.headers())?;
+ } else {
+ validate_singlepart_headermap(self.headers())?;
+ }
+ match self.body() {
+ &MailBody::SingleBody { .. } => {},
+ &MailBody::MultipleBodies { ref bodies, .. } => {
+ for body in bodies {
+ body.generally_validate_mail()?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Turns the mail into a future with resolves to an `EncodableMail`.
+ ///
+ /// While this future resolves it will do following thinks:
+ ///
+ /// 1. Validate the mail.
+ /// - This uses `generally_validate_mail`.
+ /// - Additionally it does check for required top level headers
+ /// which will not be auto-generated (the `From` header).
+ ///
+ /// 2. Make sure all resources are loaded and transfer encoded.
+ /// - This will concurrently load + transfer encode all resources
+ /// replacing the old resource instances with the new loaded and
+ /// encoded ones once all of them had been loaded (and encoded)
+ /// successfully.
+ ///
+ /// 3. Insert all auto generated headers (like e.g. `Date`).
+ ///
+ /// 4. Insert boundary parameters into all multipart media types
+ /// (overriding any existing one).
+ ///
+ /// Use this if you want to encode a mail. This is needed as `Resource`
+ /// instances used in the mail are loaded "on-demand", i.e. if you attach
+ /// two images but never turn the mail into an encodable mail the images
+ /// are never loaded from disk.
+ ///
+ pub fn into_encodable_mail<C: Context>(self, ctx: C) -> MailFuture<C> {
+ MailFuture::new(self, ctx)
+ }
+
+ /// Visit all mail bodies, the visiting order is deterministic.
+ ///
+ /// This function guarantees to have the same visiting order as
+ /// `visit_mail_bodies_mut` as long as the mail has not been changed.
+ ///
+ /// So the 3rd visit in a `visit_mail_bodies` and the 3rd visit in a later
+ /// `visit_mail_bodies_mut` are guaranteed to pass in a reference **to the
+ /// same Resource` (assuming the mail had not been modified in it's structure
+ /// in between).
+ fn visit_mail_bodies<FN>(&self, use_it_fn: &mut FN)
+ where FN: FnMut(&Resource)
+ {
+ use self::MailBody::*;
+ match self.body {
+ SingleBody { ref body } =>
+ use_it_fn(body),
+ MultipleBodies { ref bodies, .. } =>
+ for body in bodies {
+ body.visit_mail_bodies(use_it_fn)
+ }
+ }
+ }
+
+ /// Visit all mail bodies, the visiting order is deterministic.
+ ///
+ /// See `visit_mail_bodies` for a listing of **visiting order guarantees** given
+ /// by this function.
+ fn visit_mail_bodies_mut<FN>(&mut self, use_it_fn: &mut FN)
+ where FN: FnMut(&mut Resource)
+ {
+ use self::MailBody::*;
+ match self.body {
+ SingleBody { ref mut body } =>
+ use_it_fn(body),
+ MultipleBodies { ref mut bodies, .. } =>
+ for body in bodies {
+ body.visit_mail_bodies_mut(use_it_fn)
+ }
+ }
+ }
+}
+
+
+impl MailBody {
+
+ /// Returns `true` if it's an multipart body.
+ pub fn is_multipart(&self) -> bool {
+ use self::MailBody::*;
+ match *self {
+ SingleBody { .. } => false,
+ MultipleBodies { .. } => true
+ }
+ }
+}
+
+/// A future resolving to an encodable mail.
+pub struct MailFuture<C: Context> {
+ inner: InnerMailFuture<C>
+}
+
+enum InnerMailFuture<C: Context> {
+ New { mail: Mail, ctx: C },
+ Loading {
+ mail: Mail,
+ pending: future::JoinAll<Vec<SendBoxFuture<EncData, ResourceLoadingError>>>,
+ ctx: C
+ },
+ Poison
+}
+
+impl<C> MailFuture<C>
+ where C: Context
+{
+ fn new(mail: Mail, ctx: C) -> Self {
+ MailFuture { inner: InnerMailFuture::New { mail, ctx } }
+ }
+}
+
+impl<T> Future for MailFuture<T>
+ where T: Context,
+{
+ type Item = EncodableMail;
+ type Error = MailError;
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ use self::InnerMailFuture::*;
+ loop {
+ let state = mem::replace(&mut self.inner, InnerMailFuture::Poison);
+ match state {
+ New { mail, ctx } => {
+ mail.generally_validate_mail()?;
+ top_level_validation(&mail)?;
+
+ let mut futures = Vec::new();
+ mail.visit_mail_bodies(&mut |resource: &Resource| {
+ let fut = ctx.load_transfer_encoded_resource(resource);
+ futures.push(fut);
+ });
+
+ mem::replace(
+ &mut self.inner,
+ InnerMailFuture::Loading {
+ mail, ctx,
+ pending: future::join_all(futures)
+ }
+ );
+ },
+ Loading { mut mail, mut pending, ctx } => {
+ match pending.poll() {
+ Err(err) => return Err(err.into()),
+ Ok(Async::NotReady) => {
+ mem::replace(
+ &mut self.inner,
+ InnerMailFuture::Loading { mail, pending, ctx }
+ );
+ return Ok(Async::NotReady);
+ },
+ Ok(Async::Ready(encoded_bodies)) => {
+ auto_gen_headers(&mut mail, encoded_bodies, &ctx);
+ return Ok(Async::Ready(EncodableMail(mail)));
+ }
+ }
+ },
+ Poison => panic!("called again after completion (through value, error or panic)")
+ }
+ }
+ }
+}
+
+/// a mail with all contained futures resolved, so that it can be encoded
+#[derive(Clone)]
+pub struct EncodableMail(Mail);
+
+impl EncodableMail {
+
+ /// Encode the mail using the given encoding buffer.
+ ///
+ /// After encoding succeeded the buffer should contain
+ /// a fully encoded mail including all attachments, embedded
+ /// images alternate bodies etc.
+ ///
+ /// # Error
+ ///
+ /// This can fail for a large number of reasons, e.g. some
+ /// input can not be encoded with the given mail type or
+ /// some headers/resources breack the mails hard line length limit.
+ pub fn encode(&self, encoder: &mut EncodingBuffer) -> Result<(), MailError> {
+ ::encode::encode_mail(self, true, encoder)
+ }
+
+ /// A wrapper for `encode` which will create a buffer, enocde the mail and then returns the buffers content.
+ pub fn encode_into_bytes(&self, mail_type: MailType) -> Result<Vec<u8>, MailError> {
+ let mut buffer = EncodingBuffer::new(mail_type);
+ self.encode(&mut buffer)?;
+ Ok(buffer.into())
+ }
+}
+
+fn top_level_validation(mail: &Mail) -> Result<(), HeaderValidationError> {
+ if mail.headers().contains(_From) {
+ Ok(())
+ } else {
+ Err(OtherValidationError::NoFrom.into())
+ }
+}
+
+/// inserts ContentType and ContentTransferEncoding into
+/// the headers of any contained `MailBody::SingleBody`,
+/// based on the `Resource` representing the body
+fn auto_gen_headers<C: Context>(
+ mail: &mut Mail,
+ encoded_resources: Vec<EncData>,
+ ctx: &C
+) {
+ {
+ let headers = mail.headers_mut();
+ if !headers.contains(Date) {
+ headers.insert(Date::body(DateTime::now()));
+ }
+
+ if !headers.contains(MessageId) {
+ headers.insert(MessageId::body(ctx.generate_message_id()));
+ }
+ }
+
+ let mut iter = encoded_resources.into_iter();
+ mail.visit_mail_bodies_mut(&mut move |resource: &mut Resource| {
+ let enc_data = iter.next()
+ .expect("[BUG] mail structure modified while turing it into encoded mail");
+ mem::replace(resource, Resource::EncData(enc_data));
+ });
+
+ let mut boundary_count = 0;
+ recursive_auto_gen_headers(mail, &mut boundary_count, ctx);
+}
+
+/// returns the `EncData` from a resource
+///
+/// # Panics
+///
+/// Panics if the resource is not transfer encoded
+pub(crate) fn assume_encoded(resource: &Resource) -> &EncData {
+ match resource {
+ &Resource::EncData(ref ed) => ed,
+ _ => panic!("[BUG] auto gen/encode should only be called on all resources are loaded")
+ }
+}
+
+fn recursive_auto_gen_headers<C: Context>(mail: &mut Mail, boundary_count: &mut usize, ctx: &C) {
+ let &mut Mail { ref mut headers, ref mut body } = mail;
+ match body {
+ &mut MailBody::SingleBody { ref mut body } => {
+ if let Some(Ok(disposition)) = headers.get_single_mut(ContentDisposition) {
+ let current_file_meta_mut = disposition.file_meta_mut();
+ let data = assume_encoded(body);
+ current_file_meta_mut.replace_empty_fields_with(data.file_meta())
+ }
+ },
+ &mut MailBody::MultipleBodies { ref mut bodies, .. } => {
+ let mut headers: &mut HeaderMap = headers;
+ let content_type: &mut Header<ContentType> = headers
+ .get_single_mut(ContentType)
+ .expect("[BUG] mail was already validated")
+ .expect("[BUG] mail was already validated");
+
+ let boundary = create_structured_random_boundary(*boundary_count);
+ *boundary_count += 1;
+ content_type.set_param(BOUNDARY, boundary);
+
+ for sub_mail in bodies {
+ recursive_auto_gen_headers(sub_mail, boundary_count, ctx);
+ }
+ }
+ }
+}
+
+pub(crate) fn validate_multipart_headermap(headers: &HeaderMap)
+ -> Result<(), MailError>
+{
+
+ if headers.contains(ContentTransferEncoding) {
+ return Err(OtherValidationError::ContentTransferEncodingHeaderGiven.into());
+ }
+ if let Some(header) = headers.get_single(ContentType) {
+ let header_with_right_type = header?;
+ if !header_with_right_type.is_multipart() {
+ return Err(OtherValidationError::SingleMultipartMixup.into());
+ }
+ } else {
+ return Err(OtherValidationError::MissingContentTypeHeader.into());
+ }
+ headers.use_contextual_validators()?;
+ Ok(())
+}
+
+
+pub(crate) fn validate_singlepart_headermap(headers: &HeaderMap)
+ -> Result<(), HeaderValidationError>
+{
+ if headers.contains(ContentTransferEncoding) {
+ return Err(OtherValidationError::ContentTransferEncodingHeaderGiven.into());
+ }
+ if headers.contains(ContentType) {
+ return Err(OtherValidationError::ContentTypeHeaderGiven.into());
+ }
+ headers.use_contextual_validators()?;
+ Ok(())
+}
+
+impl Deref for EncodableMail {
+
+ type Target = Mail;
+ fn deref( &self ) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl Into<Mail> for EncodableMail {
+ fn into(self) -> Mail {
+ let EncodableMail(mail) = self;
+ mail
+ }
+}
+
+
+impl fmt::Debug for EncodableMail {
+ fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
+ write!(fter, "EncodableMail {{ .. }}")
+ }
+}
+
+
+#[cfg(test)]
+mod test {
+ use std::fmt::Debug;
+
+ trait AssertDebug: Debug {}
+ trait AssertSend: Send {}
+ trait AssertSync: Sync {}
+
+ mod Mail {
+ #![allow(non_snake_case)]
+ use headers::{
+ headers::{
+ Subject,
+ Comments
+ }
+ };
+ use default_impl::test_context;
+ use super::super::*;
+ use super::{AssertDebug, AssertSend, AssertSync};
+
+ impl AssertDebug for Mail {}
+ impl AssertSend for Mail {}
+ impl AssertSync for Mail {}
+
+
+ #[test]
+ fn visit_mail_bodies_does_not_skip() {
+ let ctx = test_context();
+ let mail = Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::MultipleBodies {
+ bodies: vec! [
+ Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::MultipleBodies {
+ bodies: vec! [
+ Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::SingleBody {
+ body: Resource::plain_text("r1", &ctx)
+ }
+ },
+ Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::SingleBody {
+ body: Resource::plain_text("r2", &ctx)
+ }
+ }
+ ],
+ hidden_text: Default::default()
+ }
+ },
+ Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::SingleBody {
+ body: Resource::plain_text("r3", &ctx)
+ }
+ }
+
+ ],
+ hidden_text: Default::default()
+ }
+ };
+
+ let mut body_count = 0;
+ mail.visit_mail_bodies(&mut |body: &Resource| {
+ if let &Resource::Data(ref body) = body {
+ assert_eq!(
+ [ "r1", "r2", "r3"][body_count].as_bytes(),
+ body.buffer().as_ref()
+ )
+ } else {
+ panic!("unexpected body: {:?}", body);
+ }
+ body_count += 1;
+ });
+
+ assert_eq!(body_count, 3);
+ }
+
+ test!(insert_header_set_a_header, {
+ let ctx = test_context();
+ let mut mail = Mail::plain_text("r0", &ctx);
+ mail.insert_header(Subject::auto_body("hy")?);
+ assert!(mail.headers().contains(Subject));
+ });
+
+
+
+ test!(insert_headers_sets_all_headers, {
+ let ctx = test_context();
+ let mut mail = Mail::plain_text("r0", &ctx);
+ mail.insert_headers(headers! {
+ Subject: "yes",
+ Comments: "so much"
+ }?);
+
+ assert!(mail.headers().contains(Subject));
+ assert!(mail.headers().contains(Comments));
+ });
+
+ }
+
+ mod EncodableMail {
+ #![allow(non_snake_case)]
+ use chrono::{Utc, TimeZone};
+ use headers::{
+ headers::{
+ _From, ContentType, ContentTransferEncoding,
+ Date, Subject
+ }
+ };
+ use default_impl::test_context;
+ use super::super::*;
+ use super::{AssertDebug, AssertSend, AssertSync};
+
+ impl AssertDebug for EncodableMail {}
+ impl AssertSend for EncodableMail {}
+ impl AssertSync for EncodableMail {}
+
+ #[test]
+ fn sets_generated_headers_for_outer_mail() {
+ let ctx = test_context();
+ let resource = Resource::plain_text("r9", &ctx);
+ let mail = Mail {
+ headers: headers!{
+ _From: ["random@this.is.no.mail"],
+ Subject: "hoho"
+ }.unwrap(),
+ body: MailBody::SingleBody { body: resource }
+ };
+
+ let enc_mail = assert_ok!(mail.into_encodable_mail(ctx).wait());
+
+ let headers: &HeaderMap = enc_mail.headers();
+ assert!(headers.contains(_From));
+ assert!(headers.contains(Subject));
+ assert!(headers.contains(Date));
+ // ContenType/TransferEncoding are added on the fly when encoding
+ // for leaf bodies
+ assert_not!(headers.contains(ContentType));
+ assert_not!(headers.contains(ContentTransferEncoding));
+ assert!(headers.contains(MessageId));
+ assert_eq!(headers.len(), 4);
+ }
+
+ #[test]
+ fn sets_generated_headers_for_sub_mails() {
+ let ctx = test_context();
+ let resource = Resource::plain_text("r9", &ctx);
+ let mail = Mail {
+ headers: headers!{
+ _From: ["random@this.is.no.mail"],
+ Subject: "hoho",
+ ContentType: "multipart/mixed"
+ }.unwrap(),
+ body: MailBody::MultipleBodies {
+ bodies: vec![
+ Mail {
+ headers: HeaderMap::new(),
+ body: MailBody::SingleBody { body: resource }
+ }
+ ],
+ hidden_text: Default::default()
+ }
+ };
+
+ let mail = mail.into_encodable_mail(ctx).wait().unwrap();
+
+ assert!(mail.headers().contains(_From));
+ assert!(mail.headers().contains(Subject));
+ assert!(mail.headers().contains(Date));
+ assert!(mail.headers().contains(ContentType));
+ assert_not!(mail.headers().contains(ContentTransferEncoding));
+
+ if let MailBody::MultipleBodies { ref bodies, ..} = mail.body {
+ let headers = bodies[0].headers();
+ assert_not!(headers.contains(Date));
+ } else {
+ unreachable!()
+ }
+ }
+
+ #[test]
+ fn runs_contextual_validators() {
+ let ctx = test_context();
+ let mail = Mail {
+ headers: headers!{
+ _From: ["random@this.is.no.mail", "u.p.s@s.p.u"],
+ Subject: "hoho"
+ }.unwrap(),
+ body: MailBody::SingleBody { body: Resource::plain_text("r9", &ctx) }
+ };
+
+ assert_err!(mail.into_encodable_mail(ctx).wait());
+ }
+
+ #[test]
+ fn checks_there_is_from() {
+ let ctx = test_context();
+ let mail = Mail {
+ headers: headers!{
+ Subject: "hoho"
+ }.unwrap(),
+ body: MailBody::SingleBody { body: Resource::plain_text("r9", &ctx) }
+ };
+
+ assert_err!(mail.into_encodable_mail(ctx).wait());
+ }
+
+ test!(does_not_override_date_if_set, {
+ let ctx = test_context();
+ let provided_date = Utc.ymd(1992, 5, 25).and_hms(23, 41, 12);
+ let mut mail = Mail::plain_text("r9", &ctx);
+ mail.insert_headers(headers! {
+ _From: ["random@this.is.no.mail"],
+ Subject: "hoho",
+ Date: provided_date.clone()
+ }?);
+
+ let enc_mail = assert_ok!(mail.into_encodable_mail(ctx).wait());
+ let used_date = enc_mail.headers()
+ .get_single(Date)
+ .unwrap()
+ .unwrap();
+
+ assert_eq!(&**used_date.body(), &provided_date);
+ });
+
+ }
+
+} \ No newline at end of file