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,
ContentId
},
header_components::{
DateTime,
MediaType
},
error::{
HeaderValidationError,
}
};
use ::{
utils::SendBoxFuture,
mime::create_structured_random_boundary,
error::{
MailError,
OtherValidationError,
ResourceLoadingError
},
resource::*,
context::Context
};
#[derive(Clone, Debug)]
pub struct Mail {
headers: HeaderMap,
body: MailBody,
}
#[derive(Clone, Debug)]
pub enum MailBody {
SingleBody {
body: Resource
},
MultipleBodies {
bodies: Vec<Mail>,
hidden_text: SoftAsciiString
}
}
impl Mail {
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)
}
pub fn has_multipart_body(&self) -> bool {
self.body.is_multipart()
}
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()
}
}
}
pub fn new_singlepart_mail(body: Resource) -> Self {
let headers = HeaderMap::new();
Mail {
headers,
body: MailBody::SingleBody { body }
}
}
pub fn insert_header<H>(&mut self, header: Header<H>)
where H: HeaderKind
{
self.headers_mut().insert(header);
}
pub fn insert_headers(&mut self, headers: HeaderMap) {
self.headers_mut().insert_all(headers);
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
pub fn body(&self) -> &MailBody {
&self.body
}
pub fn body_mut(&mut self) -> &mut MailBody {
&mut self.body
}
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(())
}
pub fn into_encodable_mail<C: Context>(self, ctx: C) -> MailFuture<C> {
MailFuture::new(self, ctx)
}
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)
}
}
}
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 {
pub fn is_multipart(&self) -> bool {
use self::MailBody::*;
match *self {
SingleBody { .. } => false,
MultipleBodies { .. } => true
}
}
}
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)")
}
}
}
}
#[derive(Clone)]
pub struct EncodableMail(Mail);
impl EncodableMail {
pub fn encode(&self, encoder: &mut EncodingBuffer) -> Result<(), MailError> {
::encode::encode_mail(self, true, encoder)
}
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())
}
}
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);
mail.headers_mut().remove(ContentId);
}
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 } => {
let data = assume_encoded(body);
if let Some(Ok(disposition)) = headers.get_single_mut(ContentDisposition) {
let current_file_meta_mut = disposition.file_meta_mut();
current_file_meta_mut.replace_empty_fields_with(data.file_meta())
}
headers.insert(ContentId::body(data.content_id().clone()));
},
&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));
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);
});
}
}