diff --git a/libimagmail/Cargo.toml b/libimagmail/Cargo.toml new file mode 100644 index 00000000..68f348f8 --- /dev/null +++ b/libimagmail/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "libimagmail" +version = "0.2.0" +authors = ["Matthias Beyer "] + +[dependencies] +log = "0.3" +mailparse = "0.2.0" +semver = "0.2" +toml = "0.2.*" +filters = "0.1.0" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagref] +path = "../libimagref" + diff --git a/libimagmail/src/error.rs b/libimagmail/src/error.rs new file mode 100644 index 00000000..a11a0a94 --- /dev/null +++ b/libimagmail/src/error.rs @@ -0,0 +1,16 @@ +generate_error_module!( + generate_error_types!(MailError, MailErrorKind, + RefCreationError => "Error creating a reference to a file/directory", + RefHandlingError => "Error while handling the internal reference object", + MailParsingError => "Error while parsing mail", + + FetchByHashError => "Error fetching mail from Store by hash", + FetchError => "Error fetching mail from Store", + IOError => "IO Error" + ); +); + +pub use self::error::MailError; +pub use self::error::MailErrorKind; +pub use self::error::MapErrInto; + diff --git a/libimagmail/src/hasher.rs b/libimagmail/src/hasher.rs new file mode 100644 index 00000000..f0ced139 --- /dev/null +++ b/libimagmail/src/hasher.rs @@ -0,0 +1,64 @@ +use std::io::Read; +use std::path::PathBuf; + +use mailparse::{MailHeader, parse_mail}; + +use libimagref::hasher::Hasher; +use libimagref::hasher::DefaultHasher; +use libimagref::error::RefErrorKind as REK; +use libimagref::error::MapErrInto; +use libimagref::result::Result as RResult; +use libimagerror::into::IntoError; + +use error::MailErrorKind as MEK; + +pub struct MailHasher { + defaulthasher: DefaultHasher, +} + +impl MailHasher { + + pub fn new() -> MailHasher { + MailHasher { defaulthasher: DefaultHasher::new() } + } + +} + +impl Hasher for MailHasher { + + fn hash_name(&self) -> &'static str { + "default_mail_hasher" + } + + fn create_hash(&mut self, pb: &PathBuf, c: &mut R) -> RResult { + use filters::filter::Filter; + + let mut s = String::new(); + try!(c.read_to_string(&mut s).map_err_into(REK::UTF8Error).map_err_into(REK::IOError)); + + parse_mail(&s.as_bytes()) + .map_err(Box::new) + .map_err(|e| MEK::MailParsingError.into_error_with_cause(e)) + .map_err_into(REK::RefHashingError) + .and_then(|mail| { + let has_key = |hdr: &MailHeader, exp: &str| + hdr.get_key().map(|s| s == exp).unwrap_or(false); + + let subject_filter = |hdr: &MailHeader| has_key(hdr, "Subject"); + let from_filter = |hdr: &MailHeader| has_key(hdr, "From"); + let to_filter = |hdr: &MailHeader| has_key(hdr, "To"); + + let filter = subject_filter.or(from_filter).or(to_filter); + + let s : String = mail.headers + .iter() + .filter(|item| filter.filter(item)) + .filter_map(|hdr| hdr.get_value().ok()) // TODO: Do not hide error here + .collect::>() + .join(""); + + self.defaulthasher.create_hash(pb, &mut s.as_bytes()) + }) + } + +} diff --git a/libimagmail/src/iter.rs b/libimagmail/src/iter.rs new file mode 100644 index 00000000..365a0928 --- /dev/null +++ b/libimagmail/src/iter.rs @@ -0,0 +1,37 @@ +//! Module for the MailIter +//! +//! MailIter is a iterator which takes an Iterator that yields `Ref` and yields itself +//! `Result`, where `Err(_)` is returned if the Ref is not a Mail or parsing of the +//! referenced mail file failed. +//! + +use mail::Mail; +use result::Result; + +use libimagref::reference::Ref; + +use std::marker::PhantomData; + +struct MailIter<'a, I: 'a + Iterator>> { + _marker: PhantomData<&'a I>, + i: I, +} + +impl<'a, I: Iterator>> MailIter<'a, I> { + + pub fn new(i: I) -> MailIter<'a, I> { + MailIter { _marker: PhantomData, i: i } + } + +} + +impl<'a, I: Iterator>> Iterator for MailIter<'a, I> { + + type Item = Result>; + + fn next(&mut self) -> Option>> { + self.i.next().map(Mail::from_ref) + } + +} + diff --git a/libimagmail/src/lib.rs b/libimagmail/src/lib.rs new file mode 100644 index 00000000..1d88a069 --- /dev/null +++ b/libimagmail/src/lib.rs @@ -0,0 +1,16 @@ +#[macro_use] extern crate log; +extern crate mailparse; +extern crate semver; +extern crate toml; +extern crate filters; + +#[macro_use] extern crate libimagerror; +extern crate libimagstore; +extern crate libimagref; + +pub mod error; +pub mod hasher; +pub mod iter; +pub mod mail; +pub mod result; + diff --git a/libimagmail/src/mail.rs b/libimagmail/src/mail.rs new file mode 100644 index 00000000..b49e0ed5 --- /dev/null +++ b/libimagmail/src/mail.rs @@ -0,0 +1,120 @@ +use std::result::Result as RResult; +use std::path::Path; +use std::path::PathBuf; +use std::fs::File; +use std::io::Read; + +use libimagstore::store::{FileLockEntry, Store}; +use libimagref::reference::Ref; +use libimagref::flags::RefFlags; + +use mailparse::{MailParseError, ParsedMail, parse_mail}; + +use hasher::MailHasher; +use result::Result; +use error::{MapErrInto, MailErrorKind as MEK}; + +struct Buffer(String); + +impl Buffer { + pub fn parsed<'a>(&'a self) -> RResult, MailParseError> { + parse_mail(self.0.as_bytes()) + } +} + +impl From for Buffer { + fn from(data: String) -> Buffer { + Buffer(data) + } +} + +pub struct Mail<'a>(Ref<'a>, Buffer); + +impl<'a> Mail<'a> { + + /// Imports a mail from the Path passed + pub fn import_from_path>(store: &Store, p: P) -> Result { + let h = MailHasher::new(); + let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false); + let p = PathBuf::from(p.as_ref()); + + Ref::create_with_hasher(store, p, f, h) + .map_err_into(MEK::RefCreationError) + .and_then(|reference| { + reference.fs_file() + .map_err_into(MEK::RefHandlingError) + .and_then(|path| File::open(path).map_err_into(MEK::IOError)) + .and_then(|mut file| { + let mut s = String::new(); + file.read_to_string(&mut s) + .map(|_| s) + .map_err_into(MEK::IOError) + }) + .map(Buffer::from) + .map(|buffer| Mail(reference, buffer)) + }) + } + + /// Opens a mail by the passed hash + pub fn open>(store: &Store, hash: S) -> Result> { + Ref::get_by_hash(store, String::from(hash.as_ref())) + .map_err_into(MEK::FetchByHashError) + .map_err_into(MEK::FetchError) + .and_then(|o| match o { + Some(r) => Mail::from_ref(r).map(Some), + None => Ok(None), + }) + + } + + /// Implement me as TryFrom as soon as it is stable + pub fn from_ref(r: Ref<'a>) -> Result { + r.fs_file() + .map_err_into(MEK::RefHandlingError) + .and_then(|path| File::open(path).map_err_into(MEK::IOError)) + .and_then(|mut file| { + let mut s = String::new(); + file.read_to_string(&mut s) + .map(|_| s) + .map_err_into(MEK::IOError) + }) + .map(Buffer::from) + .map(|buffer| Mail(r, buffer)) + } + + pub fn get_field(&self, field: &str) -> Result> { + use mailparse::MailHeader; + + self.1 + .parsed() + .map_err_into(MEK::MailParsingError) + .map(|parsed| { + parsed.headers + .iter() + .filter(|hdr| hdr.get_key().map(|n| n == field).unwrap_or(false)) + .next() + .and_then(|field| field.get_value().ok()) + }) + } + + pub fn get_from(&self) -> Result> { + self.get_field("From") + } + + pub fn get_to(&self) -> Result> { + self.get_field("To") + } + + pub fn get_subject(&self) -> Result> { + self.get_field("Subject") + } + + pub fn get_message_id(&self) -> Result> { + self.get_field("Message-ID") + } + + pub fn get_in_reply_to(&self) -> Result> { + self.get_field("In-Reply-To") + } + +} diff --git a/libimagmail/src/result.rs b/libimagmail/src/result.rs new file mode 100644 index 00000000..e7715513 --- /dev/null +++ b/libimagmail/src/result.rs @@ -0,0 +1,6 @@ +use std::result::Result as RResult; + +use error::MailError; + +pub type Result = RResult; + diff --git a/libimagref/src/error.rs b/libimagref/src/error.rs index b1e5e2c5..a133e8a5 100644 --- a/libimagref/src/error.rs +++ b/libimagref/src/error.rs @@ -41,7 +41,9 @@ generate_error_module!( RefTargetPermissionError => "Ref Target permissions insufficient for referencing", RefTargetCannotBeHashed => "Ref Target cannot be hashed (is it a directory?)", RefTargetFileCannotBeOpened => "Ref Target File cannot be open()ed", - RefTargetCannotReadPermissions => "Ref Target: Cannot read permissions" + RefTargetCannotReadPermissions => "Ref Target: Cannot read permissions", + + RefHashingError => "Error while hashing" ); );