melib: Add Draft entity

embed
Manos Pitsidianakis 2018-08-29 14:11:59 +03:00
parent c01d15b19f
commit edf0464011
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
7 changed files with 237 additions and 23 deletions

View File

@ -98,7 +98,7 @@ impl Display for MultipartType {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ContentType {
Text {
kind: Text,
@ -183,7 +183,8 @@ impl Display for Text {
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum ContentTransferEncoding {
_8Bit,
_7Bit,
@ -197,3 +198,17 @@ impl Default for ContentTransferEncoding {
ContentTransferEncoding::_7Bit
}
}
impl Display for ContentTransferEncoding {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match *self {
ContentTransferEncoding::_7Bit => write!(f, "7bit"),
ContentTransferEncoding::_8Bit => write!(f, "8bit"),
ContentTransferEncoding::Base64 => write!(f, "base64"),
ContentTransferEncoding::QuotedPrintable => write!(f, "quoted-printable"),
ContentTransferEncoding::Other { tag: ref t } => {
panic!("unknown encoding {:?}", str::from_utf8(t))
}
}
}
}

View File

@ -35,7 +35,7 @@ pub use mailbox::email::attachment_types::*;
*/
// TODO: Add example.
//
#[derive(Default)]
#[derive(Default, PartialEq)]
pub struct AttachmentBuilder {
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
@ -43,7 +43,7 @@ pub struct AttachmentBuilder {
raw: Vec<u8>,
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub struct Attachment {
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
@ -272,6 +272,18 @@ impl fmt::Display for Attachment {
}
impl Attachment {
pub fn new(
content_type: ContentType,
content_transfer_encoding: ContentTransferEncoding,
raw: Vec<u8>,
) -> Self {
Attachment {
content_type,
content_transfer_encoding,
raw,
}
}
pub fn bytes(&self) -> &[u8] {
&self.raw
}

View File

@ -0,0 +1,142 @@
use super::*;
use chrono::{DateTime, Local};
use data_encoding::BASE64_MIME;
mod random;
use super::parser;
extern crate fnv;
use self::fnv::FnvHashMap;
#[derive(Debug, PartialEq)]
pub struct Draft {
// FIXME: Preserve header order
// FIXME: Validate headers, allow custom ones
headers: FnvHashMap<String, String>,
body: String,
attachments: Vec<Attachment>,
}
impl Default for Draft {
fn default() -> Self {
let mut headers = FnvHashMap::with_capacity_and_hasher(8, Default::default());
headers.insert("From".into(), "x".into());
headers.insert("To".into(), "x".into());
let now: DateTime<Local> = Local::now();
headers.insert("Date".into(), now.to_rfc2822());
headers.insert("Subject".into(), "x".into());
headers.insert("Message-ID".into(), random::gen_message_id());
headers.insert("User-Agent".into(), "meli".into());
Draft {
headers,
body: String::new(),
attachments: Vec::new(),
}
}
}
impl Draft {
pub fn set_body(&mut self, s: String) {
self.body = s;
}
pub fn to_string(&self) -> Result<String> {
let mut ret = String::new();
for (k, v) in &self.headers {
ret.extend(format!("{}: {}\n", k, v).chars());
}
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)
}
pub fn from_str(s: &str) -> Result<Self> {
if s.is_empty() {
return Err(MeliError::new("sadfsa"));
}
let (headers, _) = parser::mail(s.as_bytes()).to_full_result()?;
let mut ret = Draft::default();
for (k, v) in headers {
if ignore_header(k) {
continue;
}
ret.headers.insert(
String::from_utf8(k.to_vec())?,
String::from_utf8(v.to_vec())?,
);
}
let body = Envelope::new(0).body_bytes(s.as_bytes());
ret.body = String::from_utf8(decode(&body, None))?;
//ret.attachments = body.attachments();
Ok(ret)
}
}
fn ignore_header(header: &[u8]) -> bool {
match header {
b"From" => false,
b"To" => false,
b"Date" => false,
b"Message-ID" => false,
b"User-Agent" => false,
b"Subject" => false,
b"Reply-to" => false,
b"Cc" => false,
b"Bcc" => false,
h if h.starts_with(b"X-") => false,
_ => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let mut default = Draft::default();
assert_eq!(
Draft::from_str(&default.to_string().unwrap()).unwrap(),
default
);
default.set_body("αδφαφσαφασ".to_string());
assert_eq!(
Draft::from_str(&default.to_string().unwrap()).unwrap(),
default
);
default.set_body("ascii only".to_string());
assert_eq!(
Draft::from_str(&default.to_string().unwrap()).unwrap(),
default
);
}
}

View File

@ -0,0 +1,51 @@
use std::char;
use std::fs::File;
use std::io::prelude::*;
use std::time::SystemTime;
fn random_u64() -> u64 {
let mut f = File::open("/dev/urandom").unwrap();
let mut buffer = [0; 8];
// read exactly 10 bytes
f.read_exact(&mut buffer).unwrap();
u64::from(buffer[0])
| (u64::from(buffer[1]) << 8)
| (u64::from(buffer[2]) << 16)
| (u64::from(buffer[3]) << 24)
| (u64::from(buffer[4]) << 32)
| (u64::from(buffer[5]) << 40)
| (u64::from(buffer[6]) << 48)
| (u64::from(buffer[7]) << 56)
}
fn clock() -> u64 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn base36(mut m: u64) -> String {
let mut stack = Vec::with_capacity(32);
while m >= 36 {
stack.push((m % 36) as u32);
m /= 36;
}
let mut ret = String::with_capacity(stack.len());
while let Some(d) = stack.pop() {
ret.push(char::from_digit(d, 36).unwrap());
}
ret
}
pub fn gen_message_id() -> String {
let clock = base36(clock());
let rand = base36(random_u64());
format!("<{}.{}@meli>", clock, rand)
}

View File

@ -22,6 +22,10 @@
/*!
* Email parsing, handling, sending etc.
*/
mod compose;
pub use self::compose::*;
mod attachment_types;
pub mod attachments;
pub use self::attachments::*;
@ -439,6 +443,11 @@ impl Envelope {
.unwrap_or_else(|_| Vec::new())
}
pub fn body_bytes(&self, bytes: &[u8]) -> Attachment {
if bytes.is_empty() {
let builder = AttachmentBuilder::new(bytes);
return builder.build();
}
let (headers, body) = match parser::mail(bytes).to_full_result() {
Ok(v) => v,
Err(_) => {
@ -463,10 +472,12 @@ impl Envelope {
}
pub fn body(&self, mut operation: Box<BackendOp>) -> Attachment {
let file = operation.as_bytes();
self.body_bytes(file.unwrap())
/*
let (headers, body) = match parser::mail(file.unwrap()).to_full_result() {
Ok(v) => v,
Err(_) => {
eprintln!("error in parsing mail\n");
eprintln!("2error in parsing mail\n");
let error_msg = b"Mail cannot be shown because of errors.";
let mut builder = AttachmentBuilder::new(error_msg);
return builder.build();
@ -484,6 +495,7 @@ impl Envelope {
}
}
builder.build()
*/
}
pub fn subject(&self) -> Cow<str> {
match self.subject {

View File

@ -20,21 +20,6 @@
*/
use super::*;
use fnv::FnvHashMap;
struct Draft {
headers: FnvHashMap<String, String>,
body: String,
}
impl Default for Draft {
fn default() -> Self {
Draft {
headers: FnvHashMap::with_capacity_and_hasher(8, Default::default()),
body: String::new(),
}
}
}
#[derive(Debug)]
pub struct Composer {

View File

@ -54,9 +54,6 @@ use execute::*;
pub mod state;
pub use state::*;
mod compose;
pub(crate) use compose::*;
pub mod components;
pub use components::*;