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/// IMF + MIME fields parsed at once
8pub mod field;
9
10use nom::{
11    branch::alt,
12    bytes::complete::is_not,
13    combinator::{not, recognize},
14    multi::many0,
15    sequence::pair,
16    IResult,
17};
18
19use crate::mime;
20use crate::mime::{AnyMIME, NaiveMIME};
21use crate::part::{
22    composite::{message, multipart, Message, Multipart},
23    discrete::{Binary, Text},
24};
25use crate::text::ascii::CRLF;
26use crate::text::boundary::boundary;
27use crate::text::whitespace::obs_crlf;
28
29#[derive(Debug, PartialEq)]
30pub enum AnyPart<'a> {
31    Mult(Multipart<'a>),
32    Msg(Message<'a>),
33    Txt(Text<'a>),
34    Bin(Binary<'a>),
35}
36impl<'a> AnyPart<'a> {
37    pub fn as_multipart(&self) -> Option<&Multipart<'a>> {
38        match self {
39            Self::Mult(x) => Some(x),
40            _ => None,
41        }
42    }
43    pub fn as_message(&self) -> Option<&Message<'a>> {
44        match self {
45            Self::Msg(x) => Some(x),
46            _ => None,
47        }
48    }
49    pub fn as_text(&self) -> Option<&Text<'a>> {
50        match self {
51            Self::Txt(x) => Some(x),
52            _ => None,
53        }
54    }
55    pub fn as_binary(&self) -> Option<&Binary<'a>> {
56        match self {
57            Self::Bin(x) => Some(x),
58            _ => None,
59        }
60    }
61    pub fn mime(&self) -> &NaiveMIME<'a> {
62        match self {
63            Self::Mult(v) => &v.mime.fields,
64            Self::Msg(v) => &v.mime.fields,
65            Self::Txt(v) => &v.mime.fields,
66            Self::Bin(v) => &v.mime.fields,
67        }
68    }
69}
70impl<'a> From<Multipart<'a>> for AnyPart<'a> {
71    fn from(m: Multipart<'a>) -> Self {
72        Self::Mult(m)
73    }
74}
75impl<'a> From<Message<'a>> for AnyPart<'a> {
76    fn from(m: Message<'a>) -> Self {
77        Self::Msg(m)
78    }
79}
80
81/// Parse any type of part
82///
83/// ## Note
84///
85/// Multiparts are a bit special as they have a clearly delimited beginning
86/// and end contrary to all the other parts that are going up to the end of the buffer
87pub fn anypart<'a>(m: AnyMIME<'a>) -> impl FnOnce(&'a [u8]) -> IResult<&'a [u8], AnyPart<'a>> {
88    move |input| {
89        let part = match m {
90            AnyMIME::Mult(a) => multipart(a)(input)
91                .map(|(_, multi)| multi.into())
92                .unwrap_or(AnyPart::Txt(Text {
93                    mime: mime::MIME::<mime::r#type::DeductibleText>::default(),
94                    body: input,
95                })),
96            AnyMIME::Msg(a) => {
97                message(a)(input)
98                    .map(|(_, msg)| msg.into())
99                    .unwrap_or(AnyPart::Txt(Text {
100                        mime: mime::MIME::<mime::r#type::DeductibleText>::default(),
101                        body: input,
102                    }))
103            }
104            AnyMIME::Txt(a) => AnyPart::Txt(Text {
105                mime: a,
106                body: input,
107            }),
108            AnyMIME::Bin(a) => AnyPart::Bin(Binary {
109                mime: a,
110                body: input,
111            }),
112        };
113
114        // This function always consumes the whole input
115        Ok((&input[input.len()..], part))
116    }
117}
118
119pub fn part_raw<'a>(bound: &[u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> + '_ {
120    move |input| {
121        recognize(many0(pair(
122            not(boundary(bound)),
123            alt((is_not(CRLF), obs_crlf)),
124        )))(input)
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_preamble() {
134        assert_eq!(
135            part_raw(b"hello")(
136                b"blip
137bloup
138
139blip
140bloup--
141--bim
142--bim--
143
144--hello
145Field: Body
146"
147            ),
148            Ok((
149                &b"\n--hello\nField: Body\n"[..],
150                &b"blip\nbloup\n\nblip\nbloup--\n--bim\n--bim--\n"[..],
151            ))
152        );
153    }
154
155    #[test]
156    fn test_part_raw() {
157        assert_eq!(
158            part_raw(b"simple boundary")(b"Content-type: text/plain; charset=us-ascii
159
160This is explicitly typed plain US-ASCII text.
161It DOES end with a linebreak.
162
163--simple boundary--
164"),
165            Ok((
166                &b"\n--simple boundary--\n"[..], 
167                &b"Content-type: text/plain; charset=us-ascii\n\nThis is explicitly typed plain US-ASCII text.\nIt DOES end with a linebreak.\n"[..],
168            ))
169        );
170    }
171}