Skip to main content

eml_codec/imf/
mod.rs

1/// Parse and represent IMF (Internet Message Format) headers (RFC822, RFC5322)
2#[cfg(feature = "arbitrary")]
3use arbitrary::Arbitrary;
4pub mod address;
5pub mod datetime;
6pub mod field;
7pub mod identification;
8pub mod mailbox;
9pub mod mime;
10pub mod trace;
11
12use bounded_static::ToStatic;
13use std::collections::HashSet;
14
15#[cfg(feature = "arbitrary")]
16use crate::fuzz_eq::FuzzEq;
17use crate::i18n::ContainsUtf8;
18use crate::imf::address::AddressRef;
19use crate::imf::datetime::DateTime;
20use crate::imf::field::{Entry, Field};
21use crate::imf::identification::MessageID;
22use crate::imf::mailbox::{MailboxList, MailboxRef};
23use crate::imf::mime::Version;
24use crate::imf::trace::ReturnPath;
25use crate::text::misc_token::{PhraseList, Unstructured};
26
27#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
28#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
29pub struct Imf<'a> {
30    // 3.6.1.  The Origination Date Field
31    pub date: DateTimeOpt,
32
33    // 3.6.2.  Originator Fields
34    pub from: From<'a>, // combines 'from' and 'sender'
35    pub reply_to: Vec<AddressRef<'a>>,
36
37    // 3.6.3.  Destination Address Fields
38    pub to: Vec<AddressRef<'a>>,
39    pub cc: Vec<AddressRef<'a>>,
40    pub bcc: Option<Vec<AddressRef<'a>>>,
41
42    // 3.6.4.  Identification Fields
43    pub msg_id: Option<MessageID<'a>>,
44    pub in_reply_to: Vec<MessageID<'a>>,
45    pub references: Vec<MessageID<'a>>,
46
47    // 3.6.5.  Informational Fields
48    pub subject: Option<Unstructured<'a>>,
49    pub comments: Vec<Unstructured<'a>>,
50    pub keywords: Vec<PhraseList<'a>>,
51
52    // 3.6.6 Not implemented
53
54    // 3.6.7 Trace Fields
55    pub trace: Vec<TraceField<'a>>,
56
57    // MIME
58    pub mime_version: Option<Version>,
59}
60
61#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
62#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
63pub enum DateTimeOpt {
64    Some(DateTime),
65    // Following RFC5322, it is invalid for the Date header to be missing
66    // However, IMAP RFCs allow the Date header to be missing (e.g. for draft
67    // emails) (in particular, RFC9051 "IMAP4rev2" makes it clear in ยง7.5.2
68    // ENVELOPE).
69    InvalidMissing,
70}
71
72#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
73#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
74pub enum From<'a> {
75    Single {
76        from: MailboxRef<'a>,
77        sender: Option<MailboxRef<'a>>,
78    },
79    Multiple {
80        from: MailboxList<'a>, // must contain at least two elements
81        sender: MailboxRef<'a>,
82    },
83    // Following RFC5322, it is invalid for the From header to be missing.
84    // However, IMAP RFCs allow it to be missing (e.g. for draft emails).
85    // This also represents the case where both From and Sender are missing,
86    // as `InvalidMissingFrom { sender: None }`.
87    InvalidMissingFrom {
88        sender: Option<MailboxRef<'a>>,
89    },
90    // Following RFC5322, it is invalid for the Sender header to be missing
91    // if there are more than one From mailbox.
92    // However, IMAP RFCs allow it to be missing (e.g. for draft emails).
93    InvalidMissingSender {
94        from: MailboxList<'a>, // must contain at least two elements
95    },
96}
97
98#[cfg(feature = "arbitrary")]
99impl<'a> Arbitrary<'a> for From<'a> {
100    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
101        match u.int_in_range(0..=3)? {
102            0 => Ok(From::Single {
103                from: u.arbitrary()?,
104                sender: u.arbitrary()?,
105            }),
106            1 => {
107                let mut from: MailboxList = u.arbitrary()?;
108                from.0.push(u.arbitrary()?);
109                Ok(From::Multiple {
110                    from,
111                    sender: u.arbitrary()?,
112                })
113            }
114            2 => Ok(From::InvalidMissingFrom {
115                sender: u.arbitrary()?,
116            }),
117            3 => {
118                let mut from: MailboxList = u.arbitrary()?;
119                from.0.push(u.arbitrary()?);
120                Ok(From::InvalidMissingSender { from })
121            }
122            _ => unreachable!(),
123        }
124    }
125}
126
127#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
128#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
129pub enum TraceField<'a> {
130    // At the moment, we do not try to parse the structure of Received fields.
131    // RFC5322 gives a rough grammar for tokenizing these fields, relegating
132    // their actual interpretation to RFC5321 (which is, for now, outside of
133    // the scope of this library).
134    // Furthermore, in practice many real-world emails contain Received headers
135    // that do not parse even wrt to the rough tokenization of RFC5322.
136    Received(Unstructured<'a>),
137    ReturnPath(ReturnPath<'a>),
138}
139
140impl<'a> Imf<'a> {
141    pub fn new() -> Self {
142        Self {
143            date: DateTimeOpt::InvalidMissing,
144            from: From::InvalidMissingFrom { sender: None },
145            reply_to: vec![],
146            to: vec![],
147            cc: vec![],
148            bcc: None,
149            msg_id: None,
150            in_reply_to: vec![],
151            references: vec![],
152            subject: None,
153            comments: vec![],
154            keywords: vec![],
155            trace: vec![],
156            mime_version: None,
157        }
158    }
159
160    pub fn from_or_sender(&self) -> Option<&MailboxRef<'a>> {
161        match &self.from {
162            From::Single {
163                from: _,
164                sender: Some(sender),
165            } => Some(sender),
166            From::Single { from, sender: None } => Some(from),
167            From::Multiple { from: _, sender } => Some(sender),
168            From::InvalidMissingFrom { sender } => sender.as_ref(),
169            From::InvalidMissingSender { from: _ } => None,
170        }
171    }
172
173    pub fn from(&self) -> Option<MailboxList<'a>> {
174        match &self.from {
175            From::Single { from, .. } => Some(MailboxList(vec![from.clone()])),
176            From::Multiple { from, .. } => Some(from.clone()),
177            From::InvalidMissingFrom { sender: _ } => None,
178            From::InvalidMissingSender { from } => Some(from.clone()),
179        }
180    }
181
182    pub fn sender(&self) -> Option<MailboxRef<'a>> {
183        match &self.from {
184            From::Single { sender, .. } => sender.clone(),
185            From::Multiple { sender, .. } => Some(sender.clone()),
186            From::InvalidMissingFrom { sender } => sender.clone(),
187            From::InvalidMissingSender { from: _ } => None,
188        }
189    }
190
191    pub fn get_field(&self, f: field::Entry) -> Option<field::Field<'a>> {
192        match f {
193            field::Entry::Date => match &self.date {
194                DateTimeOpt::Some(d) => Some(field::Field::Date(d.clone())),
195                DateTimeOpt::InvalidMissing => None,
196            },
197            field::Entry::From => self.from().map(field::Field::From),
198            field::Entry::Sender => self.sender().map(field::Field::Sender),
199            field::Entry::ReplyTo => {
200                if !self.reply_to.is_empty() {
201                    Some(field::Field::ReplyTo(self.reply_to.clone()))
202                } else {
203                    None
204                }
205            }
206            field::Entry::To => {
207                if !self.to.is_empty() {
208                    Some(field::Field::To(self.to.clone()))
209                } else {
210                    None
211                }
212            }
213            field::Entry::Cc => {
214                if !self.cc.is_empty() {
215                    Some(field::Field::Cc(self.cc.clone()))
216                } else {
217                    None
218                }
219            }
220            field::Entry::Bcc => self.bcc.clone().map(field::Field::Bcc),
221            field::Entry::MessageID => self.msg_id.clone().map(field::Field::MessageID),
222            field::Entry::InReplyTo => {
223                if !self.in_reply_to.is_empty() {
224                    Some(field::Field::InReplyTo(self.in_reply_to.clone()))
225                } else {
226                    None
227                }
228            }
229            field::Entry::References => {
230                if !self.references.is_empty() {
231                    Some(field::Field::References(self.references.clone()))
232                } else {
233                    None
234                }
235            }
236            field::Entry::Subject => self.subject.clone().map(field::Field::Subject),
237            field::Entry::Comments(i) => Some(field::Field::Comments(self.comments[i].clone())),
238            field::Entry::Keywords(i) => Some(field::Field::Keywords(self.keywords[i].clone())),
239            field::Entry::MIMEVersion => self.mime_version.clone().map(field::Field::MIMEVersion),
240            field::Entry::Trace(i) => match &self.trace[i] {
241                TraceField::Received(r) => Some(field::Field::Received(r.clone())),
242                TraceField::ReturnPath(p) => Some(field::Field::ReturnPath(p.clone())),
243            },
244        }
245    }
246
247    // Returns the entries included in this Imf struct. This is used to define
248    // the Arbitrary instance for Message, to construct a randomly ordered list
249    // of field entries.
250    //
251    // The first component of the pair is the list of trace entries (for which
252    // the order matters), and the second component is the set of other entries.
253    pub fn field_entries(&self) -> (Vec<field::Entry>, HashSet<field::Entry>) {
254        let mut trace = vec![];
255        for i in 0..self.trace.len() {
256            trace.push(field::Entry::Trace(i))
257        }
258
259        let mut fs = HashSet::default();
260        if let DateTimeOpt::Some(_) = &self.date {
261            fs.insert(field::Entry::Date);
262        }
263        match &self.from {
264            From::Single { from: _, sender } => {
265                fs.insert(field::Entry::From);
266                if sender.is_some() {
267                    fs.insert(field::Entry::Sender);
268                }
269            }
270            From::Multiple { from: _, sender: _ } => {
271                fs.insert(field::Entry::From);
272                fs.insert(field::Entry::Sender);
273            }
274            From::InvalidMissingFrom { sender } => {
275                if sender.is_some() {
276                    fs.insert(field::Entry::Sender);
277                }
278            }
279            From::InvalidMissingSender { from: _ } => {
280                fs.insert(field::Entry::From);
281            }
282        }
283        if !self.reply_to.is_empty() {
284            fs.insert(field::Entry::ReplyTo);
285        }
286        if !self.to.is_empty() {
287            fs.insert(field::Entry::To);
288        }
289        if !self.cc.is_empty() {
290            fs.insert(field::Entry::Cc);
291        }
292        if self.bcc.is_some() {
293            fs.insert(field::Entry::Bcc);
294        }
295        if self.msg_id.is_some() {
296            fs.insert(field::Entry::MessageID);
297        }
298        if !self.in_reply_to.is_empty() {
299            fs.insert(field::Entry::InReplyTo);
300        }
301        if !self.references.is_empty() {
302            fs.insert(field::Entry::References);
303        }
304        if self.subject.is_some() {
305            fs.insert(field::Entry::Subject);
306        }
307        for i in 0..self.comments.len() {
308            fs.insert(field::Entry::Comments(i));
309        }
310        for i in 0..self.keywords.len() {
311            fs.insert(field::Entry::Keywords(i));
312        }
313        for i in 0..self.keywords.len() {
314            fs.insert(field::Entry::Keywords(i));
315        }
316        fs.insert(field::Entry::MIMEVersion);
317
318        (trace, fs)
319    }
320}
321
322impl<'a> Default for Imf<'a> {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328#[derive(Debug, Default, PartialEq, ToStatic)]
329pub struct PartialImf<'a> {
330    date: Option<DateTime>,
331    from: Option<MailboxList<'a>>,
332    sender: Option<MailboxRef<'a>>,
333    reply_to: Option<Vec<AddressRef<'a>>>,
334    to: Option<Vec<AddressRef<'a>>>,
335    cc: Option<Vec<AddressRef<'a>>>,
336    bcc: Option<Vec<AddressRef<'a>>>,
337    msg_id: Option<MessageID<'a>>,
338    in_reply_to: Option<Vec<MessageID<'a>>>,
339    references: Option<Vec<MessageID<'a>>>,
340    subject: Option<Unstructured<'a>>,
341    comments: Vec<Unstructured<'a>>,
342    keywords: Vec<PhraseList<'a>>,
343    trace: Vec<TraceField<'a>>,
344    trace_complete: bool,
345    mime_version: Option<Version>,
346}
347
348#[derive(Clone, Copy, Debug)]
349pub enum AddFieldErr {
350    // This field results in no entry, but its contents have been taken into
351    // account and there is no loss of data.
352    NoEntry,
353    // This field is conflicting with an earlier field and must be dropped, its
354    // data will not be part of the IMF AST.
355    Conflict,
356}
357
358impl<'a> PartialImf<'a> {
359    pub fn add_field(&mut self, f: Field<'a>) -> Result<Entry, AddFieldErr> {
360        match &f {
361            // trace fields
362            Field::ReturnPath(_) | Field::Received(_) => {
363                if self.trace_complete {
364                    // drop trace fields that come after other IMF fields
365                    return Err(AddFieldErr::Conflict);
366                }
367            }
368            // non-trace fields
369            _ => {
370                // register the trace section to be complete as soon as
371                // we encounter a non-trace field
372                self.trace_complete = true;
373            }
374        }
375        match f {
376            Field::Date(date) => set_if_new(&mut self.date, date, Entry::Date),
377            Field::From(from) => set_if_new(&mut self.from, from, Entry::From),
378            Field::Sender(sender) => set_if_new(&mut self.sender, sender, Entry::Sender),
379            Field::ReplyTo(reply_to) => set_if_new(&mut self.reply_to, reply_to, Entry::ReplyTo),
380            Field::To(to) => set_or_extend(&mut self.to, to, Entry::To),
381            Field::Cc(cc) => set_or_extend(&mut self.cc, cc, Entry::Cc),
382            Field::Bcc(bcc) => set_or_extend(&mut self.bcc, bcc, Entry::Bcc),
383            Field::MessageID(id) => set_if_new(&mut self.msg_id, id, Entry::MessageID),
384            Field::InReplyTo(in_reply_to) => {
385                set_if_new(&mut self.in_reply_to, in_reply_to, Entry::InReplyTo)
386            }
387            Field::References(refs) => set_if_new(&mut self.references, refs, Entry::References),
388            Field::Subject(subject) => set_if_new(&mut self.subject, subject, Entry::Subject),
389            Field::Comments(comments) => {
390                let idx = self.comments.len();
391                self.comments.push(comments);
392                Ok(Entry::Comments(idx))
393            }
394            Field::Keywords(kwds) => {
395                let idx = self.keywords.len();
396                self.keywords.push(kwds);
397                Ok(Entry::Keywords(idx))
398            }
399            Field::Received(received) => {
400                let idx = self.trace.len();
401                self.trace.push(TraceField::Received(received));
402                Ok(Entry::Trace(idx))
403            }
404            Field::ReturnPath(path) => {
405                let idx = self.trace.len();
406                self.trace.push(TraceField::ReturnPath(path));
407                Ok(Entry::Trace(idx))
408            }
409            Field::MIMEVersion(version) => {
410                set_if_new(&mut self.mime_version, version, Entry::MIMEVersion)
411            }
412        }
413    }
414
415    pub fn to_imf(self) -> Imf<'a> {
416        let date = match self.date {
417            Some(dt) => DateTimeOpt::Some(dt),
418            None => DateTimeOpt::InvalidMissing,
419        };
420        let from = match (self.from, self.sender) {
421            (None, sender) => From::InvalidMissingFrom { sender },
422            (Some(mut l), sender) if l.0.len() == 1 => From::Single {
423                from: l.0.pop().unwrap(),
424                sender,
425            },
426            (Some(l), Some(sender)) => From::Multiple { from: l, sender },
427            (Some(l), None) => From::InvalidMissingSender { from: l },
428        };
429
430        Imf {
431            date,
432            from,
433            reply_to: self.reply_to.unwrap_or_default(),
434            to: self.to.unwrap_or_default(),
435            cc: self.cc.unwrap_or_default(),
436            bcc: self.bcc,
437            msg_id: self.msg_id,
438            in_reply_to: self.in_reply_to.unwrap_or_default(),
439            references: self.references.unwrap_or_default(),
440            subject: self.subject,
441            comments: self.comments,
442            keywords: self.keywords,
443            trace: self.trace,
444            mime_version: self.mime_version,
445        }
446    }
447}
448
449fn set_if_new<T: PartialEq, U>(o: &mut Option<T>, x: T, y: U) -> Result<U, AddFieldErr> {
450    match *o {
451        None => {
452            *o = Some(x);
453            Ok(y)
454        }
455        Some(_) => Err(AddFieldErr::Conflict),
456    }
457}
458
459fn set_or_extend<T, U>(o: &mut Option<Vec<T>>, x: Vec<T>, y: U) -> Result<U, AddFieldErr> {
460    match o {
461        None => {
462            *o = Some(x);
463            Ok(y)
464        }
465        Some(v) => {
466            v.extend(x);
467            Err(AddFieldErr::NoEntry)
468        }
469    }
470}