melib: add attachment support in email/compose.rs

embed
Manos Pitsidianakis 2019-07-31 13:33:39 +03:00
parent 391e5b5d13
commit c87ed5012d
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 191 additions and 17 deletions

View File

@ -18,7 +18,7 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
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<AttachmentBuilder>) -> 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()),

View File

@ -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<String>,
body: String,
attachments: Vec<Attachment>,
attachments: Vec<AttachmentBuilder>,
}
impl Default for Draft {
@ -193,6 +194,14 @@ impl Draft {
&self.headers
}
pub fn attachments(&self) -> &Vec<AttachmentBuilder> {
&self.attachments
}
pub fn attachments_mut(&mut self) -> &mut Vec<AttachmentBuilder> {
&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<AttachmentBuilder>) {
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::<Vec<AttachmentBuilder>>(),
);
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());
}
}

View File

@ -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)
}