diff --git a/melib/src/email/attachment_types.rs b/melib/src/email/attachment_types.rs index 9a0d0066e..307474d57 100644 --- a/melib/src/email/attachment_types.rs +++ b/melib/src/email/attachment_types.rs @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use crate::email::attachments::Attachment; +use crate::email::attachments::{Attachment, AttachmentBuilder}; use crate::email::parser::BytesExt; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::str; @@ -178,6 +178,42 @@ impl ContentType { } } + pub fn make_boundary(subattachments: &Vec) -> String { + use crate::email::compose::random::gen_boundary; + let mut boundary = "bzz_bzz__bzz__".to_string(); + let mut random_boundary = gen_boundary(); + + let mut loop_counter = 4096; + 'loo: loop { + let mut flag = true; + for sub in subattachments { + 'sub_loop: loop { + if sub.raw().find(random_boundary.as_bytes()).is_some() { + random_boundary = gen_boundary(); + flag = false; + } else { + break 'sub_loop; + } + } + } + if flag { + break 'loo; + } + loop_counter -= 1; + if loop_counter == 0 { + panic!("Can't generate randomness. This is a BUG"); + } + } + + boundary.extend(random_boundary.chars()); + /* rfc134 + * "The only mandatory parameter for the multipart Content-Type is the boundary parameter, + * which consists of 1 to 70 characters from a set of characters known to be very robust + * through email gateways, and NOT ending with white space"*/ + boundary.truncate(70); + boundary + } + pub fn name(&self) -> Option<&str> { match self { ContentType::Other { ref name, .. } => name.as_ref().map(|n| n.as_ref()), diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs index 883ab4c76..2b6f4ff4e 100644 --- a/melib/src/email/compose.rs +++ b/melib/src/email/compose.rs @@ -1,11 +1,12 @@ use super::*; use crate::backends::BackendOp; +use crate::email::attachments::AttachmentBuilder; use chrono::{DateTime, Local}; use data_encoding::BASE64_MIME; use std::str; -mod mime; -mod random; +pub mod mime; +pub mod random; //use self::mime::*; @@ -18,7 +19,7 @@ pub struct Draft { header_order: Vec, body: String, - attachments: Vec, + attachments: Vec, } impl Default for Draft { @@ -193,6 +194,14 @@ impl Draft { &self.headers } + pub fn attachments(&self) -> &Vec { + &self.attachments + } + + pub fn attachments_mut(&mut self) -> &mut Vec { + &mut self.attachments + } + pub fn body(&self) -> &str { &self.body } @@ -248,22 +257,32 @@ impl Draft { } ret.push_str("MIME-Version: 1.0\n"); - if self.body.is_ascii() { - ret.push('\n'); - ret.push_str(&self.body); + if !self.attachments.is_empty() { + let mut subattachments = Vec::with_capacity(self.attachments.len() + 1); + let attachments = std::mem::replace(&mut self.attachments, Vec::new()); + let mut body_attachment = AttachmentBuilder::default(); + body_attachment.set_raw(self.body.as_bytes().to_vec()); + subattachments.push(body_attachment); + subattachments.extend(attachments.into_iter()); + build_multipart(&mut ret, MultipartType::Mixed, subattachments); } else { - let content_type: ContentType = Default::default(); - let content_transfer_encoding: ContentTransferEncoding = - ContentTransferEncoding::Base64; + if self.body.is_ascii() { + ret.push('\n'); + ret.push_str(&self.body); + } else { + let content_type: ContentType = Default::default(); + let content_transfer_encoding: ContentTransferEncoding = + ContentTransferEncoding::Base64; - ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars()); - ret.extend( - format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(), - ); - ret.push('\n'); + ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars()); + ret.extend( + format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(), + ); + ret.push('\n'); - ret.push_str(&BASE64_MIME.encode(&self.body.as_bytes()).trim()); - ret.push('\n'); + ret.push_str(&BASE64_MIME.encode(&self.body.as_bytes()).trim()); + ret.push('\n'); + } } Ok(ret) @@ -289,9 +308,98 @@ fn ignore_header(header: &[u8]) -> bool { } } +fn build_multipart(ret: &mut String, kind: MultipartType, subattachments: Vec) { + use ContentType::*; + let boundary = ContentType::make_boundary(&subattachments); + ret.extend( + format!( + "Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\n", + kind, boundary + ) + .chars(), + ); + ret.push('\n'); + /* rfc1341 */ + ret.extend("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\n".chars()); + for sub in subattachments { + ret.push_str("--"); + ret.extend(boundary.chars()); + ret.push('\n'); + match sub.content_type { + ContentType::Text { + kind: crate::email::attachment_types::Text::Plain, + charset: Charset::UTF8, + } => { + ret.push('\n'); + ret.push_str(&String::from_utf8_lossy(sub.raw())); + ret.push('\n'); + } + Text { + ref kind, + charset: _, + } => { + ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", kind).chars()); + ret.push('\n'); + ret.push_str(&String::from_utf8_lossy(sub.raw())); + ret.push('\n'); + } + Multipart { + boundary: _boundary, + kind, + subattachments: subsubattachments, + } => { + build_multipart( + ret, + kind, + subsubattachments + .into_iter() + .map(|s| s.into()) + .collect::>(), + ); + ret.push('\n'); + } + MessageRfc822 | PGPSignature => { + ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", kind).chars()); + ret.push('\n'); + ret.push_str(&String::from_utf8_lossy(sub.raw())); + ret.push('\n'); + } + _ => { + let content_transfer_encoding: ContentTransferEncoding = + ContentTransferEncoding::Base64; + if let Some(name) = sub.content_type().name() { + ret.extend( + format!( + "Content-Type: {}; name=\"{}\"; charset=\"utf-8\"\n", + sub.content_type(), + name + ) + .chars(), + ); + } else { + ret.extend( + format!("Content-Type: {}; charset=\"utf-8\"\n", sub.content_type()) + .chars(), + ); + } + ret.extend( + format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(), + ); + ret.push('\n'); + ret.push_str(&BASE64_MIME.encode(sub.raw()).trim()); + ret.push('\n'); + } + } + } + ret.push_str("--"); + ret.extend(boundary.chars()); + ret.push_str("--\n"); +} + #[cfg(test)] mod tests { use super::*; + use std::io::Read; use std::str::FromStr; #[test] @@ -312,4 +420,26 @@ mod tests { default ); } + + #[test] + fn test_attachments() { + return; + let mut default = Draft::default(); + default.set_body("αδφαφσαφασ".to_string()); + + let mut file = std::fs::File::open("file path").unwrap(); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).unwrap(); + + let mut attachment = AttachmentBuilder::new(b""); + attachment + .set_raw(contents) + .set_content_type(ContentType::Other { + name: Some("images.jpeg".to_string()), + tag: b"image/jpeg".to_vec(), + }) + .set_content_transfer_encoding(ContentTransferEncoding::Base64); + default.attachments_mut().push(attachment); + println!("{}", default.finalise().unwrap()); + } } diff --git a/melib/src/email/compose/random.rs b/melib/src/email/compose/random.rs index 261c7cfa8..f0af4a68b 100644 --- a/melib/src/email/compose/random.rs +++ b/melib/src/email/compose/random.rs @@ -49,3 +49,11 @@ pub fn gen_message_id(fqdn: &str) -> String { format!("<{}.{}@{}>", clock, rand, fqdn) } + +pub fn gen_boundary() -> String { + let clock = base36(clock()); + let rand = base36(random_u64()); + let rand2 = base36(random_u64()); + + format!("{}{}{}", rand, clock, rand2) +}