Add attachment support
parent
9946fbcbe0
commit
63670259f8
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "nutt"
|
name = "meli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||||
|
|
||||||
|
@ -19,4 +19,5 @@ optional = false
|
||||||
version = "5.86.0"
|
version = "5.86.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
#lto = true
|
||||||
|
opt-level = 2
|
||||||
|
|
|
@ -21,17 +21,15 @@
|
||||||
//use std::io::prelude::*;
|
//use std::io::prelude::*;
|
||||||
//use std::fs::File;
|
//use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use super::email::Mail;
|
use mailbox::email::Mail;
|
||||||
use error::{MeliError, Result};
|
use error::{MeliError, Result};
|
||||||
|
|
||||||
|
use mailbox::backends::MailBackend;
|
||||||
|
|
||||||
pub trait MailBackend {
|
|
||||||
fn get(&self) -> Result<Vec<Mail>>;
|
|
||||||
}
|
|
||||||
pub struct MaildirType {
|
pub struct MaildirType {
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MailBackend for MaildirType {
|
impl MailBackend for MaildirType {
|
||||||
fn get(&self) -> Result<Vec<Mail>> {
|
fn get(&self) -> Result<Vec<Mail>> {
|
||||||
MaildirType::is_valid(&self.path)?;
|
MaildirType::is_valid(&self.path)?;
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod maildir;
|
||||||
|
|
||||||
|
use mailbox::email::Mail;
|
||||||
|
use error::Result;
|
||||||
|
|
||||||
|
pub trait MailBackend {
|
||||||
|
fn get(&self) -> Result<Vec<Mail>>;
|
||||||
|
}
|
|
@ -0,0 +1,308 @@
|
||||||
|
use super::parser;
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Data
|
||||||
|
* Text { content: String }
|
||||||
|
* Multipart
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Clone,Debug, PartialEq)]
|
||||||
|
pub enum MultipartType {
|
||||||
|
Mixed,
|
||||||
|
Alternative,
|
||||||
|
Digest,
|
||||||
|
Unsupported { tag: String },
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum AttachmentType {
|
||||||
|
Data { tag: String },
|
||||||
|
Text { content: String },
|
||||||
|
Multipart { of_type: MultipartType, subattachments: Vec<Box<Attachment>>, }
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum ContentType {
|
||||||
|
Text,
|
||||||
|
Multipart { boundary: String },
|
||||||
|
Unsupported { tag: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Display for ContentType {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ContentType::Text => {
|
||||||
|
write!(f, "text")
|
||||||
|
},
|
||||||
|
ContentType::Multipart { boundary: _ } => {
|
||||||
|
write!(f, "multipart")
|
||||||
|
},
|
||||||
|
ContentType::Unsupported { tag: ref t } => {
|
||||||
|
write!(f, "{}", t)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug, PartialEq)]
|
||||||
|
pub enum ContentSubType {
|
||||||
|
Plain,
|
||||||
|
Other { tag: String },
|
||||||
|
}
|
||||||
|
impl ::std::fmt::Display for ContentSubType {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ContentSubType::Plain => {
|
||||||
|
write!(f, "plain")
|
||||||
|
},
|
||||||
|
ContentSubType::Other { tag: ref t } => {
|
||||||
|
write!(f, "{}", t)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum ContentTransferEncoding {
|
||||||
|
_8Bit,
|
||||||
|
_7Bit,
|
||||||
|
Base64,
|
||||||
|
QuotedPrintable,
|
||||||
|
Other { tag: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AttachmentBuilder {
|
||||||
|
content_type: (ContentType, ContentSubType),
|
||||||
|
content_transfer_encoding: ContentTransferEncoding,
|
||||||
|
|
||||||
|
raw: Box<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttachmentBuilder {
|
||||||
|
pub fn new(content: &[u8]) -> Self {
|
||||||
|
AttachmentBuilder {
|
||||||
|
content_type: (ContentType::Text, ContentSubType::Plain),
|
||||||
|
content_transfer_encoding: ContentTransferEncoding::_7Bit,
|
||||||
|
raw: Box::new(content.to_vec()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn content_type(&mut self, value: &str) -> &Self {
|
||||||
|
match parser::content_type(value.as_bytes()).to_full_result() {
|
||||||
|
Ok((ct, cst, params)) => {
|
||||||
|
match ct.to_lowercase().as_ref() {
|
||||||
|
"multipart" => {
|
||||||
|
let mut boundary = None;
|
||||||
|
for (n, v) in params {
|
||||||
|
if n.to_lowercase() == "boundary" {
|
||||||
|
boundary = Some(format!("--{}", v).to_string());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(boundary.is_some());
|
||||||
|
self.content_type.0 = ContentType::Multipart { boundary: boundary.unwrap() };
|
||||||
|
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
|
||||||
|
},
|
||||||
|
"text" => {
|
||||||
|
self.content_type.0 = ContentType::Text;
|
||||||
|
let cst = cst.to_lowercase();
|
||||||
|
match cst.as_ref() {
|
||||||
|
"plain" => {},
|
||||||
|
_ => {
|
||||||
|
self.content_type.1 = ContentSubType::Other { tag: cst };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unsupported_type => {
|
||||||
|
self.content_type.0 = ContentType::Unsupported { tag: unsupported_type.to_string() };
|
||||||
|
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn content_transfer_encoding(&mut self, value: &str) -> &Self {
|
||||||
|
self.content_transfer_encoding =
|
||||||
|
match value.to_lowercase().as_ref() {
|
||||||
|
"base64" => {
|
||||||
|
ContentTransferEncoding::Base64
|
||||||
|
},
|
||||||
|
"7bit" => {
|
||||||
|
ContentTransferEncoding::_7Bit
|
||||||
|
},
|
||||||
|
"8bit" => {
|
||||||
|
ContentTransferEncoding::_8Bit
|
||||||
|
},
|
||||||
|
"quoted-printable" => {
|
||||||
|
ContentTransferEncoding::QuotedPrintable
|
||||||
|
},
|
||||||
|
k => {
|
||||||
|
ContentTransferEncoding::Other { tag: k.to_string() }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn decode(&self) -> String {
|
||||||
|
match self.content_transfer_encoding {
|
||||||
|
ContentTransferEncoding::Base64 => {
|
||||||
|
match ::base64::decode(&::std::str::from_utf8(&self.raw).unwrap().trim().lines().fold(String::with_capacity(self.raw.len()), |mut acc, x| { acc.push_str(x); acc })) {
|
||||||
|
Ok( ref v ) => {
|
||||||
|
String::from_utf8_lossy(v).into_owned()
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
String::from_utf8_lossy(&self.raw).into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ContentTransferEncoding::QuotedPrintable => {
|
||||||
|
parser::quoted_printable_text(&self.raw).to_full_result().unwrap()
|
||||||
|
},
|
||||||
|
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit => {
|
||||||
|
String::from_utf8_lossy(&self.raw).into_owned()
|
||||||
|
},
|
||||||
|
ContentTransferEncoding::Other { tag: _ } => {
|
||||||
|
String::from_utf8_lossy(&self.raw).into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn build(self) -> Attachment {
|
||||||
|
let attachment_type =
|
||||||
|
match self.content_type.0 {
|
||||||
|
ContentType::Text => {
|
||||||
|
AttachmentType::Text { content: self.decode() }
|
||||||
|
},
|
||||||
|
ContentType::Multipart { boundary: ref b } => {
|
||||||
|
let multipart_type =
|
||||||
|
match self.content_type.1 {
|
||||||
|
ContentSubType::Other { ref tag } => {
|
||||||
|
match tag.to_lowercase().as_ref() {
|
||||||
|
"mixed" => {
|
||||||
|
MultipartType::Mixed
|
||||||
|
},
|
||||||
|
"alternative" => {
|
||||||
|
MultipartType::Alternative
|
||||||
|
},
|
||||||
|
"digest" => {
|
||||||
|
MultipartType::Digest
|
||||||
|
},
|
||||||
|
t => {
|
||||||
|
MultipartType::Unsupported { tag: t.to_string() }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AttachmentType::Multipart {
|
||||||
|
of_type: multipart_type,
|
||||||
|
subattachments: Attachment::subattachments(&self.raw, &b),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ContentType::Unsupported { ref tag } => {
|
||||||
|
AttachmentType::Data { tag: tag.clone() }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Attachment {
|
||||||
|
content_type: self.content_type,
|
||||||
|
content_transfer_encoding: self.content_transfer_encoding,
|
||||||
|
raw: self.raw,
|
||||||
|
attachment_type: attachment_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct Attachment {
|
||||||
|
content_type: (ContentType, ContentSubType),
|
||||||
|
content_transfer_encoding: ContentTransferEncoding,
|
||||||
|
|
||||||
|
raw: Box<Vec<u8>>,
|
||||||
|
|
||||||
|
attachment_type: AttachmentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attachment {
|
||||||
|
fn get_text_recursive(&self, text: &mut String) {
|
||||||
|
match self.attachment_type {
|
||||||
|
AttachmentType::Data { tag: _ } => {
|
||||||
|
text.push_str(&format!("Data attachment of type {}", self.get_tag()));
|
||||||
|
},
|
||||||
|
AttachmentType::Text { content: ref t } => {
|
||||||
|
text.push_str(t);
|
||||||
|
},
|
||||||
|
AttachmentType::Multipart {
|
||||||
|
of_type: ref multipart_type,
|
||||||
|
subattachments: ref sub_att_vec,
|
||||||
|
} => {
|
||||||
|
if *multipart_type == MultipartType::Alternative {
|
||||||
|
for a in sub_att_vec {
|
||||||
|
if a.content_type.1 == ContentSubType::Plain {
|
||||||
|
a.get_text_recursive(text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for a in sub_att_vec {
|
||||||
|
a.get_text_recursive(text);
|
||||||
|
text.push_str("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_text(&self) -> String {
|
||||||
|
let mut text = String::with_capacity(self.raw.len());
|
||||||
|
self.get_text_recursive(&mut text);
|
||||||
|
text
|
||||||
|
}
|
||||||
|
pub fn get_description(&self) -> String {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
pub fn get_tag(&self) -> String {
|
||||||
|
format!("{}/{}", self.content_type.0, self.content_type.1).to_string()
|
||||||
|
}
|
||||||
|
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Box<Attachment>> {
|
||||||
|
match parser::attachments(raw, boundary, &format!("{}--", boundary)).to_full_result() {
|
||||||
|
Ok(attachments) => {
|
||||||
|
let mut vec = Vec::with_capacity(attachments.len());
|
||||||
|
for a in attachments {
|
||||||
|
let (headers, body) = match parser::attachment(a).to_full_result() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("error in parsing attachment");
|
||||||
|
eprintln!("\n-------------------------------");
|
||||||
|
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
|
||||||
|
eprintln!("-------------------------------\n");
|
||||||
|
|
||||||
|
continue;}
|
||||||
|
};
|
||||||
|
let mut builder = AttachmentBuilder::new(body);
|
||||||
|
for (name, value) in headers {
|
||||||
|
match name.to_lowercase().as_ref(){
|
||||||
|
"content-type" => {
|
||||||
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
|
builder.content_type(&value);
|
||||||
|
},
|
||||||
|
"content-transfer-encoding" => {
|
||||||
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
|
builder.content_transfer_encoding(&value);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec.push(Box::new(builder.build()));
|
||||||
|
}
|
||||||
|
vec
|
||||||
|
},
|
||||||
|
a => {
|
||||||
|
eprintln!("error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}", a, ::std::str::from_utf8(raw).unwrap(), boundary);
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,10 @@ use std::fmt;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use mailbox::parser::*;
|
mod parser;
|
||||||
|
mod attachments;
|
||||||
|
|
||||||
|
use self::attachments::*;
|
||||||
|
|
||||||
use chrono;
|
use chrono;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
|
@ -48,7 +51,7 @@ impl StrBuild for MessageID {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_strbuilder() {
|
fn test_strbuilder() {
|
||||||
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
|
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
|
||||||
let (_, slice) = message_id(m_id.as_bytes()).unwrap();
|
let (_, slice) = parser::message_id(m_id.as_bytes()).unwrap();
|
||||||
assert_eq!(MessageID::new(m_id, slice), MessageID (m_id.to_string(), StrBuilder{offset: 1, length: 43}));
|
assert_eq!(MessageID::new(m_id, slice), MessageID (m_id.to_string(), StrBuilder{offset: 1, length: 43}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +78,13 @@ pub struct Mail {
|
||||||
date: String,
|
date: String,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
to: Option<String>,
|
to: Option<String>,
|
||||||
body: String,
|
body: Option<Attachment>,
|
||||||
subject: Option<String>,
|
subject: Option<String>,
|
||||||
message_id: Option<MessageID>,
|
message_id: Option<MessageID>,
|
||||||
in_reply_to: Option<MessageID>,
|
in_reply_to: Option<MessageID>,
|
||||||
references: Option<References>,
|
references: Option<References>,
|
||||||
|
|
||||||
datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
|
datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
|
||||||
|
|
||||||
thread: usize,
|
thread: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,8 +113,8 @@ impl Mail {
|
||||||
None => "",
|
None => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_body(&self) -> &str {
|
pub fn get_body(&self) -> &Attachment {
|
||||||
&self.body
|
self.body.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
pub fn get_subject(&self) -> &str {
|
pub fn get_subject(&self) -> &str {
|
||||||
match self.subject {
|
match self.subject {
|
||||||
|
@ -154,7 +156,7 @@ impl Mail {
|
||||||
self.to = Some(new_val);
|
self.to = Some(new_val);
|
||||||
}
|
}
|
||||||
fn set_in_reply_to(&mut self, new_val: &str) -> () {
|
fn set_in_reply_to(&mut self, new_val: &str) -> () {
|
||||||
let slice = match message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => { v },
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||||
self.in_reply_to = None;
|
self.in_reply_to = None;
|
||||||
|
@ -166,7 +168,7 @@ impl Mail {
|
||||||
self.subject = Some(new_val);
|
self.subject = Some(new_val);
|
||||||
}
|
}
|
||||||
fn set_message_id(&mut self, new_val: &str) -> () {
|
fn set_message_id(&mut self, new_val: &str) -> () {
|
||||||
let slice = match message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => { v },
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||||
self.message_id = None;
|
self.message_id = None;
|
||||||
|
@ -175,7 +177,7 @@ impl Mail {
|
||||||
self.message_id = Some(MessageID::new(new_val, slice));
|
self.message_id = Some(MessageID::new(new_val, slice));
|
||||||
}
|
}
|
||||||
fn push_references(&mut self, new_val: &str) -> () {
|
fn push_references(&mut self, new_val: &str) -> () {
|
||||||
let slice = match message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => { v },
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||||
return; }
|
return; }
|
||||||
|
@ -213,8 +215,8 @@ impl Mail {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_body(&mut self, new_val: String) -> () {
|
pub fn set_body(&mut self, new_val: Attachment) -> () {
|
||||||
self.body = new_val;
|
self.body = Some(new_val);
|
||||||
}
|
}
|
||||||
pub fn get_thread(&self) -> usize {
|
pub fn get_thread(&self) -> usize {
|
||||||
self.thread
|
self.thread
|
||||||
|
@ -226,33 +228,32 @@ impl Mail {
|
||||||
self.datetime = new_val;
|
self.datetime = new_val;
|
||||||
}
|
}
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Mail {
|
Mail {
|
||||||
date: "".to_string(),
|
date: "".to_string(),
|
||||||
from: None,
|
from: None,
|
||||||
to: None,
|
to: None,
|
||||||
body: "".to_string(),
|
body: None,
|
||||||
subject: None,
|
subject: None,
|
||||||
message_id: None,
|
message_id: None,
|
||||||
in_reply_to: None,
|
in_reply_to: None,
|
||||||
references: None,
|
references: None,
|
||||||
|
|
||||||
datetime: None,
|
datetime: None,
|
||||||
|
|
||||||
thread: 0,
|
thread: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from(path: std::string::String) -> Option<Self> {
|
pub fn from(path: std::string::String) -> Option<Self> {
|
||||||
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
|
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
|
||||||
let file = unsafe { f.as_slice() };
|
let file = unsafe { f.as_slice() };
|
||||||
let (headers, body) = match mail(file).to_full_result() {
|
let (headers, body) = match parser::mail(file).to_full_result() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
eprintln!("error in parsing");
|
eprintln!("error in parsing");
|
||||||
let path = std::path::PathBuf::from(&path);
|
let path = std::path::PathBuf::from(&path);
|
||||||
|
|
||||||
let mut czc = std::fs::File::open(path).unwrap();
|
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
let _ = czc.read_to_end(&mut buffer);
|
let _ = std::fs::File::open(path).unwrap().read_to_end(&mut buffer);
|
||||||
eprintln!("\n-------------------------------");
|
eprintln!("\n-------------------------------");
|
||||||
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
|
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
|
||||||
eprintln!("-------------------------------\n");
|
eprintln!("-------------------------------\n");
|
||||||
|
@ -263,6 +264,7 @@ impl Mail {
|
||||||
let mut in_reply_to = None;
|
let mut in_reply_to = None;
|
||||||
let mut datetime = None;
|
let mut datetime = None;
|
||||||
|
|
||||||
|
let mut builder = AttachmentBuilder::new(body);
|
||||||
for (name, value) in headers {
|
for (name, value) in headers {
|
||||||
if value.len() == 1 && value[0].is_empty() {
|
if value.len() == 1 && value[0].is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
@ -270,7 +272,7 @@ impl Mail {
|
||||||
match name {
|
match name {
|
||||||
"To" => {
|
"To" => {
|
||||||
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
let parse_result = subject(value.as_bytes());
|
let parse_result = parser::subject(value.as_bytes());
|
||||||
let value = match parse_result.is_done() {
|
let value = match parse_result.is_done() {
|
||||||
true => {
|
true => {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
|
@ -283,7 +285,7 @@ impl Mail {
|
||||||
},
|
},
|
||||||
"From" => {
|
"From" => {
|
||||||
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
let parse_result = subject(value.as_bytes());
|
let parse_result = parser::subject(value.as_bytes());
|
||||||
let value = match parse_result.is_done() {
|
let value = match parse_result.is_done() {
|
||||||
true => {
|
true => {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
|
@ -296,7 +298,7 @@ impl Mail {
|
||||||
},
|
},
|
||||||
"Subject" => {
|
"Subject" => {
|
||||||
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
|
||||||
let parse_result = subject(value.trim().as_bytes());
|
let parse_result = parser::subject(value.trim().as_bytes());
|
||||||
let value = match parse_result.is_done() {
|
let value = match parse_result.is_done() {
|
||||||
true => {
|
true => {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
|
@ -313,7 +315,7 @@ impl Mail {
|
||||||
"References" => {
|
"References" => {
|
||||||
let folded_value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
let folded_value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
{
|
{
|
||||||
let parse_result = references(&folded_value.as_bytes());
|
let parse_result = parser::references(&folded_value.as_bytes());
|
||||||
match parse_result.is_done() {
|
match parse_result.is_done() {
|
||||||
true => {
|
true => {
|
||||||
for v in parse_result.to_full_result().unwrap() {
|
for v in parse_result.to_full_result().unwrap() {
|
||||||
|
@ -334,6 +336,14 @@ impl Mail {
|
||||||
mail.set_date(value.clone());
|
mail.set_date(value.clone());
|
||||||
datetime = Some(value);
|
datetime = Some(value);
|
||||||
},
|
},
|
||||||
|
"Content-Transfer-Encoding" => {
|
||||||
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
|
builder.content_transfer_encoding(&value);
|
||||||
|
},
|
||||||
|
"Content-Type" => {
|
||||||
|
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
|
||||||
|
builder.content_type(&value);
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -342,10 +352,10 @@ impl Mail {
|
||||||
mail.push_references(x);
|
mail.push_references(x);
|
||||||
},
|
},
|
||||||
None => {},
|
None => {},
|
||||||
}
|
};
|
||||||
mail.set_body(String::from_utf8_lossy(body).into_owned());
|
mail.set_body(builder.build());
|
||||||
if datetime.is_some() {
|
if datetime.is_some() {
|
||||||
mail.set_datetime(date(&datetime.unwrap()));
|
mail.set_datetime(parser::date(&datetime.unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(mail)
|
Some(mail)
|
|
@ -60,7 +60,11 @@ named!(headers<std::vec::Vec<(&str, std::vec::Vec<&str>)>>,
|
||||||
|
|
||||||
named!(pub mail<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
|
named!(pub mail<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
|
||||||
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| { true }))));
|
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| { true }))));
|
||||||
//pair!(headers, take_while!(call!(|_| { true }))));
|
named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
|
||||||
|
do_parse!(
|
||||||
|
opt!(is_a!(" \n\t\r")) >>
|
||||||
|
pair: pair!(many0!(complete!(header)), take_while!(call!(|_| { true }))) >>
|
||||||
|
( { pair } )));
|
||||||
|
|
||||||
/* try chrono parse_from_str with several formats
|
/* try chrono parse_from_str with several formats
|
||||||
* https://docs.rs/chrono/0.4.0/chrono/struct.DateTime.html#method.parse_from_str
|
* https://docs.rs/chrono/0.4.0/chrono/struct.DateTime.html#method.parse_from_str
|
||||||
|
@ -174,3 +178,66 @@ named!(pub message_id<&str>,
|
||||||
|
|
||||||
named!(pub references<Vec<&str>>, many0!(preceded!(is_not!("<"), message_id)));
|
named!(pub references<Vec<&str>>, many0!(preceded!(is_not!("<"), message_id)));
|
||||||
|
|
||||||
|
named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
|
||||||
|
dbg!(alt_complete!(do_parse!(
|
||||||
|
take_until!(boundary) >>
|
||||||
|
vecs: many0!(complete!(do_parse!(
|
||||||
|
tag!(boundary) >>
|
||||||
|
tag!("\n") >>
|
||||||
|
body: take_until1!(boundary) >>
|
||||||
|
( { body } )))) >>
|
||||||
|
tag!(boundary_end) >>
|
||||||
|
tag!("\n") >>
|
||||||
|
take_while!(call!(|_| { true })) >>
|
||||||
|
( {
|
||||||
|
vecs
|
||||||
|
} )
|
||||||
|
) | do_parse!(
|
||||||
|
take_until!(boundary_end) >>
|
||||||
|
tag!(boundary_end) >>
|
||||||
|
( { Vec::<&[u8]>::new() } ))
|
||||||
|
)));
|
||||||
|
#[test]
|
||||||
|
fn test_attachments() {
|
||||||
|
use std::io::Read;
|
||||||
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
|
let _ = std::fs::File::open("test/attachment_test").unwrap().read_to_end(&mut buffer);
|
||||||
|
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160";
|
||||||
|
let (_, body) = match mail(&buffer).to_full_result() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => { panic!() }
|
||||||
|
};
|
||||||
|
//eprintln!("{:?}",std::str::from_utf8(body));
|
||||||
|
let attachments = attachments(body, boundary).to_full_result().unwrap();
|
||||||
|
assert_eq!(attachments.len(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
||||||
|
do_parse!(
|
||||||
|
_type: map_res!(take_until!("/"), std::str::from_utf8) >>
|
||||||
|
tag!("/") >>
|
||||||
|
_subtype: map_res!(is_not!(";"), std::str::from_utf8) >>
|
||||||
|
parameters: many0!(preceded!(tag!(";"), pair!(
|
||||||
|
terminated!(map_res!(ws!(take_until!("=")), std::str::from_utf8), tag!("=")),
|
||||||
|
map_res!(ws!(alt_complete!(
|
||||||
|
delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
|
||||||
|
)), std::str::from_utf8)))) >>
|
||||||
|
( {
|
||||||
|
(_type, _subtype, parameters)
|
||||||
|
} )
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
|
named!(pub quoted_printable_text<String>,
|
||||||
|
do_parse!(
|
||||||
|
bytes: many0!(alt_complete!(
|
||||||
|
preceded!(tag!("=\n"), quoted_printable_byte) |
|
||||||
|
preceded!(tag!("=\n"), le_u8) |
|
||||||
|
quoted_printable_byte |
|
||||||
|
le_u8)) >>
|
||||||
|
( {
|
||||||
|
String::from_utf8_lossy(&bytes).into_owned()
|
||||||
|
} )
|
||||||
|
)
|
||||||
|
);
|
|
@ -24,11 +24,15 @@
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std;
|
use std;
|
||||||
mod maildir;
|
|
||||||
pub mod email;
|
pub mod email;
|
||||||
mod parser;
|
|
||||||
pub use self::email::*;
|
pub use self::email::*;
|
||||||
use mailbox::maildir::MailBackend;
|
|
||||||
|
/* Mail backends. Currently only maildir is supported */
|
||||||
|
mod backends;
|
||||||
|
use mailbox::backends::MailBackend;
|
||||||
|
|
||||||
|
use mailbox::backends::maildir;
|
||||||
|
|
||||||
use error::Result;
|
use error::Result;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -204,9 +204,11 @@ impl Pager {
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
/* y,x coordinates of upper left corner of win */
|
/* y,x coordinates of upper left corner of win */
|
||||||
ncurses::getparyx(win, &mut y, &mut x);
|
ncurses::getparyx(win, &mut y, &mut x);
|
||||||
let body = mail.get_body();
|
|
||||||
let lines: Vec<&str> = body.trim().split('\n').collect();
|
let text = mail.get_body().get_text();
|
||||||
|
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||||
let lines_length = lines.len();
|
let lines_length = lines.len();
|
||||||
|
|
||||||
let pad = ncurses::newpad(lines_length as i32, 1024);
|
let pad = ncurses::newpad(lines_length as i32, 1024);
|
||||||
ncurses::wclear(pad);
|
ncurses::wclear(pad);
|
||||||
for l in lines {
|
for l in lines {
|
||||||
|
|
Loading…
Reference in New Issue