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