diff --git a/melib/src/email/attachment_types.rs b/melib/src/email/attachment_types.rs
index 9a0d0066..307474d5 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 883ab4c7..2b6f4ff4 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;
-
- 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');
+ 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.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 261c7cfa..f0af4a68 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)
+}