melib/email/compose: set attachment status

Set Content-Disposition: attachment to, well, attachments.
memfd
Manos Pitsidianakis 2020-09-16 19:57:06 +03:00
parent e8f3b6aa24
commit 83bee279e6
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
9 changed files with 65 additions and 73 deletions

2
Cargo.lock generated
View File

@ -972,7 +972,6 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",
"xdg", "xdg",
"xdg-utils",
] ]
[[package]] [[package]]
@ -1006,6 +1005,7 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",
"xdg", "xdg",
"xdg-utils",
] ]
[[package]] [[package]]

View File

@ -39,7 +39,6 @@ serde_json = "1.0"
toml = { version = "0.5.6", features = ["preserve_order", ] } toml = { version = "0.5.6", features = ["preserve_order", ] }
indexmap = { version = "^1.5", features = ["serde-1", ] } indexmap = { version = "^1.5", features = ["serde-1", ] }
linkify = "0.4.0" linkify = "0.4.0"
xdg-utils = "0.3.0"
notify = "4.0.1" # >:c notify = "4.0.1" # >:c
notify-rust = { version = "^4", optional = true } notify-rust = { version = "^4", optional = true }
termion = "1.5.1" termion = "1.5.1"

View File

@ -48,6 +48,7 @@ smol = "0.1.18"
async-stream = "0.2.1" async-stream = "0.2.1"
base64 = { version = "0.12.3", optional = true } base64 = { version = "0.12.3", optional = true }
flate2 = { version = "1.0.16", optional = true } flate2 = { version = "1.0.16", optional = true }
xdg-utils = "0.3.0"
[features] [features]
default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"] default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"]

View File

