2020-01-30 00:25:51 +02:00
|
|
|
/*
|
|
|
|
* meli - melib crate.
|
|
|
|
*
|
|
|
|
* Copyright 2017-2020 Manos Pitsidianakis
|
|
|
|
*
|
|
|
|
* This file is part of meli.
|
|
|
|
*
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2020-09-24 16:51:51 +03:00
|
|
|
/*! Compose a `Draft`, with MIME and attachment support */
|
2018-08-29 14:11:59 +03:00
|
|
|
use super::*;
|
2020-09-09 14:24:30 +03:00
|
|
|
use crate::email::attachment_types::{
|
|
|
|
Charset, ContentTransferEncoding, ContentType, MultipartType,
|
|
|
|
};
|
2022-09-11 01:11:33 +03:00
|
|
|
use crate::email::attachments::AttachmentBuilder;
|
2019-09-28 11:53:39 +03:00
|
|
|
use crate::shellexpand::ShellExpandTrait;
|
2018-08-29 14:11:59 +03:00
|
|
|
use data_encoding::BASE64_MIME;
|
2019-08-01 12:14:45 +03:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::io::Read;
|
2019-09-28 11:53:39 +03:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-09-02 09:50:07 +03:00
|
|
|
use std::str::FromStr;
|
2020-09-16 19:57:06 +03:00
|
|
|
use xdg_utils::query_mime_info;
|
2018-08-29 14:11:59 +03:00
|
|
|
|
2019-07-31 13:33:39 +03:00
|
|
|
pub mod mime;
|
|
|
|
pub mod random;
|
2019-02-15 19:21:58 +02:00
|
|
|
|
2019-03-03 22:11:15 +02:00
|
|
|
//use self::mime::*;
|
2018-08-29 14:11:59 +03:00
|
|
|
|
|
|
|
use super::parser;
|
|
|
|
|
2019-10-20 11:17:54 +03:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
2018-08-29 14:11:59 +03:00
|
|
|
pub struct Draft {
|
2020-08-25 12:25:26 +03:00
|
|
|
pub headers: HeaderMap,
|
2019-09-28 10:46:49 +03:00
|
|
|
pub body: String,
|
2022-09-02 09:50:07 +03:00
|
|
|
pub wrap_header_preamble: Option<(String, String)>,
|
2018-08-29 14:11:59 +03:00
|
|
|
|
2019-09-28 10:46:49 +03:00
|
|
|
pub attachments: Vec<AttachmentBuilder>,
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Draft {
|
|
|
|
fn default() -> Self {
|
2020-08-25 12:25:26 +03:00
|
|
|
let mut headers = HeaderMap::default();
|
2020-08-18 12:20:23 +03:00
|
|
|
headers.insert(
|
2020-08-25 12:25:26 +03:00
|
|
|
HeaderName::new_unchecked("Date"),
|
2021-07-19 03:52:09 +03:00
|
|
|
crate::datetime::timestamp_to_string(
|
|
|
|
crate::datetime::now(),
|
|
|
|
Some(crate::datetime::RFC822_DATE),
|
|
|
|
true,
|
|
|
|
),
|
2020-08-18 12:20:23 +03:00
|
|
|
);
|
2020-08-25 12:25:26 +03:00
|
|
|
headers.insert(HeaderName::new_unchecked("From"), "".into());
|
|
|
|
headers.insert(HeaderName::new_unchecked("To"), "".into());
|
|
|
|
headers.insert(HeaderName::new_unchecked("Cc"), "".into());
|
|
|
|
headers.insert(HeaderName::new_unchecked("Bcc"), "".into());
|
|
|
|
headers.insert(HeaderName::new_unchecked("Subject"), "".into());
|
2018-08-29 14:11:59 +03:00
|
|
|
|
|
|
|
Draft {
|
|
|
|
headers,
|
|
|
|
body: String::new(),
|
2022-09-02 09:50:07 +03:00
|
|
|
wrap_header_preamble: None,
|
2018-08-29 14:11:59 +03:00
|
|
|
|
|
|
|
attachments: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
impl FromStr for Draft {
|
2022-12-08 22:20:05 +02:00
|
|
|
type Err = Error;
|
2018-09-12 15:10:19 +03:00
|
|
|
fn from_str(s: &str) -> Result<Self> {
|
|
|
|
if s.is_empty() {
|
2022-12-08 22:20:05 +02:00
|
|
|
return Err(Error::new("Empty input in Draft::from_str"));
|
2018-09-12 15:10:19 +03:00
|
|
|
}
|
|
|
|
|
2020-06-06 19:38:20 +03:00
|
|
|
let (headers, _) = parser::mail(s.as_bytes())?;
|
2018-09-12 15:10:19 +03:00
|
|
|
let mut ret = Draft::default();
|
|
|
|
|
|
|
|
for (k, v) in headers {
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.headers
|
|
|
|
.insert(k.try_into()?, String::from_utf8(v.to_vec())?);
|
2019-04-14 17:26:33 +03:00
|
|
|
}
|
2022-12-08 20:43:52 +02:00
|
|
|
let body = Envelope::new(EnvelopeHash::default()).body_bytes(s.as_bytes());
|
2018-09-12 15:10:19 +03:00
|
|
|
|
2022-09-11 01:11:33 +03:00
|
|
|
ret.body = String::from_utf8(body.decode(Default::default()))?;
|
2018-09-12 15:10:19 +03:00
|
|
|
|
|
|
|
Ok(ret)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 14:11:59 +03:00
|
|
|
impl Draft {
|
2020-08-25 12:25:26 +03:00
|
|
|
pub fn edit(envelope: &Envelope, bytes: &[u8]) -> Result<Self> {
|
2019-03-26 15:26:09 +02:00
|
|
|
let mut ret = Draft::default();
|
2022-09-18 00:09:23 +03:00
|
|
|
for (k, v) in envelope.headers(bytes).unwrap_or_else(|_| Vec::new()) {
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.headers.insert(k.try_into()?, v.into());
|
2019-03-26 15:26:09 +02:00
|
|
|
}
|
2019-03-26 19:53:39 +02:00
|
|
|
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.body = envelope.body_bytes(bytes).text();
|
2019-03-26 15:26:09 +02:00
|
|
|
|
2019-09-27 13:18:59 +03:00
|
|
|
Ok(ret)
|
2019-03-26 15:26:09 +02:00
|
|
|
}
|
2019-09-27 13:18:59 +03:00
|
|
|
|
2020-08-18 12:20:23 +03:00
|
|
|
pub fn set_header(&mut self, header: &str, value: String) -> &mut Self {
|
2020-08-25 12:25:26 +03:00
|
|
|
self.headers
|
|
|
|
.insert(HeaderName::new_unchecked(header), value);
|
2020-08-18 12:20:23 +03:00
|
|
|
self
|
2019-06-18 21:58:55 +03:00
|
|
|
}
|
2020-08-18 12:20:23 +03:00
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
pub fn set_wrap_header_preamble(&mut self, value: Option<(String, String)>) -> &mut Self {
|
|
|
|
self.wrap_header_preamble = value;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update(&mut self, value: &str) -> Result<bool> {
|
|
|
|
let mut value: std::borrow::Cow<'_, str> = value.into();
|
|
|
|
if let Some((pre, post)) = self.wrap_header_preamble.as_ref() {
|
|
|
|
let mut s = value.as_ref();
|
|
|
|
s = s.strip_prefix(pre).unwrap_or(s);
|
|
|
|
s = s.strip_prefix('\n').unwrap_or(s);
|
|
|
|
|
|
|
|
if let Some(pos) = s.find(post) {
|
|
|
|
let mut headers = &s[..pos];
|
|
|
|
headers = headers.strip_suffix(post).unwrap_or(headers);
|
2022-09-03 17:47:58 +03:00
|
|
|
if headers.ends_with('\n') {
|
|
|
|
headers = &headers[..headers.len() - 1];
|
|
|
|
}
|
2022-09-02 09:50:07 +03:00
|
|
|
value = format!(
|
|
|
|
"{headers}{body}",
|
|
|
|
headers = headers,
|
|
|
|
body = &s[pos + post.len()..]
|
|
|
|
)
|
|
|
|
.into();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let new = Draft::from_str(value.as_ref())?;
|
|
|
|
let changes: bool = self.headers != new.headers || self.body != new.body;
|
|
|
|
self.headers = new.headers;
|
|
|
|
self.body = new.body;
|
|
|
|
Ok(changes)
|
|
|
|
}
|
|
|
|
|
2020-08-18 12:20:23 +03:00
|
|
|
pub fn new_reply(envelope: &Envelope, bytes: &[u8], reply_to_all: bool) -> Self {
|
2018-09-04 15:00:23 +03:00
|
|
|
let mut ret = Draft::default();
|
|
|
|
ret.headers_mut().insert(
|
2020-08-25 12:25:26 +03:00
|
|
|
HeaderName::new_unchecked("References"),
|
2018-09-04 15:00:23 +03:00
|
|
|
format!(
|
|
|
|
"{} {}",
|
|
|
|
envelope
|
|
|
|
.references()
|
|
|
|
.iter()
|
|
|
|
.fold(String::new(), |mut acc, x| {
|
|
|
|
if !acc.is_empty() {
|
|
|
|
acc.push(' ');
|
|
|
|
}
|
|
|
|
acc.push_str(&x.to_string());
|
|
|
|
acc
|
|
|
|
}),
|
2018-09-12 15:10:19 +03:00
|
|
|
envelope.message_id_display()
|
2018-09-04 15:00:23 +03:00
|
|
|
),
|
|
|
|
);
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.headers_mut().insert(
|
|
|
|
HeaderName::new_unchecked("In-Reply-To"),
|
|
|
|
envelope.message_id_display().into(),
|
|
|
|
);
|
2020-08-18 12:20:23 +03:00
|
|
|
// "Mail-Followup-To/(To+Cc+(Mail-Reply-To/Reply-To/From)) for follow-up,
|
|
|
|
// Mail-Reply-To/Reply-To/From for reply-to-author."
|
|
|
|
// source: https://cr.yp.to/proto/replyto.html
|
|
|
|
if reply_to_all {
|
|
|
|
if let Some(reply_to) = envelope.other_headers().get("Mail-Followup-To") {
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.headers_mut()
|
|
|
|
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
|
2020-08-18 12:20:23 +03:00
|
|
|
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
|
|
|
ret.headers_mut()
|
2020-08-25 12:25:26 +03:00
|
|
|
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
|
|
|
|
} else {
|
|
|
|
ret.headers_mut().insert(
|
|
|
|
HeaderName::new_unchecked("To"),
|
|
|
|
envelope.field_from_to_string(),
|
|
|
|
);
|
2020-08-18 12:20:23 +03:00
|
|
|
}
|
2021-09-12 14:33:00 +03:00
|
|
|
// FIXME: add To/Cc
|
|
|
|
} else if let Some(reply_to) = envelope.other_headers().get("Mail-Reply-To") {
|
|
|
|
ret.headers_mut()
|
|
|
|
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
|
|
|
|
} else if let Some(reply_to) = envelope.other_headers().get("Reply-To") {
|
|
|
|
ret.headers_mut()
|
|
|
|
.insert(HeaderName::new_unchecked("To"), reply_to.to_string());
|
|
|
|
} else {
|
|
|
|
ret.headers_mut().insert(
|
|
|
|
HeaderName::new_unchecked("To"),
|
|
|
|
envelope.field_from_to_string(),
|
|
|
|
);
|
2019-06-24 23:59:45 +03:00
|
|
|
}
|
2020-08-25 12:25:26 +03:00
|
|
|
ret.headers_mut().insert(
|
|
|
|
HeaderName::new_unchecked("Cc"),
|
|
|
|
envelope.field_cc_to_string(),
|
|
|
|
);
|
2018-09-04 15:00:23 +03:00
|
|
|
let body = envelope.body_bytes(bytes);
|
|
|
|
ret.body = {
|
2022-09-11 01:11:33 +03:00
|
|
|
let reply_body_bytes = body.decode_rec(Default::default());
|
2018-09-04 15:00:23 +03:00
|
|
|
let reply_body = String::from_utf8_lossy(&reply_body_bytes);
|
|
|
|
let lines: Vec<&str> = reply_body.lines().collect();
|
2019-11-16 19:59:47 +02:00
|
|
|
let mut ret = format!(
|
|
|
|
"On {} {} wrote:\n",
|
|
|
|
envelope.date_as_str(),
|
2020-08-25 12:25:26 +03:00
|
|
|
&ret.headers()["To"]
|
2019-11-16 19:59:47 +02:00
|
|
|
);
|
2018-09-04 15:00:23 +03:00
|
|
|
for l in lines {
|
|
|
|
ret.push('>');
|
2019-11-16 19:59:47 +02:00
|
|
|
ret.push_str(l);
|
2018-09-04 15:00:23 +03:00
|
|
|
ret.push('\n');
|
|
|
|
}
|
|
|
|
ret.pop();
|
|
|
|
ret
|
|
|
|
};
|
|
|
|
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
2020-08-25 12:25:26 +03:00
|
|
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
2018-08-30 15:54:30 +03:00
|
|
|
&mut self.headers
|
|
|
|
}
|
2018-09-04 15:00:23 +03:00
|
|
|
|
2020-08-25 12:25:26 +03:00
|
|
|
pub fn headers(&self) -> &HeaderMap {
|
2018-08-29 19:09:51 +03:00
|
|
|
&self.headers
|
|
|
|
}
|
|
|
|
|
2019-07-31 13:33:39 +03:00
|
|
|
pub fn attachments(&self) -> &Vec<AttachmentBuilder> {
|
|
|
|
&self.attachments
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn attachments_mut(&mut self) -> &mut Vec<AttachmentBuilder> {
|
|
|
|
&mut self.attachments
|
|
|
|
}
|
|
|
|
|
2018-08-29 19:09:51 +03:00
|
|
|
pub fn body(&self) -> &str {
|
|
|
|
&self.body
|
|
|
|
}
|
|
|
|
|
2020-08-18 12:20:23 +03:00
|
|
|
pub fn set_body(&mut self, s: String) -> &mut Self {
|
2018-08-29 14:11:59 +03:00
|
|
|
self.body = s;
|
2020-08-18 12:20:23 +03:00
|
|
|
self
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
pub fn to_edit_string(&self) -> String {
|
2018-08-29 14:11:59 +03:00
|
|
|
let mut ret = String::new();
|
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
if let Some((pre, _)) = self.wrap_header_preamble.as_ref() {
|
|
|
|
if !pre.is_empty() {
|
2022-09-18 00:09:23 +03:00
|
|
|
ret.push_str(pre);
|
2022-09-02 09:50:07 +03:00
|
|
|
if !pre.ends_with('\n') {
|
|
|
|
ret.push('\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-25 12:25:26 +03:00
|
|
|
for (k, v) in self.headers.deref() {
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!("{}: {}\n", k, v));
|
2018-08-29 19:09:51 +03:00
|
|
|
}
|
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
if let Some((_, post)) = self.wrap_header_preamble.as_ref() {
|
|
|
|
if !post.is_empty() {
|
2022-09-03 17:47:58 +03:00
|
|
|
if !post.starts_with('\n') && !ret.ends_with('\n') {
|
2022-09-02 09:50:07 +03:00
|
|
|
ret.push('\n');
|
|
|
|
}
|
2022-09-18 00:09:23 +03:00
|
|
|
ret.push_str(post);
|
2022-09-03 17:47:58 +03:00
|
|
|
ret.push('\n');
|
2022-09-02 09:50:07 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-15 19:21:58 +02:00
|
|
|
ret.push('\n');
|
|
|
|
ret.push_str(&self.body);
|
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
ret
|
2019-02-15 19:21:58 +02:00
|
|
|
}
|
2019-03-29 19:37:02 +02:00
|
|
|
|
2019-04-14 23:05:29 +03:00
|
|
|
pub fn finalise(mut self) -> Result<String> {
|
2019-02-15 19:21:58 +02:00
|
|
|
let mut ret = String::new();
|
2018-08-29 19:09:51 +03:00
|
|
|
|
2019-04-14 23:05:29 +03:00
|
|
|
if self.headers.contains_key("From") && !self.headers.contains_key("Message-ID") {
|
2020-06-06 19:38:20 +03:00
|
|
|
if let Ok((_, addr)) = super::parser::address::mailbox(self.headers["From"].as_bytes())
|
2019-04-14 23:05:29 +03:00
|
|
|
{
|
|
|
|
if let Some(fqdn) = addr.get_fqdn() {
|
2020-08-25 12:25:26 +03:00
|
|
|
self.headers.insert(
|
|
|
|
HeaderName::new_unchecked("Message-ID"),
|
|
|
|
random::gen_message_id(&fqdn),
|
|
|
|
);
|
2019-04-14 23:05:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-25 12:25:26 +03:00
|
|
|
for (k, v) in self.headers.deref() {
|
2019-04-14 23:05:29 +03:00
|
|
|
if v.is_ascii() {
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!("{}: {}\r\n", k, v));
|
2019-04-14 23:05:29 +03:00
|
|
|
} else {
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!("{}: {}\r\n", k, mime::encode_header(v)));
|
2019-04-14 23:05:29 +03:00
|
|
|
}
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("MIME-Version: 1.0\r\n");
|
2018-08-29 14:11:59 +03:00
|
|
|
|
2019-09-28 10:46:49 +03:00
|
|
|
if self.attachments.is_empty() {
|
|
|
|
let content_type: ContentType = Default::default();
|
|
|
|
let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit;
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Type: {}; charset=\"utf-8\"\r\n",
|
|
|
|
content_type
|
|
|
|
));
|
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Transfer-Encoding: {}\r\n",
|
|
|
|
content_transfer_encoding
|
|
|
|
));
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("\r\n");
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in self.body.lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2019-12-06 16:34:31 +02:00
|
|
|
} else if self.body.is_empty() && self.attachments.len() == 1 {
|
2022-09-18 00:09:23 +03:00
|
|
|
let attachment = std::mem::take(&mut self.attachments).remove(0);
|
2020-10-08 16:42:34 +03:00
|
|
|
print_attachment(&mut ret, attachment);
|
2019-09-28 10:46:49 +03:00
|
|
|
} else {
|
2019-09-25 22:00:30 +03:00
|
|
|
let mut parts = Vec::with_capacity(self.attachments.len() + 1);
|
2022-09-18 00:09:23 +03:00
|
|
|
let attachments = std::mem::take(&mut self.attachments);
|
2020-04-04 19:17:16 +03:00
|
|
|
if !self.body.is_empty() {
|
|
|
|
let mut body_attachment = AttachmentBuilder::default();
|
|
|
|
body_attachment.set_raw(self.body.as_bytes().to_vec());
|
|
|
|
parts.push(body_attachment);
|
|
|
|
}
|
2019-09-25 22:00:30 +03:00
|
|
|
parts.extend(attachments.into_iter());
|
|
|
|
build_multipart(&mut ret, MultipartType::Mixed, parts);
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(ret)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-25 22:00:30 +03:00
|
|
|
fn build_multipart(ret: &mut String, kind: MultipartType, parts: Vec<AttachmentBuilder>) {
|
|
|
|
let boundary = ContentType::make_boundary(&parts);
|
2020-10-09 11:51:34 +03:00
|
|
|
ret.push_str(&format!(
|
|
|
|
r#"Content-Type: {}; charset="utf-8"; boundary="{}""#,
|
|
|
|
kind, boundary
|
|
|
|
));
|
|
|
|
if kind == MultipartType::Encrypted {
|
|
|
|
ret.push_str(r#"; protocol="application/pgp-encrypted""#);
|
|
|
|
}
|
|
|
|
ret.push_str("\r\n\r\n");
|
2019-07-31 13:33:39 +03:00
|
|
|
/* rfc1341 */
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("This is a MIME formatted message with attachments. Use a MIME-compliant client to view it properly.\r\n");
|
2019-09-25 22:00:30 +03:00
|
|
|
for sub in parts {
|
2019-07-31 13:33:39 +03:00
|
|
|
ret.push_str("--");
|
2020-07-05 15:28:55 +03:00
|
|
|
ret.push_str(&boundary);
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("\r\n");
|
2020-10-08 16:42:34 +03:00
|
|
|
print_attachment(ret, sub);
|
2019-12-06 16:34:31 +02:00
|
|
|
}
|
|
|
|
ret.push_str("--");
|
2020-07-05 15:28:55 +03:00
|
|
|
ret.push_str(&boundary);
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str("--\r\n");
|
2019-12-06 16:34:31 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 16:42:34 +03:00
|
|
|
fn print_attachment(ret: &mut String, a: AttachmentBuilder) {
|
2019-12-06 16:34:31 +02:00
|
|
|
use ContentType::*;
|
|
|
|
match a.content_type {
|
|
|
|
ContentType::Text {
|
|
|
|
kind: crate::email::attachment_types::Text::Plain,
|
|
|
|
charset: Charset::UTF8,
|
|
|
|
parameters: ref v,
|
|
|
|
} if v.is_empty() => {
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("\r\n");
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in String::from_utf8_lossy(a.raw()).lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2019-12-06 16:34:31 +02:00
|
|
|
}
|
|
|
|
Text { .. } => {
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in a.build().into_raw().lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2019-12-06 16:34:31 +02:00
|
|
|
}
|
|
|
|
Multipart {
|
2020-10-09 11:51:34 +03:00
|
|
|
boundary: _,
|
2019-12-06 16:34:31 +02:00
|
|
|
kind,
|
2020-10-09 11:51:34 +03:00
|
|
|
parts,
|
2019-12-06 16:34:31 +02:00
|
|
|
} => {
|
|
|
|
build_multipart(
|
|
|
|
ret,
|
2019-07-31 13:33:39 +03:00
|
|
|
kind,
|
2020-10-09 11:51:34 +03:00
|
|
|
parts
|
2019-12-06 16:34:31 +02:00
|
|
|
.into_iter()
|
|
|
|
.map(|s| s.into())
|
|
|
|
.collect::<Vec<AttachmentBuilder>>(),
|
|
|
|
);
|
|
|
|
}
|
2020-10-08 16:42:34 +03:00
|
|
|
MessageRfc822 => {
|
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Type: {}; charset=\"utf-8\"\r\n",
|
|
|
|
a.content_type
|
|
|
|
));
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("Content-Disposition: attachment\r\n");
|
|
|
|
ret.push_str("\r\n");
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in String::from_utf8_lossy(a.raw()).lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2019-12-06 16:34:31 +02:00
|
|
|
}
|
2020-10-08 16:42:34 +03:00
|
|
|
PGPSignature => {
|
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Type: {}; charset=\"utf-8\"; name=\"signature.asc\"\r\n",
|
|
|
|
a.content_type
|
|
|
|
));
|
|
|
|
ret.push_str("Content-Description: Digital signature\r\n");
|
|
|
|
ret.push_str("Content-Disposition: inline\r\n");
|
|
|
|
ret.push_str("\r\n");
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in String::from_utf8_lossy(a.raw()).lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2020-10-08 16:42:34 +03:00
|
|
|
}
|
2019-12-06 16:34:31 +02:00
|
|
|
_ => {
|
2020-10-09 11:51:34 +03:00
|
|
|
let content_transfer_encoding: ContentTransferEncoding = if a.raw().is_ascii() {
|
|
|
|
ContentTransferEncoding::_8Bit
|
|
|
|
} else {
|
|
|
|
ContentTransferEncoding::Base64
|
|
|
|
};
|
2019-12-06 16:34:31 +02:00
|
|
|
if let Some(name) = a.content_type().name() {
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Type: {}; name=\"{}\"; charset=\"utf-8\"\r\n",
|
|
|
|
a.content_type(),
|
|
|
|
name
|
|
|
|
));
|
2019-12-06 16:34:31 +02:00
|
|
|
} else {
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Type: {}; charset=\"utf-8\"\r\n",
|
|
|
|
a.content_type()
|
|
|
|
));
|
2019-07-31 13:33:39 +03:00
|
|
|
}
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("Content-Disposition: attachment\r\n");
|
2020-11-10 17:24:04 +02:00
|
|
|
ret.push_str(&format!(
|
|
|
|
"Content-Transfer-Encoding: {}\r\n",
|
|
|
|
content_transfer_encoding
|
|
|
|
));
|
2020-09-16 19:57:06 +03:00
|
|
|
ret.push_str("\r\n");
|
2020-10-09 11:51:34 +03:00
|
|
|
if content_transfer_encoding == ContentTransferEncoding::Base64 {
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in BASE64_MIME.encode(a.raw()).trim().lines() {
|
|
|
|
ret.push_str(line);
|
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
2020-10-09 11:51:34 +03:00
|
|
|
} else {
|
2020-11-10 17:24:04 +02:00
|
|
|
for line in String::from_utf8_lossy(a.raw()).lines() {
|
|
|
|
ret.push_str(line);
|
2020-10-09 11:51:34 +03:00
|
|
|
ret.push_str("\r\n");
|
|
|
|
}
|
|
|
|
}
|
2019-07-31 13:33:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-29 14:11:59 +03:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-05-08 18:00:35 +03:00
|
|
|
use std::str::FromStr;
|
2018-08-29 14:11:59 +03:00
|
|
|
|
|
|
|
#[test]
|
2022-09-02 09:50:07 +03:00
|
|
|
fn test_new_draft() {
|
2018-08-29 14:11:59 +03:00
|
|
|
let mut default = Draft::default();
|
2022-09-02 09:50:07 +03:00
|
|
|
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
2018-08-29 14:11:59 +03:00
|
|
|
default.set_body("αδφαφσαφασ".to_string());
|
2022-09-02 09:50:07 +03:00
|
|
|
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
2018-08-29 14:11:59 +03:00
|
|
|
default.set_body("ascii only".to_string());
|
2022-09-02 09:50:07 +03:00
|
|
|
assert_eq!(Draft::from_str(&default.to_edit_string()).unwrap(), default);
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
2019-07-31 13:33:39 +03:00
|
|
|
|
2022-09-02 09:50:07 +03:00
|
|
|
#[test]
|
|
|
|
fn test_draft_update() {
|
|
|
|
let mut default = Draft::default();
|
|
|
|
default
|
|
|
|
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())))
|
|
|
|
.set_body("αδφαφσαφασ".to_string())
|
|
|
|
.set_header("Subject", "test_update()".into())
|
|
|
|
.set_header("Date", "Sun, 16 Jun 2013 17:56:45 +0200".into());
|
|
|
|
|
|
|
|
let original = default.clone();
|
|
|
|
let s = default.to_edit_string();
|
2022-09-03 17:47:58 +03:00
|
|
|
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nαδφαφσαφασ");
|
2022-09-02 09:50:07 +03:00
|
|
|
assert!(!default.update(&s).unwrap());
|
|
|
|
assert_eq!(&original, &default);
|
|
|
|
|
|
|
|
default.set_wrap_header_preamble(Some(("".to_string(), "".to_string())));
|
|
|
|
let original = default.clone();
|
|
|
|
let s = default.to_edit_string();
|
|
|
|
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
|
|
|
|
assert!(!default.update(&s).unwrap());
|
|
|
|
assert_eq!(&original, &default);
|
|
|
|
|
|
|
|
default.set_wrap_header_preamble(None);
|
|
|
|
let original = default.clone();
|
|
|
|
let s = default.to_edit_string();
|
|
|
|
assert_eq!(s, "Date: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n\nαδφαφσαφασ");
|
|
|
|
assert!(!default.update(&s).unwrap());
|
|
|
|
assert_eq!(&original, &default);
|
|
|
|
|
|
|
|
default.set_wrap_header_preamble(Some((
|
|
|
|
"{-\n\n\n===========".to_string(),
|
|
|
|
"</mixed>".to_string(),
|
|
|
|
)));
|
|
|
|
let original = default.clone();
|
|
|
|
let s = default.to_edit_string();
|
2022-09-03 17:47:58 +03:00
|
|
|
assert_eq!(s, "{-\n\n\n===========\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n</mixed>\n\nαδφαφσαφασ");
|
2022-09-02 09:50:07 +03:00
|
|
|
assert!(!default.update(&s).unwrap());
|
|
|
|
assert_eq!(&original, &default);
|
|
|
|
|
|
|
|
default
|
|
|
|
.set_body(
|
|
|
|
"hellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n".to_string(),
|
|
|
|
)
|
|
|
|
.set_wrap_header_preamble(Some(("<!--".to_string(), "-->".to_string())));
|
|
|
|
let original = default.clone();
|
|
|
|
let s = default.to_edit_string();
|
2022-09-03 17:47:58 +03:00
|
|
|
assert_eq!(s, "<!--\nDate: Sun, 16 Jun 2013 17:56:45 +0200\nFrom: \nTo: \nCc: \nBcc: \nSubject: test_update()\n-->\n\nhellohello<!--\n<!--\n<--hellohello\nhellohello-->\n-->\n-->hello\n");
|
2022-09-02 09:50:07 +03:00
|
|
|
assert!(!default.update(&s).unwrap());
|
|
|
|
assert_eq!(&original, &default);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2019-07-31 13:33:39 +03:00
|
|
|
#[test]
|
|
|
|
fn test_attachments() {
|
|
|
|
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());
|
|
|
|
}
|
2022-09-02 09:50:07 +03:00
|
|
|
*/
|
2018-08-29 14:11:59 +03:00
|
|
|
}
|
2019-08-01 12:14:45 +03:00
|
|
|
|
|
|
|
/// Reads file from given path, and returns an 'application/octet-stream' AttachmentBuilder object
|
|
|
|
pub fn attachment_from_file<I>(path: &I) -> Result<AttachmentBuilder>
|
|
|
|
where
|
|
|
|
I: AsRef<OsStr>,
|
|
|
|
{
|
2019-09-28 11:53:39 +03:00
|
|
|
let path: PathBuf = Path::new(path).expand();
|
2019-08-01 12:14:45 +03:00
|
|
|
if !path.is_file() {
|
2022-12-08 22:20:05 +02:00
|
|
|
return Err(Error::new(format!("{} is not a file", path.display())));
|
2019-08-01 12:14:45 +03:00
|
|
|
}
|
|
|
|
|
2019-09-28 11:53:39 +03:00
|
|
|
let mut file = std::fs::File::open(&path)?;
|
2019-08-01 12:14:45 +03:00
|
|
|
let mut contents = Vec::new();
|
|
|
|
file.read_to_end(&mut contents)?;
|
2019-11-19 22:46:25 +02:00
|
|
|
let mut attachment = AttachmentBuilder::default();
|
2020-09-16 19:57:06 +03:00
|
|
|
|
2019-08-01 12:14:45 +03:00
|
|
|
attachment
|
|
|
|
.set_raw(contents)
|
2019-11-19 22:46:25 +02:00
|
|
|
.set_body_to_raw()
|
2019-08-01 12:14:45 +03:00
|
|
|
.set_content_type(ContentType::Other {
|
|
|
|
name: path.file_name().map(|s| s.to_string_lossy().into()),
|
2020-09-16 19:57:06 +03:00
|
|
|
tag: if let Ok(mime_type) = query_mime_info(&path) {
|
|
|
|
mime_type
|
|
|
|
} else {
|
|
|
|
b"application/octet-stream".to_vec()
|
|
|
|
},
|
2019-08-01 12:14:45 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
Ok(attachment)
|
|
|
|
}
|