Add attachment support
parent
9946fbcbe0
commit
63670259f8
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "nutt"
|
||||
name = "meli"
|
||||
version = "0.1.0"
|
||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||
|
||||
|
@ -19,4 +19,5 @@ optional = false
|
|||
version = "5.86.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
#lto = true
|
||||
opt-level = 2
|
||||
|
|
|
@ -21,17 +21,15 @@
|
|||
//use std::io::prelude::*;
|
||||
//use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use super::email::Mail;
|
||||
use mailbox::email::Mail;
|
||||
use error::{MeliError, Result};
|
||||
|
||||
use mailbox::backends::MailBackend;
|
||||
|
||||
pub trait MailBackend {
|
||||
fn get(&self) -> Result<Vec<Mail>>;
|
||||
}
|
||||
pub struct MaildirType {
|
||||
path: String,
|
||||
}
|
||||
|
||||
|
||||
impl MailBackend for MaildirType {
|
||||
fn get(&self) -> Result<Vec<Mail>> {
|
||||
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::io::prelude::*;
|
||||
use mailbox::parser::*;
|
||||
mod parser;
|
||||
mod attachments;
|
||||
|
||||
use self::attachments::*;
|
||||
|
||||
use chrono;
|
||||
use chrono::TimeZone;
|
||||
|
@ -48,7 +51,7 @@ impl StrBuild for MessageID {
|
|||
#[test]
|
||||
fn test_strbuilder() {
|
||||
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}));
|
||||
}
|
||||
|
||||
|
@ -75,14 +78,13 @@ pub struct Mail {
|
|||
date: String,
|
||||
from: Option<String>,
|
||||
to: Option<String>,
|
||||
body: String,
|
||||
body: Option<Attachment>,
|
||||
subject: Option<String>,
|
||||
message_id: Option<MessageID>,
|
||||
in_reply_to: Option<MessageID>,
|
||||
references: Option<References>,
|
||||
|
||||
datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
|
||||
|
||||
thread: usize,
|
||||
}
|
||||
|
||||
|
@ -111,8 +113,8 @@ impl Mail {
|
|||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn get_body(&self) -> &str {
|
||||
&self.body
|
||||
pub fn get_body(&self) -> &Attachment {
|
||||
self.body.as_ref().unwrap()
|
||||
}
|
||||
pub fn get_subject(&self) -> &str {
|
||||
match self.subject {
|
||||
|
@ -154,7 +156,7 @@ impl Mail {
|
|||
self.to = Some(new_val);
|
||||
}
|
||||
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 },
|
||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||
self.in_reply_to = None;
|
||||
|
@ -166,7 +168,7 @@ impl Mail {
|
|||
self.subject = Some(new_val);
|
||||
}
|
||||
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 },
|
||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||
self.message_id = None;
|
||||
|
@ -175,7 +177,7 @@ impl Mail {
|
|||
self.message_id = Some(MessageID::new(new_val, slice));
|
||||
}
|
||||
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 },
|
||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
||||
return; }
|
||||
|
@ -213,8 +215,8 @@ impl Mail {
|
|||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn set_body(&mut self, new_val: String) -> () {
|
||||
self.body = new_val;
|
||||
pub fn set_body(&mut self, new_val: Attachment) -> () {
|
||||
self.body = Some(new_val);
|
||||
}
|
||||
pub fn get_thread(&self) -> usize {
|
||||
self.thread
|
||||
|
@ -226,33 +228,32 @@ impl Mail {
|
|||
self.datetime = new_val;
|
||||
}
|
||||
pub fn new() -> Self {
|
||||
Mail {
|
||||
date: "".to_string(),
|
||||
from: None,
|
||||
to: None,
|
||||
body: "".to_string(),
|
||||
subject: None,
|
||||
message_id: None,
|
||||
in_reply_to: None,
|
||||
references: None,
|
||||
Mail {
|
||||
date: "".to_string(),
|
||||
from: None,
|
||||
to: None,
|
||||
body: None,
|
||||
subject: None,
|
||||
message_id: None,
|
||||
in_reply_to: None,
|
||||
references: None,
|
||||
|
||||
datetime: None,
|
||||
datetime: None,
|
||||
|
||||
thread: 0,
|
||||
thread: 0,
|
||||
}
|
||||
}
|
||||
pub fn from(path: std::string::String) -> Option<Self> {
|
||||
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
|
||||
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,
|
||||
Err(_) => {
|
||||
eprintln!("error in parsing");
|
||||
let path = std::path::PathBuf::from(&path);
|
||||
|
||||
let mut czc = std::fs::File::open(path).unwrap();
|
||||
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", std::string::String::from_utf8_lossy(&buffer));
|
||||
eprintln!("-------------------------------\n");
|
||||
|
@ -263,6 +264,7 @@ impl Mail {
|
|||
let mut in_reply_to = None;
|
||||
let mut datetime = None;
|
||||
|
||||
let mut builder = AttachmentBuilder::new(body);
|
||||
for (name, value) in headers {
|
||||
if value.len() == 1 && value[0].is_empty() {
|
||||
continue;
|
||||
|
@ -270,7 +272,7 @@ impl Mail {
|
|||
match name {
|
||||
"To" => {
|
||||
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() {
|
||||
true => {
|
||||
parse_result.to_full_result().unwrap()
|
||||
|
@ -283,7 +285,7 @@ impl Mail {
|
|||
},
|
||||
"From" => {
|
||||
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() {
|
||||
true => {
|
||||
parse_result.to_full_result().unwrap()
|
||||
|
@ -296,7 +298,7 @@ impl Mail {
|
|||
},
|
||||
"Subject" => {
|
||||
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() {
|
||||
true => {
|
||||
parse_result.to_full_result().unwrap()
|
||||
|
@ -313,7 +315,7 @@ impl Mail {
|
|||
"References" => {
|
||||
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() {
|
||||
true => {
|
||||
for v in parse_result.to_full_result().unwrap() {
|
||||
|
@ -334,6 +336,14 @@ impl Mail {
|
|||
mail.set_date(value.clone());
|
||||
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);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
mail.set_body(String::from_utf8_lossy(body).into_owned());
|
||||
};
|
||||
mail.set_body(builder.build());
|
||||
if datetime.is_some() {
|
||||
mail.set_datetime(date(&datetime.unwrap()));
|
||||
mail.set_datetime(parser::date(&datetime.unwrap()));
|
||||
}
|
||||
|
||||
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])>,
|
||||
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
|
||||
* 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_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::collections::HashMap;
|
||||
use std;
|
||||
mod maildir;
|
||||
pub mod email;
|
||||
mod parser;
|
||||
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;
|
||||
|
||||
|
||||
|
|
|
@ -204,9 +204,11 @@ impl Pager {
|
|||
let mut y = 0;
|
||||
/* y,x coordinates of upper left corner of win */
|
||||
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 pad = ncurses::newpad(lines_length as i32, 1024);
|
||||
ncurses::wclear(pad);
|
||||
for l in lines {
|
||||
|
|
Loading…
Reference in New Issue