Browse Source

various fixes

- Clippy fixes
- Rewrite header value parser
- Rewrite string allocations in header encodings
- Thread mail parsing for maildir.rs
- Split crate to lib and bin
embed
Manos Pitsidianakis 5 years ago
parent
commit
04ff21a55f
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 12
      Cargo.toml
  2. 28
      benches/maildir.rs
  3. 35
      src/bin.rs
  4. 38
      src/conf/mod.rs
  5. 20
      src/error.rs
  6. 33
      src/lib.rs
  7. 97
      src/mailbox/backends/maildir.rs
  8. 20
      src/mailbox/backends/mod.rs
  9. 80
      src/mailbox/email/attachments.rs
  10. 133
      src/mailbox/email/mod.rs
  11. 186
      src/mailbox/email/parser.rs
  12. 79
      src/mailbox/mod.rs
  13. 127
      src/ui/index.rs
  14. 53
      src/ui/mod.rs
  15. 23
      src/ui/pager.rs

12
Cargo.toml

@ -3,6 +3,14 @@ name = "meli"
version = "0.1.0"
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
[lib]
name = "melib"
path = "src/lib.rs"
[[bin]]
name = "meli"
path = "src/bin.rs"
[dependencies]
chrono = "0.4"
xdg = "2.1.0"
@ -12,6 +20,7 @@ serde = "^1.0.8"
nom = "3.2.0"
memmap = "*"
base64 = "*"
crossbeam = "^0.3.0"
[dependencies.ncurses]
features = ["wide"]
@ -19,5 +28,4 @@ optional = false
version = "5.86.0"
[profile.release]
#lto = true
opt-level = 2
lto = true

28
benches/maildir.rs

@ -0,0 +1,28 @@
#![feature(test)]
extern crate melib;
use melib::mailbox::backends::MailBackend;
use melib::mailbox::backends::maildir::*;
extern crate test;
use self::test::Bencher;
#[bench]
fn bench_threads_1(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(1));
}
#[bench]
fn bench_threads_2(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(2));
}
#[bench]
fn bench_threads_3(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(3));
}
#[bench]
fn bench_threads_4(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(4));
}
#[bench]
fn bench_threads_6(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(6));
}

35
src/main.rs → src/bin.rs

@ -1,8 +1,8 @@
/*
* meli - main.rs
* meli - bin.rs
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
@ -18,41 +18,31 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate ncurses;
pub mod mailbox;
mod ui;
mod conf;
mod error;
use ui::index::*;
extern crate melib;
use melib::*;
use mailbox::*;
use conf::*;
#[macro_use]
extern crate serde_derive;
/* parser */
#[macro_use]
extern crate nom;
extern crate chrono;
extern crate base64;
extern crate memmap;
extern crate ncurses;
fn main() {
let locale_conf = ncurses::LcCategory::all;
ncurses::setlocale(locale_conf, "en_US.UTF-8");
let set = Settings::new();
let ui = ui::TUI::initialize();
//let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4");
let mut j = 0;
let folder_length = set.accounts.get("norn").unwrap().folders.len();
let folder_length = set.accounts["norn"].folders.len();
'main : loop {
ncurses::touchwin(ncurses::stdscr());
ncurses::mv(0,0);
let mailbox = Mailbox::new(&set.accounts.get("norn").unwrap().folders[j],
Some(&set.accounts.get("norn").unwrap().sent_folder));
let mailbox = Mailbox::new(&set.accounts["norn"].folders[j],
Some(&set.accounts["norn"].sent_folder));
let mut index: Box<Window> = match mailbox {
Ok(v) => {
Ok(v) => {
Box::new(Index::new(v))
},
Err(v) => {
@ -93,6 +83,11 @@ fn main() {
break 'inner;
}
},
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
eprintln!("key_resize");
index.redraw();
continue;
},
_ => {}
}
}

38
src/conf/mod.rs

