Skip to main content

libimagmail/
mail.rs

1//
2// imag - the personal information management suite for the commandline
3// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; version
8// 2.1 of the License.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18//
19
20use failure::Fallible as Result;
21use failure::ResultExt;
22use failure::Error;
23
24use libimagstore::store::Entry;
25use libimagentryutil::isa::Is;
26use libimagentryutil::isa::IsKindHeaderPathProvider;
27use libimagentryref::reference::Config as RefConfig;
28use libimagentryref::reference::{Ref, RefFassade};
29
30provide_kindflag_path!(pub IsMail, "mail.is_mail");
31
32pub trait Mail : RefFassade {
33    fn is_mail(&self)                                       -> Result<bool>;
34    fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>>;
35    fn get_from(&self, refconfig: &RefConfig)               -> Result<Option<String>>;
36    fn get_to(&self, refconfig: &RefConfig)                 -> Result<Option<String>>;
37    fn get_subject(&self, refconfig: &RefConfig)            -> Result<Option<String>>;
38    fn get_message_id(&self, refconfig: &RefConfig)         -> Result<Option<String>>;
39    fn get_in_reply_to(&self, refconfig: &RefConfig)        -> Result<Option<String>>;
40}
41
42impl Mail for Entry {
43
44    fn is_mail(&self) -> Result<bool> {
45        self.is::<IsMail>()
46    }
47
48    /// Get a value of a single field of the mail file
49    fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>> {
50        use std::fs::read_to_string;
51        use crate::hasher::MailHasher;
52
53        debug!("Getting field in mail: {:?}", field);
54        let mail_file_location = self.as_ref_with_hasher::<MailHasher>().get_path(refconfig)?;
55
56        match ::mailparse::parse_mail(read_to_string(mail_file_location.as_path())?.as_bytes())
57            .context(format_err!("Cannot parse Email {}", mail_file_location.display()))?
58            .headers
59            .into_iter()
60            .filter_map(|hdr| {
61                match hdr.get_key()
62                    .context(format_err!("Cannot fetch key '{}' from Email {}", field, mail_file_location.display()))
63                    .map_err(Error::from)
64                {
65                    Ok(k) => if k == field {
66                        Some(Ok(hdr))
67                    } else {
68                        None
69                    },
70                    Err(e) => Some(Err(e)),
71                }
72            })
73            .next()
74        {
75            None          => Ok(None),
76            Some(Err(e))  => Err(e),
77            Some(Ok(hdr)) => Ok(Some(hdr.get_value()?))
78        }
79    }
80
81    /// Get a value of the `From` field of the mail file
82    ///
83    /// # Note
84    ///
85    /// Use `Mail::mail_header()` if you need to read more than one field.
86    fn get_from(&self, refconfig: &RefConfig) -> Result<Option<String>> {
87        self.get_field(refconfig, "From")
88    }
89
90    /// Get a value of the `To` field of the mail file
91    ///
92    /// # Note
93    ///
94    /// Use `Mail::mail_header()` if you need to read more than one field.
95    fn get_to(&self, refconfig: &RefConfig) -> Result<Option<String>> {
96        self.get_field(refconfig, "To")
97    }
98
99    /// Get a value of the `Subject` field of the mail file
100    ///
101    /// # Note
102    ///
103    /// Use `Mail::mail_header()` if you need to read more than one field.
104    fn get_subject(&self, refconfig: &RefConfig) -> Result<Option<String>> {
105        self.get_field(refconfig, "Subject")
106    }
107
108    /// Get a value of the `Message-ID` field of the mail file
109    ///
110    /// # Note
111    ///
112    /// Use `Mail::mail_header()` if you need to read more than one field.
113    fn get_message_id(&self, refconfig: &RefConfig) -> Result<Option<String>> {
114        self.get_field(refconfig, "Message-ID")
115            .map(|o| o.map(crate::util::strip_message_delimiters))
116    }
117
118    /// Get a value of the `In-Reply-To` field of the mail file
119    ///
120    /// # Note
121    ///
122    /// Use `Mail::mail_header()` if you need to read more than one field.
123    fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result<Option<String>> {
124        self.get_field(refconfig, "In-Reply-To")
125    }
126
127}
128
129#[derive(Debug)]
130pub struct MailHeader<'a>(Vec<::mailparse::MailHeader<'a>>);
131
132impl<'a> From<Vec<::mailparse::MailHeader<'a>>> for MailHeader<'a> {
133    fn from(mh: Vec<::mailparse::MailHeader<'a>>) -> Self {
134        MailHeader(mh)
135    }
136}
137
138impl<'a> MailHeader<'a> {
139    /// Get a value of a single field of the mail file
140    pub fn get_field(&self, field: &str) -> Result<Option<String>> {
141        match self.0
142            .iter()
143            .filter_map(|hdr| {
144                match hdr.get_key()
145                    .context(format_err!("Cannot get field {}", field))
146                    .map_err(Error::from)
147                {
148                    Ok(key) => if key == field {
149                        Some(Ok(hdr))
150                    } else {
151                        None
152                    },
153                    Err(e) => Some(Err(e))
154                }
155            })
156            .next()
157        {
158            None          => Ok(None),
159            Some(Err(e))  => Err(e),
160            Some(Ok(hdr)) => Ok(Some(hdr.get_value()?))
161        }
162    }
163
164    /// Get a value of the `From` field of the mail file
165    pub fn get_from(&self) -> Result<Option<String>> {
166        self.get_field("From")
167    }
168
169    /// Get a value of the `To` field of the mail file
170    pub fn get_to(&self) -> Result<Option<String>> {
171        self.get_field("To")
172    }
173
174    /// Get a value of the `Subject` field of the mail file
175    pub fn get_subject(&self) -> Result<Option<String>> {
176        self.get_field("Subject")
177    }
178
179    /// Get a value of the `Message-ID` field of the mail file
180    pub fn get_message_id(&self) -> Result<Option<String>> {
181        self.get_field("Message-ID")
182    }
183
184    /// Get a value of the `In-Reply-To` field of the mail file
185    pub fn get_in_reply_to(&self) -> Result<Option<String>> {
186        self.get_field("In-Reply-To")
187    }
188
189    // TODO: Offer functionality to load and parse mail _once_ from disk, and then use helper object
190    // to offer access to header fields and content.
191    //
192    // With the existing functionality, one has to open-parse-close the file all the time, which is
193    // _NOT_ optimal.
194}