crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

commit 6206bd7a0af220b058e239b103cfbae13c39dda1
parent d85b511e2e19baf2134906cbdfbd2ab38dea428e
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Wed, 12 Jan 2022 17:43:57 -0800

oops forgot maildir file

Diffstat:
Asrc/maildir.rs | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 307 insertions(+), 0 deletions(-)

diff --git a/src/maildir.rs b/src/maildir.rs @@ -0,0 +1,307 @@ +// This file is licensed under the terms of 0BSD: +// +// Permission to use, copy, modify, and/or distribute this software for any purpose with or without +// fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +// SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +// OF THIS SOFTWARE. + +// Vendoring https://github.com/staktrace/maildir +// Could cut down a bit more +use std::error; +use std::fmt; +use std::fs; +use std::io::prelude::*; +use std::ops::Deref; +use std::path::PathBuf; + +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, + } + } +} + +/// This struct represents a single email message inside +/// the maildir. Creation of the struct does not automatically +/// load the content of the email file into memory - however, +/// that may happen upon calling functions that require parsing +/// the email. +pub struct MailEntry { + id: String, + flags: String, + path: PathBuf, + data: MailData, +} + +impl MailEntry { + 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 path(&self) -> &PathBuf { + &self.path + } +} + +enum Subfolder { + New, + Cur, +} + +/// An iterator over the email messages in a particular +/// maildir subfolder (either `cur` or `new`). This iterator +/// produces a `std::io::Result<MailEntry>`, which can be an +/// `Err` if an error was encountered while trying to read +/// file system properties on a particular entry, or if an +/// invalid file was found in the maildir. Files starting with +/// a dot (.) character in the maildir folder are ignored. +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 { + // we need to skip over files starting with a '.' + 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) + } +} + +/// The main entry point for this library. This struct can be +/// instantiated from a path using the `from` implementations. +/// The path passed in to the `from` should be the root of the +/// maildir (the folder containing `cur`, `new`, and `tmp`). +pub struct Maildir { + path: PathBuf, +} + +impl Maildir { + /// Returns an iterator over the messages inside the `new` + /// maildir folder. The order of messages in the iterator + /// is not specified, and is not guaranteed to be stable + /// over multiple invocations of this method. + pub fn list_new(&self) -> MailEntries { + MailEntries::new(self.path.clone(), Subfolder::New) + } + + /// Returns an iterator over the messages inside the `cur` + /// maildir folder. The order of messages in the iterator + /// is not specified, and is not guaranteed to be stable + /// over multiple invocations of this method. + pub fn list_cur(&self) -> MailEntries { + MailEntries::new(self.path.clone(), Subfolder::Cur) + } +} + +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)) + } +}