1pub mod field;
3
4pub mod mechanism;
6
7pub mod r#type;
9
10#[cfg(feature = "arbitrary")]
11use arbitrary::Arbitrary;
12use bounded_static::ToStatic;
13use std::collections::HashSet;
14
15#[cfg(feature = "arbitrary")]
16use crate::fuzz_eq::FuzzEq;
17use crate::i18n::ContainsUtf8;
18use crate::imf::identification::MessageID;
19use crate::mime::field::NaiveField;
20use crate::mime::mechanism::Mechanism;
21use crate::mime::r#type::{AnyType, MessageSubtype, NaiveType};
22use crate::text::misc_token::Unstructured;
23use crate::utils::set_opt;
24
25#[derive(Debug, Default, PartialEq, Clone, ToStatic)]
26#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
27pub struct CommonMIME<'a> {
28 pub transfer_encoding: Mechanism<'a>,
29 pub id: Option<MessageID<'a>>,
30 pub description: Option<Unstructured<'a>>,
31}
32
33impl<'a> ContainsUtf8 for CommonMIME<'a> {
34 fn contains_utf8(&self) -> bool {
35 self.transfer_encoding.contains_utf8()
36 || self.id.contains_utf8()
37 || self.description.contains_utf8()
38 }
39}
40
41#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
44#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
45pub struct MIME<'a, T> {
46 pub ctype: T,
47 pub fields: CommonMIME<'a>,
48}
49
50#[cfg(feature = "arbitrary")]
51impl<'a> Arbitrary<'a> for MIME<'a, r#type::Multipart<'a>> {
52 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
53 let mime = MIME {
54 ctype: u.arbitrary()?,
55 fields: u.arbitrary()?,
56 };
57 match mime.fields.transfer_encoding {
58 Mechanism::_7Bit | Mechanism::_8Bit | Mechanism::Binary => (),
59 _ => return Err(arbitrary::Error::IncorrectFormat),
60 };
61 Ok(mime)
62 }
63}
64
65#[cfg(feature = "arbitrary")]
66impl<'a> Arbitrary<'a> for MIME<'a, r#type::Message<'a>> {
67 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
68 let mime = MIME {
69 ctype: u.arbitrary()?,
70 fields: u.arbitrary()?,
71 };
72 match mime.fields.transfer_encoding {
73 Mechanism::_7Bit | Mechanism::_8Bit | Mechanism::Binary => (),
74 _ => return Err(arbitrary::Error::IncorrectFormat),
75 };
76 Ok(mime)
77 }
78}
79
80#[cfg(feature = "arbitrary")]
81impl<'a> Arbitrary<'a> for MIME<'a, r#type::Text<'a>> {
82 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
83 Ok(MIME {
84 ctype: u.arbitrary()?,
85 fields: u.arbitrary()?,
86 })
87 }
88}
89
90#[cfg(feature = "arbitrary")]
91impl<'a> Arbitrary<'a> for MIME<'a, r#type::Binary<'a>> {
92 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
93 Ok(MIME {
94 ctype: u.arbitrary()?,
95 fields: u.arbitrary()?,
96 })
97 }
98}
99
100#[derive(Debug, PartialEq, Clone, ToStatic)]
101#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
102pub enum AnyMIME<'a> {
103 Mult(MIME<'a, r#type::Multipart<'a>>),
104 Msg(MIME<'a, r#type::Message<'a>>),
105 Txt(MIME<'a, r#type::Text<'a>>),
106 Bin(MIME<'a, r#type::Binary<'a>>),
107}
108impl<'a> AnyMIME<'a> {
109 pub fn ctype(&self) -> AnyType<'a> {
110 match self {
111 Self::Mult(m) => AnyType::Multipart(m.ctype.clone()),
112 Self::Msg(m) => AnyType::Message(m.ctype.clone()),
113 Self::Txt(m) => AnyType::Text(m.ctype.clone()),
114 Self::Bin(m) => AnyType::Binary(m.ctype.clone()),
115 }
116 }
117
118 pub fn common(&self) -> &CommonMIME<'a> {
119 match self {
120 Self::Mult(v) => &v.fields,
121 Self::Msg(v) => &v.fields,
122 Self::Txt(v) => &v.fields,
123 Self::Bin(v) => &v.fields,
124 }
125 }
126
127 pub fn get_field(&self, f: field::Entry) -> Option<field::Field<'a>> {
128 match f {
129 field::Entry::Type => Some(field::Field::Type(self.ctype())),
130 field::Entry::TransferEncoding => Some(field::Field::TransferEncoding(
131 self.common().transfer_encoding.clone(),
132 )),
133 field::Entry::ID => self.common().id.clone().map(field::Field::ID),
134 field::Entry::Description => self
135 .common()
136 .description
137 .clone()
138 .map(field::Field::Description),
139 }
140 }
141
142 pub fn field_entries(&self) -> HashSet<field::Entry> {
146 let mut fs = HashSet::default();
147 fs.insert(field::Entry::Type);
148 fs.insert(field::Entry::TransferEncoding);
149 let common = self.common();
150 if common.id.is_some() {
151 fs.insert(field::Entry::ID);
152 }
153 if common.description.is_some() {
154 fs.insert(field::Entry::Description);
155 }
156 fs
157 }
158}
159impl<'a> ContainsUtf8 for AnyMIME<'a> {
160 fn contains_utf8(&self) -> bool {
161 match self {
162 Self::Mult(v) => v.contains_utf8(),
163 Self::Msg(v) => v.contains_utf8(),
164 Self::Txt(v) => v.contains_utf8(),
165 Self::Bin(v) => v.contains_utf8(),
166 }
167 }
168}
169
170impl<'a> From<MIME<'a, r#type::Multipart<'a>>> for AnyMIME<'a> {
171 fn from(val: MIME<'a, r#type::Multipart<'a>>) -> Self {
172 AnyMIME::Mult(val)
173 }
174}
175
176impl<'a> From<MIME<'a, r#type::Message<'a>>> for AnyMIME<'a> {
177 fn from(val: MIME<'a, r#type::Message<'a>>) -> Self {
178 AnyMIME::Msg(val)
179 }
180}
181
182impl<'a> From<MIME<'a, r#type::Text<'a>>> for AnyMIME<'a> {
183 fn from(val: MIME<'a, r#type::Text<'a>>) -> Self {
184 AnyMIME::Txt(val)
185 }
186}
187impl<'a> From<MIME<'a, r#type::Binary<'a>>> for AnyMIME<'a> {
188 fn from(val: MIME<'a, r#type::Binary<'a>>) -> Self {
189 AnyMIME::Bin(val)
190 }
191}
192
193#[derive(Clone, Debug, Default, PartialEq, ToStatic)]
194#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
195pub struct NaiveMIME<'a> {
196 ctype: Option<r#type::NaiveType<'a>>,
197 transfer_encoding: Option<Mechanism<'a>>,
198 id: Option<MessageID<'a>>,
199 description: Option<Unstructured<'a>>,
200}
201
202impl<'a> NaiveMIME<'a> {
203 pub fn add_field(&mut self, f: NaiveField<'a>) -> Option<field::Entry> {
204 match f {
205 NaiveField::Type(ctype) => {
206 set_opt(&mut self.ctype, ctype).then_some(field::Entry::Type)
207 }
208 NaiveField::TransferEncoding(enc) => {
209 set_opt(&mut self.transfer_encoding, enc).then_some(field::Entry::TransferEncoding)
210 }
211 NaiveField::ID(id) => set_opt(&mut self.id, id).then_some(field::Entry::ID),
212 NaiveField::Description(desc) => {
213 set_opt(&mut self.description, desc).then_some(field::Entry::Description)
214 }
215 }
216 }
217
218 pub fn to_interpreted(self, default_type: DefaultType) -> AnyMIME<'a> {
219 let typ: AnyType = self
220 .ctype
221 .as_ref()
222 .map(NaiveType::to_type)
223 .unwrap_or(default_type.to_type());
224 let transfer_encoding = self.transfer_encoding.unwrap_or_default();
225 let mut fields = CommonMIME {
226 transfer_encoding,
227 id: self.id,
228 description: self.description,
229 };
230 match typ {
231 AnyType::Multipart(ctype) => {
232 fields.transfer_encoding = fields.transfer_encoding.to_multipart_encoding();
234 AnyMIME::Mult(MIME { ctype, fields })
235 }
236 AnyType::Message(ctype) => {
237 if let MessageSubtype::RFC822 = ctype.subtype {
239 fields.transfer_encoding =
240 fields.transfer_encoding.to_message_rfc822_encoding();
241 }
242 AnyMIME::Msg(MIME { ctype, fields })
244 }
245 AnyType::Text(ctype) => AnyMIME::Txt(MIME { ctype, fields }),
246 AnyType::Binary(ctype) => AnyMIME::Bin(MIME { ctype, fields }),
247 }
248 }
249}
250
251#[derive(Default)]
252pub enum DefaultType {
253 #[default]
254 Generic,
255 Digest,
256}
257
258#[expect(clippy::wrong_self_convention)]
259impl DefaultType {
260 fn to_type(self) -> AnyType<'static> {
261 match self {
262 Self::Generic => AnyType::Text(Default::default()),
263 Self::Digest => AnyType::Message(Default::default()),
264 }
265 }
266}