@ -30,6 +30,7 @@ use std::ffi::OsStr;
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str; use std::str;
use xdg_utils::query_mime_info;
pub mod mime; pub mod mime;
pub mod random; pub mod random;
@ -244,21 +245,25 @@ impl Draft {
} }
for (k, v) in self.headers.deref() { for (k, v) in self.headers.deref() {
if v.is_ascii() { if v.is_ascii() {
ret.extend(format!("{}: {}\n", k, v).chars()); ret.extend(format!("{}: {}\r\n", k, v).chars());
} else { } else {
ret.extend(format!("{}: {}\n", k, mime::encode_header(v)).chars()); ret.extend(format!("{}: {}\r\n", k, mime::encode_header(v)).chars());
} }
} }
ret.push_str("MIME-Version: 1.0\n"); ret.push_str("MIME-Version: 1.0\r\n");
if self.attachments.is_empty() { if self.attachments.is_empty() {
let content_type: ContentType = Default::default(); let content_type: ContentType = Default::default();
let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit; let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit;
ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars()); ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\r\n", content_type).chars());
ret.extend( ret.extend(
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(), format!(
"Content-Transfer-Encoding: {}\r\n",
content_transfer_encoding
)
.chars(),
); );
ret.push('\n'); ret.push_str("\r\n");
ret.push_str(&self.body); ret.push_str(&self.body);
} else if self.body.is_empty() && self.attachments.len() == 1 { } else if self.body.is_empty() && self.attachments.len() == 1 {
let attachment = std::mem::replace(&mut self.attachments, Vec::new()).remove(0); let attachment = std::mem::replace(&mut self.attachments, Vec::new()).remove(0);
@ -283,18 +288,18 @@ fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentB
let boundary = ContentType::make_boundary(&parts); let boundary = ContentType::make_boundary(&parts);
ret.extend( ret.extend(
format!( format!(
"Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\n", "Content-Type: {}; charset=\"utf-8\"; boundary=\"{}\"\r\n",
kind, boundary kind, boundary
) )
.chars(), .chars(),
); );
ret.push('\n'); ret.push_str("\r\n");
/* rfc1341 */ /* rfc1341 */
ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\n"); ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\r\n");
for sub in parts { for sub in parts {
ret.push_str("--"); ret.push_str("--");
ret.push_str(&boundary); ret.push_str(&boundary);
ret.push('\n'); ret.push_str("\r\n");
print_attachment(ret, &kind, sub); print_attachment(ret, &kind, sub);
} }
ret.push_str("--"); ret.push_str("--");
@ -310,13 +315,13 @@ fn print_attachment(ret: &mut String, kind: &MultipartType, a: AttachmentBuilder
charset: Charset::UTF8, charset: Charset::UTF8,
parameters: ref v, parameters: ref v,
} if v.is_empty() => { } if v.is_empty() => {
ret.push('\n'); ret.push_str("\r\n");
ret.push_str(&String::from_utf8_lossy(a.raw())); ret.push_str(&String::from_utf8_lossy(a.raw()));
ret.push('\n'); ret.push_str("\r\n");
} }
Text { .. } => { Text { .. } => {
ret.push_str(&a.build().into_raw()); ret.push_str(&a.build().into_raw());
ret.push('\n'); ret.push_str("\r\n");
} }
Multipart { Multipart {
boundary: _boundary, boundary: _boundary,
@ -331,13 +336,14 @@ fn print_attachment(ret: &mut String, kind: &MultipartType, a: AttachmentBuilder
.map(|s| s.into()) .map(|s| s.into())
.collect::<Vec<AttachmentBuilder>>(), .collect::<Vec<AttachmentBuilder>>(),
); );
ret.push('\n'); ret.push_str("\r\n");
} }
MessageRfc822 | PGPSignature => { MessageRfc822 | PGPSignature => {
ret.push_str(&format!("Content-Type: {}; charset=\"utf-8\"\n", kind)); ret.push_str(&format!("Content-Type: {}; charset=\"utf-8\"\r\n", kind));
ret.push('\n'); ret.push_str("Content-Disposition: attachment\r\n");
ret.push_str("\r\n");
ret.push_str(&String::from_utf8_lossy(a.raw())); ret.push_str(&String::from_utf8_lossy(a.raw()));
ret.push('\n'); ret.push_str("\r\n");
} }
_ => { _ => {
let content_transfer_encoding: ContentTransferEncoding = let content_transfer_encoding: ContentTransferEncoding =
@ -345,7 +351,7 @@ fn print_attachment(ret: &mut String, kind: &MultipartType, a: AttachmentBuilder
if let Some(name) = a.content_type().name() { if let Some(name) = a.content_type().name() {
ret.extend( ret.extend(
format!( format!(
"Content-Type: {}; name=\"{}\"; charset=\"utf-8\"\n", "Content-Type: {}; name=\"{}\"; charset=\"utf-8\"\r\n",
a.content_type(), a.content_type(),
name name
) )
@ -353,15 +359,20 @@ fn print_attachment(ret: &mut String, kind: &MultipartType, a: AttachmentBuilder
); );
} else { } else {
ret.extend( ret.extend(
format!("Content-Type: {}; charset=\"utf-8\"\n", a.content_type()).chars(), format!("Content-Type: {}; charset=\"utf-8\"\r\n", a.content_type()).chars(),
); );
} }
ret.push_str("Content-Disposition: attachment\r\n");
ret.extend( ret.extend(
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(), format!(
"Content-Transfer-Encoding: {}\r\n",
content_transfer_encoding
)
.chars(),
); );
ret.push('\n'); ret.push_str("\r\n");
ret.push_str(&BASE64_MIME.encode(a.raw()).trim()); ret.push_str(&BASE64_MIME.encode(a.raw()).trim());
ret.push('\n'); ret.push_str("\r\n");
} }
} }
} }
@ -428,12 +439,17 @@ where
let mut contents = Vec::new(); let mut contents = Vec::new();
file.read_to_end(&mut contents)?; file.read_to_end(&mut contents)?;
let mut attachment = AttachmentBuilder::default(); let mut attachment = AttachmentBuilder::default();
attachment attachment
.set_raw(contents) .set_raw(contents)
.set_body_to_raw() .set_body_to_raw()
.set_content_type(ContentType::Other { .set_content_type(ContentType::Other {
name: path.file_name().map(|s| s.to_string_lossy().into()), name: path.file_name().map(|s| s.to_string_lossy().into()),
tag: b"application/octet-stream".to_vec(), tag: if let Ok(mime_type) = query_mime_info(&path) {
mime_type
} else {
b"application/octet-stream".to_vec()
},
}); });
Ok(attachment) Ok(attachment)

