crabmail

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

maildir.rs (7559B) - raw


      1 // This file is licensed under the terms of 0BSD:
      2 //
      3 // Permission to use, copy, modify, and/or distribute this software for any purpose with or without
      4 // fee is hereby granted.
      5 //
      6 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
      7 // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
      8 // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
      9 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
     10 // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
     11 // OF THIS SOFTWARE.
     12 
     13 // Vendoring https://github.com/staktrace/maildir
     14 // TODO cleanup
     15 use std::error;
     16 use std::fmt;
     17 use std::fs;
     18 use std::ops::Deref;
     19 use std::path::PathBuf;
     20 
     21 #[derive(Debug)]
     22 pub enum MailEntryError {
     23     IOError(std::io::Error),
     24     DateError(&'static str),
     25 }
     26 
     27 impl fmt::Display for MailEntryError {
     28     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     29         match *self {
     30             MailEntryError::IOError(ref err) => write!(f, "IO error: {}", err),
     31             MailEntryError::DateError(ref msg) => write!(f, "Date error: {}", msg),
     32         }
     33     }
     34 }
     35 
     36 impl error::Error for MailEntryError {
     37     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
     38         match *self {
     39             MailEntryError::IOError(ref err) => Some(err),
     40             MailEntryError::DateError(_) => None,
     41         }
     42     }
     43 }
     44 
     45 impl From<std::io::Error> for MailEntryError {
     46     fn from(err: std::io::Error) -> MailEntryError {
     47         MailEntryError::IOError(err)
     48     }
     49 }
     50 
     51 impl From<&'static str> for MailEntryError {
     52     fn from(err: &'static str) -> MailEntryError {
     53         MailEntryError::DateError(err)
     54     }
     55 }
     56 
     57 /// This struct represents a single email message inside
     58 /// the maildir. Creation of the struct does not automatically
     59 /// load the content of the email file into memory - however,
     60 /// that may happen upon calling functions that require parsing
     61 /// the email.
     62 pub struct MailEntry {
     63     #[allow(dead_code)]
     64     id: String,
     65     #[allow(dead_code)]
     66     flags: String,
     67     path: PathBuf,
     68 }
     69 
     70 impl MailEntry {
     71     pub fn path(&self) -> &PathBuf {
     72         &self.path
     73     }
     74 }
     75 
     76 enum Subfolder {
     77     New,
     78     Cur,
     79 }
     80 
     81 /// An iterator over the email messages in a particular
     82 /// maildir subfolder (either `cur` or `new`). This iterator
     83 /// produces a `std::io::Result<MailEntry>`, which can be an
     84 /// `Err` if an error was encountered while trying to read
     85 /// file system properties on a particular entry, or if an
     86 /// invalid file was found in the maildir. Files starting with
     87 /// a dot (.) character in the maildir folder are ignored.
     88 pub struct MailEntries {
     89     path: PathBuf,
     90     subfolder: Subfolder,
     91     readdir: Option<fs::ReadDir>,
     92 }
     93 
     94 impl MailEntries {
     95     fn new(path: PathBuf, subfolder: Subfolder) -> MailEntries {
     96         MailEntries {
     97             path,
     98             subfolder,
     99             readdir: None,
    100         }
    101     }
    102 }
    103 
    104 impl Iterator for MailEntries {
    105     type Item = std::io::Result<MailEntry>;
    106 
    107     fn next(&mut self) -> Option<std::io::Result<MailEntry>> {
    108         if self.readdir.is_none() {
    109             let mut dir_path = self.path.clone();
    110             dir_path.push(match self.subfolder {
    111                 Subfolder::New => "new",
    112                 Subfolder::Cur => "cur",
    113             });
    114             self.readdir = match fs::read_dir(dir_path) {
    115                 Err(_) => return None,
    116                 Ok(v) => Some(v),
    117             };
    118         }
    119 
    120         loop {
    121             // we need to skip over files starting with a '.'
    122             let dir_entry = self.readdir.iter_mut().next().unwrap().next();
    123             let result = dir_entry.map(|e| {
    124                 let entry = e?;
    125                 let filename = String::from(entry.file_name().to_string_lossy().deref());
    126                 if filename.starts_with('.') {
    127                     return Ok(None);
    128                 }
    129                 let (id, flags) = match self.subfolder {
    130                     Subfolder::New => (Some(filename.as_str()), Some("")),
    131                     Subfolder::Cur => {
    132                         let mut iter = filename.split(":2,");
    133                         (iter.next(), iter.next())
    134                     }
    135                 };
    136                 if id.is_none() || flags.is_none() {
    137                     return Err(std::io::Error::new(
    138                         std::io::ErrorKind::InvalidData,
    139                         "Non-maildir file found in maildir",
    140                     ));
    141                 }
    142                 Ok(Some(MailEntry {
    143                     id: String::from(id.unwrap()),
    144                     flags: String::from(flags.unwrap()),
    145                     path: entry.path(),
    146                 }))
    147             });
    148             return match result {
    149                 None => None,
    150                 Some(Err(e)) => Some(Err(e)),
    151                 Some(Ok(None)) => continue,
    152                 Some(Ok(Some(v))) => Some(Ok(v)),
    153             };
    154         }
    155     }
    156 }
    157 
    158 #[derive(Debug)]
    159 pub enum MaildirError {
    160     Io(std::io::Error),
    161     Utf8(std::str::Utf8Error),
    162     Time(std::time::SystemTimeError),
    163 }
    164 
    165 impl fmt::Display for MaildirError {
    166     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    167         use MaildirError::*;
    168 
    169         match *self {
    170             Io(ref e) => write!(f, "IO Error: {}", e),
    171             Utf8(ref e) => write!(f, "UTF8 Encoding Error: {}", e),
    172             Time(ref e) => write!(f, "Time Error: {}", e),
    173         }
    174     }
    175 }
    176 
    177 impl error::Error for MaildirError {
    178     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
    179         use MaildirError::*;
    180 
    181         match *self {
    182             Io(ref e) => Some(e),
    183             Utf8(ref e) => Some(e),
    184             Time(ref e) => Some(e),
    185         }
    186     }
    187 }
    188 
    189 impl From<std::io::Error> for MaildirError {
    190     fn from(e: std::io::Error) -> MaildirError {
    191         MaildirError::Io(e)
    192     }
    193 }
    194 impl From<std::str::Utf8Error> for MaildirError {
    195     fn from(e: std::str::Utf8Error) -> MaildirError {
    196         MaildirError::Utf8(e)
    197     }
    198 }
    199 impl From<std::time::SystemTimeError> for MaildirError {
    200     fn from(e: std::time::SystemTimeError) -> MaildirError {
    201         MaildirError::Time(e)
    202     }
    203 }
    204 
    205 /// The main entry point for this library. This struct can be
    206 /// instantiated from a path using the `from` implementations.
    207 /// The path passed in to the `from` should be the root of the
    208 /// maildir (the folder containing `cur`, `new`, and `tmp`).
    209 pub struct Maildir {
    210     path: PathBuf,
    211 }
    212 
    213 impl Maildir {
    214     /// Returns an iterator over the messages inside the `new`
    215     /// maildir folder. The order of messages in the iterator
    216     /// is not specified, and is not guaranteed to be stable
    217     /// over multiple invocations of this method.
    218     pub fn list_new(&self) -> MailEntries {
    219         MailEntries::new(self.path.clone(), Subfolder::New)
    220     }
    221 
    222     /// Returns an iterator over the messages inside the `cur`
    223     /// maildir folder. The order of messages in the iterator
    224     /// is not specified, and is not guaranteed to be stable
    225     /// over multiple invocations of this method.
    226     pub fn list_cur(&self) -> MailEntries {
    227         MailEntries::new(self.path.clone(), Subfolder::Cur)
    228     }
    229 }
    230 
    231 impl From<PathBuf> for Maildir {
    232     fn from(p: PathBuf) -> Maildir {
    233         Maildir { path: p }
    234     }
    235 }
    236 
    237 impl From<String> for Maildir {
    238     fn from(s: String) -> Maildir {
    239         Maildir::from(PathBuf::from(s))
    240     }
    241 }
    242 
    243 impl<'a> From<&'a str> for Maildir {
    244     fn from(s: &str) -> Maildir {
    245         Maildir::from(PathBuf::from(s))
    246     }
    247 }