Skip to main content

eml_codec/part/
mod.rs

1/// Parts that contain other parts inside them
2pub mod composite;
3
4/// Parts that have a body and no child parts
5pub mod discrete;
6
7/// Representation of all headers in a MIME entity
8pub mod field;
9
10use crate::mime::{AnyMIME, MIME};
11use crate::part::{
12    composite::{message, multipart, Message, Multipart},
13    discrete::{Binary, Text},
14};
15use crate::print::{print_seq, Formatter, Print};
16use crate::raw_input::RawInput;
17#[cfg(feature = "tracing-unsupported")]
18use crate::utils::bytes_to_trace_string;
19#[cfg(feature = "arbitrary")]
20use crate::{
21    arbitrary_utils::{arbitrary_shuffle, arbitrary_vec_where},
22    fuzz_eq::FuzzEq,
23    header, mime,
24};
25#[cfg(feature = "arbitrary")]
26use arbitrary::Arbitrary;
27use bounded_static::ToStatic;
28use std::borrow::Cow;
29#[cfg(feature = "tracing-unsupported")]
30use tracing::warn;
31
32#[derive(Clone, Debug, PartialEq, ToStatic)]
33#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
34pub struct AnyPart<'a> {
35    // Invariant: `fields` must be "complete and correct":
36    // - it must contain an entry for every piece of information contained in
37    //   `mime_body`'s mime headers that is not the default value. (This means
38    //    values of which are `Deductible::Explicit` or optionals set to
39    //    `Some(_)`.)
40    // - it must *only* contain entries for fields that have a value. (This means
41    //   no optional fields set to `None`.)
42    // Invariant: `fields` must contain no duplicates.
43    pub entries: Vec<field::EntityEntry<'a>>,
44    pub mime_body: MimeBody<'a>,
45    pub raw: RawInput<'a>,
46    pub raw_headers: RawInput<'a>,
47}
48
49impl<'a> AnyPart<'a> {
50    // TODO: return an iterator instead of a Vec?
51    pub fn field_list(&self) -> Vec<field::EntityField<'a>> {
52        let mime = self.mime_body.mime();
53        let mut v = vec![];
54        for e in &self.entries {
55            let field = match e {
56                field::EntityEntry::MIME { e, raw_body } => {
57                    // SAFETY: `self.entries` must only contain entries for
58                    // fields that are actually present in `mime`.
59                    field::EntityField::MIME {
60                        f: mime.get_field(*e).unwrap(),
61                        raw_body: raw_body.clone(),
62                    }
63                }
64                field::EntityEntry::Unstructured(u) => field::EntityField::Unstructured(u.clone()),
65            };
66            v.push(field);
67        }
68        v
69    }
70}
71
72impl Default for AnyPart<'static> {
73    fn default() -> Self {
74        Self {
75            entries: vec![],
76            mime_body: MimeBody::Txt(discrete::Text {
77                mime: MIME {
78                    ctype: Default::default(),
79                    fields: Default::default(),
80                },
81                body: b"".into(),
82                raw_body: RawInput(Some(b"")),
83            }),
84            raw: RawInput(Some(b"")),
85            raw_headers: RawInput(Some(b"")),
86        }
87    }
88}
89
90#[cfg(feature = "arbitrary")]
91impl<'a> Arbitrary<'a> for AnyPart<'a> {
92    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
93        let mime_body: MimeBody = u.arbitrary()?;
94        let mut entries: Vec<field::EntityEntry> = mime_body
95            .mime()
96            .field_entries()
97            .into_iter()
98            .map(|e| field::EntityEntry::MIME {
99                e,
100                raw_body: RawInput::none(),
101            })
102            .collect();
103        let unstr: Vec<header::Unstructured> =
104            arbitrary_vec_where(u, |f: &header::Unstructured| {
105                !mime::field::is_mime_header(&f.name)
106            })?;
107        entries.extend(unstr.into_iter().map(field::EntityEntry::Unstructured));
108        arbitrary_shuffle(u, &mut entries)?;
109        Ok(AnyPart {
110            entries,
111            mime_body,
112            raw: RawInput::none(),
113            raw_headers: RawInput::none(),
114        })
115    }
116}
117
118#[derive(Clone, Debug, PartialEq, ToStatic)]
119#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
120pub enum MimeBody<'a> {
121    Mult(Multipart<'a>),
122    Msg(Message<'a>),
123    Txt(Text<'a>),
124    Bin(Binary<'a>),
125}
126impl<'a> MimeBody<'a> {
127    pub fn as_multipart(&self) -> Option<&Multipart<'a>> {
128        match self {
129            Self::Mult(x) => Some(x),
130            _ => None,
131        }
132    }
133    pub fn as_message(&self) -> Option<&Message<'a>> {
134        match self {
135            Self::Msg(x) => Some(x),
136            _ => None,
137        }
138    }
139    pub fn as_text(&self) -> Option<&Text<'a>> {
140        match self {
141            Self::Txt(x) => Some(x),
142            _ => None,
143        }
144    }
145    pub fn as_binary(&self) -> Option<&Binary<'a>> {
146        match self {
147            Self::Bin(x) => Some(x),
148            _ => None,
149        }
150    }
151    pub fn mime(&self) -> AnyMIME<'a> {
152        match self {
153            Self::Mult(v) => v.mime.clone().into(),
154            Self::Msg(v) => v.mime.clone().into(),
155            Self::Txt(v) => v.mime.clone().into(),
156            Self::Bin(v) => v.mime.clone().into(),
157        }
158    }
159    pub fn raw_body(&self) -> RawInput<'a> {
160        match self {
161            Self::Mult(v) => v.raw_body.clone(),
162            Self::Msg(v) => v.raw_body.clone(),
163            Self::Txt(v) => v.raw_body.clone(),
164            Self::Bin(v) => v.raw_body.clone(),
165        }
166    }
167    pub fn print_body(&self, fmt: &mut impl Formatter) {
168        match &self {
169            MimeBody::Mult(multipart) => {
170                // TODO: also print preamble and epilogue?
171                for child in &multipart.children {
172                    fmt.write_bytes(b"--");
173                    fmt.write_current_boundary();
174                    fmt.write_crlf();
175                    child.print(fmt);
176                    fmt.write_crlf();
177                }
178                fmt.write_bytes(b"--");
179                fmt.write_current_boundary();
180                fmt.write_bytes(b"--");
181                fmt.write_crlf();
182                fmt.pop_boundary();
183            }
184            MimeBody::Msg(message) => message.child.print(fmt),
185            MimeBody::Txt(text) => fmt.write_bytes(&text.body),
186            MimeBody::Bin(binary) => fmt.write_bytes(&binary.body),
187        }
188    }
189}
190impl<'a> From<Multipart<'a>> for MimeBody<'a> {
191    fn from(m: Multipart<'a>) -> Self {
192        Self::Mult(m)
193    }
194}
195impl<'a> From<Message<'a>> for MimeBody<'a> {
196    fn from(m: Message<'a>) -> Self {
197        Self::Msg(m)
198    }
199}
200
201impl<'a> Print for AnyPart<'a> {
202    fn print(&self, fmt: &mut impl Formatter) {
203        fmt.begin_line_folding();
204        print_seq(fmt, &self.field_list(), |_| ());
205        fmt.end_line_folding();
206        fmt.write_crlf();
207        self.mime_body.print_body(fmt);
208    }
209}
210
211/// Parse any type of part.
212///
213/// This function always consumes the whole input.
214///
215/// ## Note
216///
217/// Multiparts are a bit special as they have a clearly delimited beginning
218/// and end contrary to all the other parts that are going up to the end of the buffer
219pub fn part_body<'a>(m: AnyMIME<'a>) -> impl FnOnce(&'a [u8]) -> MimeBody<'a> {
220    move |input| {
221        let part = match m {
222            AnyMIME::Mult(a) => {
223                let (_rest, part) = multipart(a)(input);
224                #[cfg(feature = "tracing-unsupported")]
225                if !_rest.is_empty() {
226                    warn!(rest = %bytes_to_trace_string(_rest),
227                          "leftover input after multipart parsing")
228                }
229                part.into()
230            }
231            AnyMIME::Msg(a) => message(a)(input).into(),
232            AnyMIME::Txt(a) => MimeBody::Txt(Text {
233                mime: a,
234                body: Cow::Borrowed(input),
235                raw_body: input.into(),
236            }),
237            AnyMIME::Bin(a) => MimeBody::Bin(Binary {
238                mime: a,
239                body: Cow::Borrowed(input),
240                raw_body: input.into(),
241            }),
242        };
243
244        part
245    }
246}