View File

@ -138,6 +138,7 @@ pub extern crate indexmap;
pub extern crate smallvec; pub extern crate smallvec;
pub extern crate smol; pub extern crate smol;
pub extern crate uuid; pub extern crate uuid;
pub extern crate xdg_utils;
pub use shellexpand::ShellExpandTrait; pub use shellexpand::ShellExpandTrait;
pub mod shellexpand { pub mod shellexpand {

View File

@ -30,7 +30,7 @@
use std::alloc::System; use std::alloc::System;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::path::PathBuf; use std::path::PathBuf;
extern crate xdg_utils;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate linkify; extern crate linkify;

View File

@ -32,7 +32,6 @@ use nix::sys::wait::WaitStatus;
use std::convert::TryInto; use std::convert::TryInto;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use xdg_utils::query_mime_info;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum Cursor { enum Cursor {
@ -1243,7 +1242,7 @@ impl Component for Composer {
.wait_with_output() .wait_with_output()
.expect("failed to launch command") .expect("failed to launch command")
.stdout; .stdout;
let mut attachment = let attachment =
match melib::email::compose::attachment_from_file(f.path()) { match melib::email::compose::attachment_from_file(f.path()) {
Ok(a) => a, Ok(a) => a,
Err(err) => { Err(err) => {
@ -1258,14 +1257,6 @@ impl Component for Composer {
return true; return true;
} }
}; };
if let Ok(mime_type) = query_mime_info(f.path()) {
match attachment.content_type {
ContentType::Other { ref mut tag, .. } => {
*tag = mime_type;
}
_ => {}
}
}
self.draft.attachments_mut().push(attachment); self.draft.attachments_mut().push(attachment);
self.dirty = true; self.dirty = true;
return true; return true;
@ -1281,7 +1272,7 @@ impl Component for Composer {
} }
} }
Action::Compose(ComposeAction::AddAttachment(ref path)) => { Action::Compose(ComposeAction::AddAttachment(ref path)) => {
let mut attachment = match melib::email::compose::attachment_from_file(path) { let attachment = match melib::email::compose::attachment_from_file(path) {
Ok(a) => a, Ok(a) => a,
Err(err) => { Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
@ -1293,14 +1284,6 @@ impl Component for Composer {
return true; return true;
} }
}; };
if let Ok(mime_type) = query_mime_info(path) {
match attachment.content_type {
ContentType::Other { ref mut tag, .. } => {
*tag = mime_type;
}
_ => {}
}
}
self.draft.attachments_mut().push(attachment); self.draft.attachments_mut().push(attachment);
self.dirty = true; self.dirty = true;
return true; return true;

View File

@ -1,19 +1,20 @@
Subject: Subject:
From: From:
To: To:
Cc: Cc:
Bcc: Bcc:
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: multipart/mixed; charset="utf-8"; boundary="bzz_bzz__bzz__" Content-Type: multipart/mixed; charset="utf-8"; boundary="bzz_bzz__bzz__"
This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly. This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.
--bzz_bzz__bzz__ --bzz_bzz__bzz__
hello world. hello world.
--bzz_bzz__bzz__ --bzz_bzz__bzz__
Content-Type: image/gif; name="test_image.gif"; charset="utf-8" Content-Type: image/gif; name="test_image.gif"; charset="utf-8"
Content-Transfer-Encoding: base64 Content-Disposition: attachment
Content-Transfer-Encoding: base64
R0lGODdhKAAXAOfZAAABzAADzQAEzgQFtBEAxAAGxBcAxwALvRcFwAAPwBcLugATuQEUuxoNuxYQ R0lGODdhKAAXAOfZAAABzAADzQAEzgQFtBEAxAAGxBcAxwALvRcFwAAPwBcLugATuQEUuxoNuxYQ
sxwOvAYVvBsStSAVtx8YsRUcuhwhth4iuCQsyDAwuDc1vTc3uDg4uT85rkc9ukJBvENCvURGukdF sxwOvAYVvBsStSAVtx8YsRUcuhwhth4iuCQsyDAwuDc1vTc3uDg4uT85rkc9ukJBvENCvURGukdF
wUVKt0hLuUxPvVZSvFlYu1hbt2BZuFxdul5joGhqlnNuf3FvlnBvwXJyt3Jxw3N0oXx1gH12gV99 wUVKt0hLuUxPvVZSvFlYu1hbt2BZuFxdul5joGhqlnNuf3FvlnBvwXJyt3Jxw3N0oXx1gH12gV99
@ -36,5 +37,5 @@ Ob+jG0YVRBErUrOiiGJ8KxgtYsh27xWL/tswnTtEbsiRVYdJNMHk4yOGhswGjR88UKjQ9Ey+/8TL
XKKGGn7Akph/8XX2WDTTcAYfguVt9hhrEPqmzIOJ3VUheb48WJiHG6amC4i+WVJKKCimqGIoYxyj XKKGGn7Akph/8XX2WDTTcAYfguVt9hhrEPqmzIOJ3VUheb48WJiHG6amC4i+WVJKKCimqGIoYxyj
WWK8kKjaJ9bA18sxvXjYhourmbbMMrjI+OIn1QymDCVXANGFK4S1gQw0PxozzC+33FLLKUJq9gk1 WWK8kKjaJ9bA18sxvXjYhourmbbMMrjI+OIn1QymDCVXANGFK4S1gQw0PxozzC+33FLLKUJq9gk1
gyWDhyNwrMLkYGUEM4wvuLRiCiieXIJJJVlmJskcZ9TZRht1lnFGGmTMkMoonVQSSSOFAGJHHI0w gyWDhyNwrMLkYGUEM4wvuLRiCiieXIJJJVlmJskcZ9TZRht1lnFGGmTMkMoonVQSSSOFAGJHHI0w
ouiijDaaCCGQRgrpH3q4QYYXWDihxBE+7KCDDjnUIEVAADs= ouiijDaaCCGQRgrpH3q4QYYXWDihxBE+7KCDDjnUIEVAADs=
--bzz_bzz__bzz__-- --bzz_bzz__bzz__--

View File

@ -1,20 +1,11 @@
use melib; use melib;
use melib::email::Draft; use melib::email::Draft;
use xdg_utils::query_mime_info;
#[test] #[test]
fn build_draft() { fn build_draft() {
let mut new_draft = Draft::default(); let mut new_draft = Draft::default();
let mut attachment = melib::email::attachment_from_file(&"./tests/test_image.gif") let attachment = melib::email::attachment_from_file(&"./tests/test_image.gif")
.expect("Could not open test_image.gif."); .expect("Could not open test_image.gif.");
if let Ok(mime_type) = query_mime_info("./tests/test_image.gif") {
match attachment.content_type {
melib::email::attachment_types::ContentType::Other { ref mut tag, .. } => {
*tag = mime_type;
}
_ => {}
}
}
new_draft.headers_mut().remove("User-Agent"); new_draft.headers_mut().remove("User-Agent");
new_draft.headers_mut().remove("Date"); new_draft.headers_mut().remove("Date");
@ -27,5 +18,5 @@ fn build_draft() {
let boundary_str = &boundary["bzz_bzz__bzz__".len()..]; let boundary_str = &boundary["bzz_bzz__bzz__".len()..];
let raw = raw.replace(boundary_str, ""); let raw = raw.replace(boundary_str, "");
assert_eq!(include_str!("generated.mail"), &raw); assert_eq!(include_str!("generated_email.eml"), &raw);
} }