1pub mod composite;
3
4pub mod discrete;
6
7pub 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
81pub 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 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}