@ -2,7 +2,7 @@
* meli - configuration module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
@ -21,7 +21,6 @@
extern crate xdg;
extern crate config;
use std::collections::HashMap;
use std::io;
use std::fs;
@ -34,10 +33,13 @@ enum MailFormat {
impl MailFormat {
pub fn from_str(x: &str) -> MailFormat {
match x {
"maildir" | "Maildir" |
"MailDir" => { MailFormat::Maildir },
_ => { panic!("Unrecognizable mail format");}
match x.to_lowercase().as_ref() {
"maildir" => {
MailFormat::Maildir
},
_ => {
panic!("Unrecognizable mail format")
}
}
}
}
@ -78,13 +80,13 @@ impl FileSettings {
let mut s = Config::new();
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
match s.is_ok() { //.unwrap_or(Settings { });
true => { s.unwrap().deserialize().unwrap() },
false => {
eprintln!("{:?}",s.err().unwrap());
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() } },
if s.is_ok() {
s.unwrap().deserialize().unwrap()
} else {
eprintln!("{:?}",s.err().unwrap());
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() }
}
}
}
@ -92,13 +94,13 @@ impl FileSettings {
impl Settings {
pub fn new() -> Settings {
let fs = FileSettings::new();
let mut s: HashMap<String, Account> = HashMap::new();
let mut s: HashMap<String, Account> = HashMap::new();
for (id, x) in fs.accounts {
let mut folders = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut().next() {
for f in f.iter_mut() {
{
let path = f.path();
if path.ends_with("cur") || path.ends_with("new") ||
@ -110,8 +112,8 @@ impl Settings {
recurse_folders(folders, path);
}
}
}
}
}
};
let path = PathBuf::from(&x.folders);

20
src/error.rs

@ -1,3 +1,23 @@
/*
* meli - error module
*
* Copyright 2017 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/>.
*/
use std::error::Error;
use std::fmt;
use std::result;

33
src/lib.rs

@ -0,0 +1,33 @@
/*
* meli - lib.rs
*
* Copyright 2017 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/>.
*/
pub mod mailbox;
pub mod conf;
pub mod error;
#[macro_use]
extern crate serde_derive;
/* parser */
#[macro_use]
extern crate nom;
extern crate chrono;
extern crate base64;
extern crate memmap;

97
src/mailbox/backends/maildir.rs

@ -2,7 +2,7 @@
* meli - mailbox module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
@ -18,20 +18,24 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//use std::io::prelude::*;
//use std::fs::File;
use std::path::PathBuf;
use mailbox::email::Mail;
use error::{MeliError, Result};
use mailbox::backends::MailBackend;
extern crate crossbeam;
use std::path::PathBuf;
pub struct MaildirType {
path: String,
}
impl MailBackend for MaildirType {
fn get(&self) -> Result<Vec<Mail>> {
self.get_multicore(4)
/*
MaildirType::is_valid(&self.path)?;
let mut path = PathBuf::from(&self.path);
path.push("cur");
@ -44,29 +48,13 @@ impl MailBackend for MaildirType {
let path = x.path();
Ok(path.to_str().unwrap().to_string())
})?;
match Mail::from(e) {
match Mail::from(&e) {
Some(e) => {r.push(e);},
None => {}
}
/*
f.read_to_end(&mut buffer)?;
eprintln!("{:?}", String::from_utf8(buffer.clone()).unwrap());
let m = match Email::parse(&buffer) {
Ok((v, rest)) => match rest.len() {
0 => v,
_ =>
{ eprintln!("{:?}", String::from_utf8(rest.to_vec()).unwrap());
panic!("didn't parse"); },
},
Err(v) => panic!(v),
};
r.push(m);
*/
}
Ok(r)
*/
}
}
@ -78,7 +66,7 @@ impl MaildirType {
}
fn is_valid(path: &str) -> Result<()> {
let mut p = PathBuf::from(path);
for d in ["cur", "new", "tmp"].iter() {
for d in &["cur", "new", "tmp"] {
p.push(d);
if !p.is_dir() {
return Err(MeliError::new(format!("{} is not a valid maildir folder", path)));
@ -87,4 +75,65 @@ impl MaildirType {
}
Ok(())
}
pub fn get_multicore(&self, cores: usize) -> Result<Vec<Mail>> {
MaildirType::is_valid(&self.path)?;
let mut path = PathBuf::from(&self.path);
path.push("cur");
let iter = path.read_dir()?;
let count = path.read_dir()?.count();
let mut files: Vec<String> = Vec::with_capacity(count);
let mut r = Vec::with_capacity(count);
for e in iter {
//eprintln!("{:?}", e);
let e = e.and_then(|x| {
let path = x.path();
Ok(path.to_str().unwrap().to_string())
})?;
files.push(e);
/*
f.read_to_end(&mut buffer)?;
eprintln!("{:?}", String::from_utf8(buffer.clone()).unwrap());
let m = match Email::parse(&buffer) {
Ok((v, rest)) => match rest.len() {
0 => v,
_ =>
{ eprintln!("{:?}", String::from_utf8(rest.to_vec()).unwrap());
panic!("didn't parse"); },
},
Err(v) => panic!(v),
};
r.push(m);
*/
}
let mut threads = Vec::with_capacity(cores);
if !files.is_empty() {
crossbeam::scope(|scope| {
let chunk_size = if count / cores > 0 {
count / cores
} else {
count
};
for chunk in files.chunks(chunk_size) {
let s = scope.spawn(move || {
let mut local_r:Vec<Mail> = Vec::with_capacity(chunk.len());
for e in chunk {
if let Some(e) = Mail::from(e) {
local_r.push(e);
}
}
local_r
});
threads.push(s);
}
});
}
for t in threads {
let mut result = t.join();
r.append(&mut result);
}
Ok(r)
}
}

20
src/mailbox/backends/mod.rs

@ -1,3 +1,23 @@
/*
* meli - backends module
*
* Copyright 2017 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/>.
*/
pub mod maildir;
use mailbox::email::Mail;

80
src/mailbox/email/attachments.rs

@ -1,4 +1,26 @@
use super::parser;
/*
* meli - attachments module
*
* Copyright 2017 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/>.
*/
use mailbox::email::parser;
use std::fmt::{Display, Formatter, Result};
/*
*
@ -18,7 +40,7 @@ pub enum MultipartType {
pub enum AttachmentType {
Data { tag: String },
Text { content: String },
Multipart { of_type: MultipartType, subattachments: Vec<Box<Attachment>>, }
Multipart { of_type: MultipartType, subattachments: Vec<Attachment>, }
}
#[derive(Clone,Debug)]
pub enum ContentType {
@ -27,13 +49,13 @@ pub enum ContentType {
Unsupported { tag: String },
}
impl ::std::fmt::Display for ContentType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
impl Display for ContentType {
fn fmt(&self, f: &mut Formatter) -> Result {
match *self {
ContentType::Text => {
write!(f, "text")
},
ContentType::Multipart { boundary: _ } => {
ContentType::Multipart { .. } => {
write!(f, "multipart")
},
ContentType::Unsupported { tag: ref t } => {
@ -47,8 +69,8 @@ pub enum ContentSubType {
Plain,
Other { tag: String },
}
impl ::std::fmt::Display for ContentSubType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
impl Display for ContentSubType {
fn fmt(&self, f: &mut Formatter) -> Result {
match *self {
ContentSubType::Plain => {
write!(f, "plain")
@ -72,7 +94,7 @@ pub struct AttachmentBuilder {
content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>,
raw: Vec<u8>,
}
impl AttachmentBuilder {
@ -80,18 +102,18 @@ impl AttachmentBuilder {
AttachmentBuilder {
content_type: (ContentType::Text, ContentSubType::Plain),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: Box::new(content.to_vec()),
raw: 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)) => {
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());
boundary = Some(format!("--{}--", v).to_string());
break;
}
}
@ -114,9 +136,10 @@ impl AttachmentBuilder {
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
},
}
},
_ => {},
},
Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
}
self
}
@ -156,10 +179,7 @@ impl AttachmentBuilder {
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: _ } => {
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned()
}
}
@ -190,12 +210,12 @@ impl AttachmentBuilder {
}
},
_ => {
panic!();
panic!()
}
};
AttachmentType::Multipart {
of_type: multipart_type,
subattachments: Attachment::subattachments(&self.raw, &b),
subattachments: Attachment::subattachments(&self.raw, b),
}
},
ContentType::Unsupported { ref tag } => {
@ -219,7 +239,7 @@ pub struct Attachment {
content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>,
raw: Vec<u8>,
attachment_type: AttachmentType,
}
@ -227,7 +247,7 @@ pub struct Attachment {
impl Attachment {
fn get_text_recursive(&self, text: &mut String) {
match self.attachment_type {
AttachmentType::Data { tag: _ } => {
AttachmentType::Data { .. } => {
text.push_str(&format!("Data attachment of type {}", self.get_tag()));
},
AttachmentType::Text { content: ref t } => {
@ -264,8 +284,9 @@ impl Attachment {
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() {
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
let boundary_length = boundary.len();
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() {
Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len());
for a in attachments {
@ -277,24 +298,23 @@ impl Attachment {
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
eprintln!("-------------------------------\n");
continue;}
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);
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);
builder.content_transfer_encoding(value);
},
_ => {
},
}
}
vec.push(Box::new(builder.build()));
vec.push(builder.build());
}
vec
},

133
src/mailbox/email/mod.rs

@ -1,15 +1,35 @@
/*
* meli - email module
*
* Copyright 2017 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/>.
*/
mod parser;
mod attachments;
use self::attachments::*;
use std::string::String;
use memmap::{Mmap, Protection};
use std;
use std::cmp::Ordering;
use std::fmt;
use std::option::Option;
use std::io::prelude::*;
mod parser;
mod attachments;
use self::attachments::*;
use chrono;
use chrono::TimeZone;
@ -73,7 +93,7 @@ struct References {
}
/* A very primitive mail object */
#[derive(Clone,Debug)]
#[derive(Clone,Debug,Default)]
pub struct Mail {
date: String,
from: Option<String>,
@ -96,7 +116,7 @@ impl Mail {
}
}
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
self.datetime.unwrap_or(chrono::FixedOffset::west(0).ymd(1970, 1, 1).and_hms(0, 0, 0))
self.datetime.unwrap_or_else(|| { chrono::FixedOffset::west(0).ymd(1970, 1, 1).and_hms(0, 0, 0)})
}
pub fn get_date_as_str(&self) -> &str {
&self.date
@ -209,9 +229,9 @@ impl Mail {
}
}
}
pub fn get_references<'a>(&'a self) -> Vec<&'a MessageID> {
pub fn get_references(&self) -> Vec<&MessageID> {
match self.references {
Some(ref s) => s.refs.iter().fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { acc.push(&x); acc }),
Some(ref s) => s.refs.iter().fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { acc.push(x); acc }),
None => Vec::new(),
}
}
@ -243,21 +263,21 @@ impl Mail {
thread: 0,
}
}
pub fn from(path: std::string::String) -> Option<Self> {
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
pub fn from(path: &str) -> Option<Self> {
let f = Mmap::open_path(path.to_string(), Protection::Read).unwrap();
let file = unsafe { f.as_slice() };
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);
Err(_) => {
eprintln!("error in parsing mail");
let path = std::path::PathBuf::from(path);
let mut buffer = Vec::new();
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", std::string::String::from_utf8_lossy(&buffer));
eprintln!("-------------------------------\n");
return None; }
};
let mut mail = Mail::new();
@ -266,96 +286,73 @@ impl Mail {
let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers {
if value.len() == 1 && value[0].is_empty() {
if value.len() == 1 && value.is_empty() {
continue;
}
match name {
"To" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = parser::subject(value.as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_to(value);
},
"From" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = parser::subject(value.as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_from(value);
},
"Subject" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
let parse_result = parser::subject(value.trim().as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_subject(value);
},
"Message-ID" | "Message-Id" | "Message-id" | "message-id" => {
mail.set_message_id(&value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }));
mail.set_message_id(value);
},
"References" => {
let folded_value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
{
let parse_result = parser::references(&folded_value.as_bytes());
match parse_result.is_done() {
true => {
for v in parse_result.to_full_result().unwrap() {
mail.push_references(v);
}
},
_ => {}
let parse_result = parser::references(value.as_bytes());
if parse_result.is_done() {
for v in parse_result.to_full_result().unwrap() {
mail.push_references(v);
}
}
}
mail.set_references(folded_value);
mail.set_references(value.to_string());
},
"In-Reply-To" | "In-reply-to" | "In-Reply-to" | "in-reply-to" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
mail.set_in_reply_to(&value);
mail.set_in_reply_to(value);
in_reply_to = Some(value); },
"Date" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
mail.set_date(value.clone());
datetime = Some(value);
mail.set_date(value.to_string());
datetime = Some(value.to_string());
},
"Content-Transfer-Encoding" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
builder.content_transfer_encoding(&value);
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);
builder.content_type(value);
},
_ => {},
}
};
match in_reply_to {
Some(ref mut x) => {
mail.push_references(x);
},
None => {},
if let Some(ref mut x) = in_reply_to {
mail.push_references(x);
};
mail.set_body(builder.build());
if datetime.is_some() {
mail.set_datetime(parser::date(&datetime.unwrap()));
if let Some(ref mut d) = datetime {
mail.set_datetime(parser::date(d));
}
Some(mail)

186
src/mailbox/email/parser.rs

@ -1,12 +1,32 @@
//use memmap::{Mmap, Protection};
/*
* meli - parser module
*
* Copyright 2017 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/>.
*/
use std;
use std::str::from_utf8;
use base64;
use chrono;
use nom::le_u8;
use nom::{le_u8, is_hex_digit};
/* Wow this sucks! */
named!(quoted_printable_byte<u8>, do_parse!(
p: map_res!(preceded!(tag!("="), verify!(complete!(take!(2)), |s: &[u8]| { ::nom::is_hex_digit(s[0]) && ::nom::is_hex_digit(s[1]) })), std::str::from_utf8) >>
p: map_res!(preceded!(tag!("="), verify!(complete!(take!(2)), |s: &[u8]| is_hex_digit(s[0]) && is_hex_digit(s[1]) )), from_utf8) >>
( {
u8::from_str_radix(p, 16).unwrap()
} )));
@ -22,51 +42,55 @@ named!(quoted_printable_byte<u8>, do_parse!(
* Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/
/*
* if a header value is a Vec<&str>, this is the tail of that Vector
*/
named!(valuelist<&str>,
map_res!(delimited!(alt_complete!(tag!("\t") | tag!(" ")), take_until!("\n"), tag!("\n")), std::str::from_utf8)
);
use nom::{IResult,Needed,ErrorKind};
/* Parse the value part of the header -> Vec<&str> */
named!(value<Vec<&str>>,
do_parse!(
head: map_res!(terminated!(take_until!("\n"), tag!("\n")), std::str::from_utf8) >>
tail: many0!(valuelist) >>
( {
let tail_len = tail.len();
let tail: Vec<&str> = tail.iter().map(|v| { v.trim()}).collect();
let mut result = Vec::with_capacity(1 + tail.len());
result.push(head.trim());
if tail_len == 1 && tail[0] == "" {
result
} else {
tail.iter().fold(result, |mut acc, x| { acc.push(x); acc})
fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
if input.is_empty() || input[0] == b'\n' {
IResult::Incomplete(Needed::Size(1))
} else {
let input_len = input.len();
for (i, x) in input.iter().enumerate() {
if *x == b'\n' {
if (i + 1) < input_len &&
((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') {
return match from_utf8(&input[0..i]) {
Ok(v) => {
IResult::Done(&input[(i+1)..], v)
},
Err(_) => {
IResult::Error(error_code!(ErrorKind::Custom(43)))
},
}
} else if i + 1 > input_len {
return IResult::Incomplete(Needed::Size(1));
}
}
} )
));
}
IResult::Error(error_code!(ErrorKind::Custom(43)))
}
}
/* Parse the name part of the header -> &str */
named!(name<&str>,
terminated!(verify!(map_res!(take_until1!(":"), std::str::from_utf8), | v: &str | { !v.contains("\n")} ), tag!(":")));
named!(name<&str>,
verify!(map_res!(take_until1!(":"), from_utf8), | v: &str | !v.contains('\n') ));
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
named!(header<(&str, std::vec::Vec<&str>)>,
pair!(complete!(name), complete!(value)));
named!(header<(&str, &str)>,
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)));
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
named!(headers<std::vec::Vec<(&str, std::vec::Vec<&str>)>>,
named!(headers<std::vec::Vec<(&str, &str)>>,
many1!(complete!(header)));
named!(pub mail<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>,
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| { true }))));
named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
named!(pub attachment<(std::vec::Vec<(&str, &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
*/
@ -75,55 +99,69 @@ named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
/* Encoded words
*"=?charset?encoding?encoded text?=".
*/
named!(utf8_token_base64<String>, do_parse!(
named!(utf8_token_base64<Vec<u8>>, do_parse!(
encoded: complete!(delimited!(tag_no_case!("=?UTF-8?B?"), take_until1!("?="), tag!("?="))) >>
( {
match base64::decode(encoded) {
Ok(ref v) => { String::from_utf8_lossy(v).into_owned()
Ok(v) => {
v
},
Err(_) => {
encoded.to_vec()
},
Err(_) => { String::from_utf8_lossy(encoded).into_owned() }
}
} )
));
named!(utf8_token_quoted_p_raw<&[u8], &[u8]>,
named!(utf8_token_quoted_p_raw<&[u8], &[u8]>,
complete!(delimited!(tag_no_case!("=?UTF-8?q?"), take_until1!("?="), tag!("?="))));
//named!(utf8_token_quoted_p<String>, escaped_transform!(call!(alpha), '=', quoted_printable_byte));
named!(utf8_token_quoted_p<String>, do_parse!(
named!(qp_underscore_header<u8>,
do_parse!(tag!("_") >> ( { b' ' } )));
named!(utf8_token_quoted_p<Vec<u8>>, do_parse!(
raw: call!(utf8_token_quoted_p_raw) >>
( {
named!(get_bytes<Vec<u8>>, dbg!(many0!(alt!(quoted_printable_byte | le_u8))));
let bytes = get_bytes(raw).to_full_result().unwrap();
String::from_utf8_lossy(&bytes).into_owned()
named!(get_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8)));
get_bytes(raw).to_full_result().unwrap()
} )));
named!(utf8_token<String>, alt_complete!(
named!(utf8_token<Vec<u8>>, alt_complete!(
utf8_token_base64 |
call!(utf8_token_quoted_p)));
named!(utf8_token_list<String>, ws!(do_parse!(
list: separated_nonempty_list!(complete!(tag!(" ")), utf8_token) >>
( {
( {
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc });
list.iter().fold(String::with_capacity(list_len), |mut acc, x| { acc.push_str(x); acc})
let bytes = list.iter().fold(Vec::with_capacity(list_len), |mut acc, x| { acc.append(&mut x.clone()); acc});
String::from_utf8_lossy(&bytes).into_owned()
} )
)));
named!(ascii_token<String>, do_parse!(
word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >>
( {
String::from_utf8_lossy(word).into_owned()
} )));
/* Lots of copying here. TODO: fix it */
named!(pub subject<String>, ws!(do_parse!(
list: many0!(alt_complete!( utf8_token_list | ascii_token)) >>
( {
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc });
let s = list.iter().fold(String::with_capacity(list_len), |mut acc, x| { acc.push_str(x); acc.push_str(" "); acc});
s.trim().to_string()
( {
let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
let list_len = list.len();
let mut i = 0;
list.iter().fold(String::with_capacity(string_len),
|acc, x| {
let mut acc = acc + &x.replace("\n", "");
if i != list_len - 1 {
acc.push_str(" ");
i+=1;
}
acc
})
} )
)));
@ -159,27 +197,33 @@ fn test_eat_comments() {
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
}
/* Date should tokenize input and convert the tokens, right now we expect input will have no extra
* spaces in between tokens */
/*
* Date should tokenize input and convert the tokens,
* right now we expect input will have no extra spaces in between tokens
*
* We should use a custom parser here*/
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
chrono::DateTime::parse_from_rfc2822(eat_comments(input).trim()).ok()
let parsed_result = subject(eat_comments(input).as_bytes()).to_full_result().unwrap().replace("-", "+");
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
}
#[test]
fn test_date() {
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
let _s = "Thu, 31 Aug 2017 13:43:37 +0000";
let __s = "=?utf-8?q?Thu=2C_31_Aug_2017_13=3A43=3A37_-0000?=";
assert_eq!(date(s).unwrap(), date(_s).unwrap());
assert_eq!(date(_s).unwrap(), date(__s).unwrap());
}
named!(pub message_id<&str>,
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), std::str::from_utf8)
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
);
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!(
named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(do_parse!(
take_until!(boundary) >>
vecs: many0!(complete!(do_parse!(
tag!(boundary) >>
@ -196,33 +240,41 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
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 boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
let boundary_len = boundary.len();
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();
let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap();
assert_eq!(attachments.len(), 4);
}
named!(content_type_parameter< (&str, &str) >,
do_parse!(
tag!(";") >>
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
value: map_res!(ws!(
alt_complete!(delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))),
from_utf8) >>
( {
(name, value)
} )
));
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
do_parse!(
_type: map_res!(take_until!("/"), std::str::from_utf8) >>
_type: map_res!(take_until!("/"), 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)))) >>
_subtype: map_res!(is_not!(";"), from_utf8) >>
parameters: many0!(complete!(content_type_parameter)) >>
( {
(_type, _subtype, parameters)
} )

79
src/mailbox/mod.rs

@ -19,32 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//use std::cmp::Ordering;
//use std::fmt;
use std::option::Option;
use std::collections::HashMap;
use std;
pub mod email;
pub use self::email::*;
/* Mail backends. Currently only maildir is supported */
mod backends;
pub mod backends;
use mailbox::backends::MailBackend;
use mailbox::backends::maildir;
use error::Result;
use std::option::Option;
use std::collections::HashMap;
use std;
type UnixTimestamp = i64;
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
#[derive(Debug)]
pub struct Mailbox{
pub path: String,
pub collection: Box<Vec<Mail>>,
pub collection: Vec<Mail>,
pub threaded_collection: Vec<usize>,
threads: Vec<Thread>,