Skip to main content

eml_codec/message/
field.rs

1use bounded_static::ToStatic;
2#[cfg(feature = "tracing")]
3use tracing::warn;
4
5#[cfg(feature = "arbitrary")]
6use crate::fuzz_eq::FuzzEq;
7use crate::print::{Formatter, Print};
8use crate::raw_input::RawInput;
9use crate::{header, imf, mime};
10
11/// Header field of a toplevel message.
12/// Is either an Imf field (RFC 5322),
13/// MIME-defined fields (RFC 2045),
14/// or an unstructured field.
15#[derive(Clone, Debug, PartialEq, ToStatic)]
16#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
17pub enum MessageField<'a> {
18    MIME {
19        f: mime::field::Field<'a>,
20        raw_body: RawInput<'a>,
21    },
22    Imf {
23        f: imf::field::Field<'a>,
24        raw_body: RawInput<'a>,
25    },
26    // invariant: has a field name that is different from IMF or MIME headers.
27    Unstructured(header::Unstructured<'a>),
28}
29
30impl<'a> MessageField<'a> {
31    pub fn raw_name(&self) -> header::FieldName<'a> {
32        match self {
33            MessageField::MIME { f, .. } => f.raw_name(),
34            MessageField::Imf { f, .. } => f.raw_name(),
35            MessageField::Unstructured(u) => u.name.clone(),
36        }
37    }
38
39    pub fn raw_body(&self) -> RawInput<'a> {
40        match self {
41            MessageField::MIME { raw_body, .. } => raw_body.clone(),
42            MessageField::Imf { raw_body, .. } => raw_body.clone(),
43            MessageField::Unstructured(u) => u.raw_body.clone(),
44        }
45    }
46}
47
48impl<'a> Print for MessageField<'a> {
49    fn print(&self, fmt: &mut impl Formatter) {
50        match self {
51            MessageField::MIME { f, .. } => f.print(fmt),
52            MessageField::Imf { f, .. } => f.print(fmt),
53            MessageField::Unstructured(u) => u.print(fmt),
54        }
55    }
56}
57
58/// Entry for a header field of a toplevel message.
59#[derive(Clone, Debug, PartialEq, ToStatic)]
60#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
61pub enum MessageEntry<'a> {
62    MIME {
63        e: mime::field::Entry,
64        raw_body: RawInput<'a>,
65    },
66    Imf {
67        e: imf::field::Entry,
68        raw_body: RawInput<'a>,
69    },
70    // invariant: has a field name that is different from IMF or MIME headers.
71    Unstructured(header::Unstructured<'a>),
72}
73
74/// Collects fields and entries for a toplevel message. Only for eml-codec's
75/// internal use.
76#[derive(Debug, PartialEq, ToStatic)]
77pub(crate) struct NaiveMessageFields<'a> {
78    pub mime: mime::NaiveMIME<'a>,
79    pub imf: imf::Imf<'a>,
80    pub entries: Vec<MessageEntry<'a>>,
81}
82
83impl<'a> FromIterator<header::FieldRaw<'a>> for NaiveMessageFields<'a> {
84    #[cfg_attr(
85        feature = "tracing",
86        tracing::instrument(name = "MessageFields::from_iter", skip(it))
87    )]
88    fn from_iter<I: IntoIterator<Item = header::FieldRaw<'a>>>(it: I) -> Self {
89        let mut mime = mime::NaiveMIME::default();
90        let mut imf = imf::PartialImf::default();
91        let mut entries = vec![];
92        for f in it {
93            match mime::field::NaiveField::try_from(&f) {
94                Ok(mimef) => {
95                    if let Some(entry) = mime.add_field(mimef) {
96                        entries.push(MessageEntry::MIME {
97                            e: entry,
98                            raw_body: f.body.into(),
99                        })
100                    } else {
101                        // otherwise drop the field
102                        #[cfg(feature = "tracing-recover")]
103                        warn!(field = ?f, "dropping conflicting MIME field")
104                    }
105                    continue;
106                }
107                Err(mime::field::InvalidField::Body) => {
108                    // this is a MIME field but its body is invalid; drop it.
109                    #[cfg(feature = "tracing-unsupported")]
110                    warn!(field = ?f, "dropping MIME field with an invalid body");
111                    continue;
112                }
113                Err(mime::field::InvalidField::Name) => {
114                    // not a MIME field
115                }
116            };
117
118            match imf::field::Field::try_from(&f) {
119                Ok(imff) => {
120                    match imf.add_field(imff) {
121                        Ok(entry) => entries.push(MessageEntry::Imf {
122                            e: entry,
123                            raw_body: f.body.into(),
124                        }),
125                        Err(imf::AddFieldErr::NoEntry) => {
126                            #[cfg(feature = "tracing-recover")]
127                            warn!(field = ?f, "no new entry for IMF field");
128                        }
129                        Err(imf::AddFieldErr::Conflict) => {
130                            #[cfg(feature = "tracing-recover")]
131                            warn!(field = ?f, "discarding conflicting IMF field");
132                        }
133                    }
134                    continue;
135                }
136                Err(imf::field::InvalidField::NeedsDiscard) => {
137                    // this is an IMF field for which we recognized the body, but the
138                    // body isn't RFC compliant and the fields needs to be dropped.
139                    #[cfg(feature = "tracing-recover")]
140                    warn!(field = ?f, "dropping IMF field with a body to be discarded");
141                    continue;
142                }
143                Err(imf::field::InvalidField::Body) => {
144                    // this is an IMF field but its body is invalid; drop it.
145                    #[cfg(feature = "tracing-unsupported")]
146                    warn!(field = ?f, "dropping IMF field with an invalid body");
147                    continue;
148                }
149                Err(imf::field::InvalidField::Name) => {
150                    // not an IMF field
151                }
152            }
153
154            if let Some(u) = header::Unstructured::from_raw(&f) {
155                entries.push(MessageEntry::Unstructured(u));
156            } else {
157                // otherwise drop the field
158                #[cfg(feature = "tracing-unsupported")]
159                warn!(field = ?f, "dropping field that cannot be parsed as unstructured")
160            }
161        }
162
163        NaiveMessageFields {
164            mime,
165            imf: imf.to_imf(),
166            entries,
167        }
168    }
169}