melib/email/compose: set attachment status
Set Content-Disposition: attachment to, well, attachments.memfd
parent
e8f3b6aa24
commit
83bee279e6
|
@ -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]]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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__--
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue