#[cfg(feature = "mmap")]
extern crate memmap;
use std::error;
use std::fmt;
use std::fs;
use std::io::prelude::*;
use std::ops::Deref;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::time;
use mailparse::*;
#[derive(Debug)]
pub enum MailEntryError {
IOError(std::io::Error),
ParseError(MailParseError),
DateError(&'static str),
}
impl fmt::Display for MailEntryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MailEntryError::IOError(ref err) => write!(f, "IO error: {}", err),
MailEntryError::ParseError(ref err) => write!(f, "Parse error: {}", err),
MailEntryError::DateError(ref msg) => write!(f, "Date error: {}", msg),
}
}
}
impl error::Error for MailEntryError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
MailEntryError::IOError(ref err) => Some(err),
MailEntryError::ParseError(ref err) => Some(err),
MailEntryError::DateError(_) => None,
}
}
}
impl From<std::io::Error> for MailEntryError {
fn from(err: std::io::Error) -> MailEntryError {
MailEntryError::IOError(err)
}
}
impl From<MailParseError> for MailEntryError {
fn from(err: MailParseError) -> MailEntryError {
MailEntryError::ParseError(err)
}
}
impl From<&'static str> for MailEntryError {
fn from(err: &'static str) -> MailEntryError {
MailEntryError::DateError(err)
}
}
enum MailData {
None,
#[cfg(not(feature = "mmap"))]
Bytes(Vec<u8>),
#[cfg(feature = "mmap")]
File(memmap::Mmap),
}
impl MailData {
fn is_none(&self) -> bool {
match self {
MailData::None => true,
_ => false,
}
}
}
pub struct MailEntry {
id: String,
flags: String,
path: PathBuf,
data: MailData,
}
impl MailEntry {
pub fn id(&self) -> &str {
&self.id
}
fn read_data(&mut self) -> std::io::Result<()> {
if self.data.is_none() {
#[cfg(feature = "mmap")]
{
let f = fs::File::open(&self.path)?;
let mmap = unsafe { memmap::MmapOptions::new().map(&f)? };
self.data = MailData::File(mmap);
}
#[cfg(not(feature = "mmap"))]
{
let mut f = fs::File::open(&self.path)?;
let mut d = Vec::<u8>::new();
f.read_to_end(&mut d)?;
self.data = MailData::Bytes(d);
}
}
Ok(())
}
pub fn parsed(&mut self) -> Result<ParsedMail, MailEntryError> {
self.read_data()?;
match self.data {
MailData::None => panic!("read_data should have returned an Err!"),
#[cfg(not(feature = "mmap"))]
MailData::Bytes(ref b) => parse_mail(b).map_err(MailEntryError::ParseError),
#[cfg(feature = "mmap")]
MailData::File(ref m) => parse_mail(m).map_err(MailEntryError::ParseError),
}
}
pub fn headers(&mut self) -> Result<Vec<MailHeader>, MailEntryError> {
self.read_data()?;
let headers = match self.data {
MailData::None => panic!("read_data should have returned an Err!"),
#[cfg(not(feature = "mmap"))]
MailData::Bytes(ref b) => parse_headers(b),
#[cfg(feature = "mmap")]
MailData::File(ref m) => parse_headers(m),
};
headers.map(|(v, _)| v).map_err(MailEntryError::ParseError)
}
pub fn received(&mut self) -> Result<i64, MailEntryError> {
self.read_data()?;
let headers = self.headers()?;
let received = headers.get_first_value("Received");
match received {
Some(v) => v
.rsplit(';')
.nth(0)
.ok_or_else(|| MailEntryError::DateError("Unable to split Received header"))
.and_then(|ts| dateparse(ts).map_err(MailEntryError::from)),
None => Err("No Received header found")?,
}
}
pub fn date(&mut self) -> Result<i64, MailEntryError> {
self.read_data()?;
let headers = self.headers()?;
let date = headers.get_first_value("Date");
match date {
Some(ts) => dateparse(&ts).map_err(MailEntryError::from),
None => Err("No Date header found")?,
}
}
pub fn flags(&self) -> &str {
&self.flags
}
pub fn is_draft(&self) -> bool {
self.flags.contains('D')
}
pub fn is_flagged(&self) -> bool {
self.flags.contains('F')
}
pub fn is_passed(&self) -> bool {
self.flags.contains('P')
}
pub fn is_replied(&self) -> bool {
self.flags.contains('R')
}
pub fn is_seen(&self) -> bool {
self.flags.contains('S')
}
pub fn is_trashed(&self) -> bool {
self.flags.contains('T')
}
pub fn path(&self) -> &PathBuf {
&self.path
}
}
enum Subfolder {
New,
Cur,
}
pub struct MailEntries {
path: PathBuf,
subfolder: Subfolder,
readdir: Option<fs::ReadDir>,
}
impl MailEntries {
fn new(path: PathBuf, subfolder: Subfolder) -> MailEntries {
MailEntries {
path,
subfolder,
readdir: None,
}
}
}
impl Iterator for MailEntries {
type Item = std::io::Result<MailEntry>;
fn next(&mut self) -> Option<std::io::Result<MailEntry>> {
if self.readdir.is_none() {
let mut dir_path = self.path.clone();
dir_path.push(match self.subfolder {
Subfolder::New => "new",
Subfolder::Cur => "cur",
});
self.readdir = match fs::read_dir(dir_path) {
Err(_) => return None,
Ok(v) => Some(v),
};
}
loop {
let dir_entry = self.readdir.iter_mut().next().unwrap().next();
let result = dir_entry.map(|e| {
let entry = e?;
let filename = String::from(entry.file_name().to_string_lossy().deref());
if filename.starts_with('.') {
return Ok(None);
}
let (id, flags) = match self.subfolder {
Subfolder::New => (Some(filename.as_str()), Some("")),
Subfolder::Cur => {
let mut iter = filename.split(":2,");
(iter.next(), iter.next())
}
};
if id.is_none() || flags.is_none() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Non-maildir file found in maildir",
));
}
Ok(Some(MailEntry {
id: String::from(id.unwrap()),
flags: String::from(flags.unwrap()),
path: entry.path(),
data: MailData::None,
}))
});
return match result {
None => None,
Some(Err(e)) => Some(Err(e)),
Some(Ok(None)) => continue,
Some(Ok(Some(v))) => Some(Ok(v)),
};
}
}
}
#[derive(Debug)]
pub enum MaildirError {
Io(std::io::Error),
Utf8(std::str::Utf8Error),
Time(std::time::SystemTimeError),
}
impl fmt::Display for MaildirError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use MaildirError::*;
match *self {
Io(ref e) => write!(f, "IO Error: {}", e),
Utf8(ref e) => write!(f, "UTF8 Encoding Error: {}", e),
Time(ref e) => write!(f, "Time Error: {}", e),
}
}
}
impl error::Error for MaildirError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
use MaildirError::*;
match *self {
Io(ref e) => Some(e),
Utf8(ref e) => Some(e),
Time(ref e) => Some(e),
}
}
}
impl From<std::io::Error> for MaildirError {
fn from(e: std::io::Error) -> MaildirError {
MaildirError::Io(e)
}
}
impl From<std::str::Utf8Error> for MaildirError {
fn from(e: std::str::Utf8Error) -> MaildirError {
MaildirError::Utf8(e)
}
}
impl From<std::time::SystemTimeError> for MaildirError {
fn from(e: std::time::SystemTimeError) -> MaildirError {
MaildirError::Time(e)
}
}
pub struct Maildir {
path: PathBuf,
}
impl Maildir {
pub fn path(&self) -> &Path {
&self.path
}
pub fn count_new(&self) -> usize {
self.list_new().count()
}
pub fn count_cur(&self) -> usize {
self.list_cur().count()
}
pub fn list_new(&self) -> MailEntries {
MailEntries::new(self.path.clone(), Subfolder::New)
}
pub fn list_cur(&self) -> MailEntries {
MailEntries::new(self.path.clone(), Subfolder::Cur)
}
pub fn move_new_to_cur(&self, id: &str) -> std::io::Result<()> {
self.move_new_to_cur_with_flags(id, "")
}
pub fn move_new_to_cur_with_flags(&self, id: &str, flags: &str) -> std::io::Result<()> {
let mut src = self.path.clone();
src.push("new");
src.push(id);
let mut dst = self.path.clone();
dst.push("cur");
dst.push(String::from(id) + ":2," + &Self::normalize_flags(flags));
fs::rename(src, dst)
}
pub fn find(&self, id: &str) -> Option<MailEntry> {
let filter = |entry: &std::io::Result<MailEntry>| match *entry {
Err(_) => false,
Ok(ref e) => e.id() == id,
};
self.list_new()
.find(&filter)
.or_else(|| self.list_cur().find(&filter))
.map(|e| e.unwrap())
}
fn normalize_flags(flags: &str) -> String {
let mut flag_chars = flags.chars().collect::<Vec<char>>();
flag_chars.sort();
flag_chars.dedup();
flag_chars.into_iter().collect()
}
fn update_flags<F>(&self, id: &str, flag_op: F) -> std::io::Result<()>
where
F: Fn(&str) -> String,
{
let filter = |entry: &std::io::Result<MailEntry>| match *entry {
Err(_) => false,
Ok(ref e) => e.id() == id,
};
match self.list_cur().find(&filter).map(|e| e.unwrap()) {
Some(m) => {
let src = m.path();
let mut dst = m.path().clone();
dst.pop();
dst.push(String::from(m.id()) + ":2," + &flag_op(m.flags()));
fs::rename(src, dst)
}
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Mail entry not found",
)),
}
}
pub fn set_flags(&self, id: &str, flags: &str) -> std::io::Result<()> {
self.update_flags(id, |_old_flags| Self::normalize_flags(flags))
}
pub fn add_flags(&self, id: &str, flags: &str) -> std::io::Result<()> {
let flag_merge = |old_flags: &str| {
let merged = String::from(old_flags) + flags;
Self::normalize_flags(&merged)
};
self.update_flags(id, &flag_merge)
}
pub fn remove_flags(&self, id: &str, flags: &str) -> std::io::Result<()> {
let flag_strip =
|old_flags: &str| old_flags.chars().filter(|c| !flags.contains(*c)).collect();
self.update_flags(id, &flag_strip)
}
pub fn delete(&self, id: &str) -> std::io::Result<()> {
match self.find(id) {
Some(m) => fs::remove_file(m.path()),
None => Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Mail entry not found",
)),
}
}
pub fn create_dirs(&self) -> std::io::Result<()> {
let mut path = self.path.clone();
for d in &["cur", "new", "tmp"] {
path.push(d);
fs::create_dir_all(path.as_path())?;
path.pop();
}
Ok(())
}
pub fn store_new(&self, data: &[u8]) -> std::result::Result<String, MaildirError> {
self.store(Subfolder::New, data, "")
}
pub fn store_cur_with_flags(
&self,
data: &[u8],
flags: &str,
) -> std::result::Result<String, MaildirError> {
self.store(
Subfolder::Cur,
data,
&format!(":2,{}", Self::normalize_flags(flags)),
)
}
fn store(
&self,
subfolder: Subfolder,
data: &[u8],
info: &str,
) -> std::result::Result<String, MaildirError> {
let pid = std::process::id();
let hostname = gethostname::gethostname();
let mut ts = time::SystemTime::now().duration_since(time::UNIX_EPOCH)?;
let mut tmppath = self.path.clone();
tmppath.push("tmp");
loop {
tmppath.push(format!(
"{}.M{}P{}.{}",
ts.as_secs(),
ts.subsec_nanos(),
pid,
hostname.to_string_lossy()
));
if !tmppath.exists() {
break;
}
tmppath.pop();
ts += time::Duration::from_millis(10);
}
let mut file = std::fs::File::create(tmppath.to_owned())?;
file.write_all(data)?;
file.sync_all()?;
let meta = file.metadata()?;
let mut newpath = self.path.clone();
newpath.push(match subfolder {
Subfolder::New => "new",
Subfolder::Cur => "cur",
});
let id = format!(
"{}.M{}P{}V{}I{}.{},S={}",
ts.as_secs(),
ts.subsec_nanos(),
pid,
meta.dev(),
meta.ino(),
hostname.to_string_lossy(),
meta.size(),
);
newpath.push(format!("{}{}", id, info));
std::fs::rename(tmppath, newpath)?;
Ok(id)
}
}
impl From<PathBuf> for Maildir {
fn from(p: PathBuf) -> Maildir {
Maildir { path: p }
}
}
impl From<String> for Maildir {
fn from(s: String) -> Maildir {
Maildir::from(PathBuf::from(s))
}
}
impl<'a> From<&'a str> for Maildir {
fn from(s: &str) -> Maildir {
Maildir::from(PathBuf::from(s))
}
}