1#[cfg(feature = "arbitrary")]
3use arbitrary::Arbitrary;
4pub mod address;
5pub mod datetime;
6pub mod field;
7pub mod identification;
8pub mod mailbox;
9pub mod mime;
10pub mod trace;
11
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::address::AddressRef;
19use crate::imf::datetime::DateTime;
20use crate::imf::field::{Entry, Field};
21use crate::imf::identification::MessageID;
22use crate::imf::mailbox::{MailboxList, MailboxRef};
23use crate::imf::mime::Version;
24use crate::imf::trace::ReturnPath;
25use crate::text::misc_token::{PhraseList, Unstructured};
26
27#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
28#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
29pub struct Imf<'a> {
30 pub date: DateTimeOpt,
32
33 pub from: From<'a>, pub reply_to: Vec<AddressRef<'a>>,
36
37 pub to: Vec<AddressRef<'a>>,
39 pub cc: Vec<AddressRef<'a>>,
40 pub bcc: Option<Vec<AddressRef<'a>>>,
41
42 pub msg_id: Option<MessageID<'a>>,
44 pub in_reply_to: Vec<MessageID<'a>>,
45 pub references: Vec<MessageID<'a>>,
46
47 pub subject: Option<Unstructured<'a>>,
49 pub comments: Vec<Unstructured<'a>>,
50 pub keywords: Vec<PhraseList<'a>>,
51
52 pub trace: Vec<TraceField<'a>>,
56
57 pub mime_version: Option<Version>,
59}
60
61#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
62#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
63pub enum DateTimeOpt {
64 Some(DateTime),
65 InvalidMissing,
70}
71
72#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
73#[cfg_attr(feature = "arbitrary", derive(FuzzEq))]
74pub enum From<'a> {
75 Single {
76 from: MailboxRef<'a>,
77 sender: Option<MailboxRef<'a>>,
78 },
79 Multiple {
80 from: MailboxList<'a>, sender: MailboxRef<'a>,
82 },
83 InvalidMissingFrom {
88 sender: Option<MailboxRef<'a>>,
89 },
90 InvalidMissingSender {
94 from: MailboxList<'a>, },
96}
97
98#[cfg(feature = "arbitrary")]
99impl<'a> Arbitrary<'a> for From<'a> {
100 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
101 match u.int_in_range(0..=3)? {
102 0 => Ok(From::Single {
103 from: u.arbitrary()?,
104 sender: u.arbitrary()?,
105 }),
106 1 => {
107 let mut from: MailboxList = u.arbitrary()?;
108 from.0.push(u.arbitrary()?);
109 Ok(From::Multiple {
110 from,
111 sender: u.arbitrary()?,
112 })
113 }
114 2 => Ok(From::InvalidMissingFrom {
115 sender: u.arbitrary()?,
116 }),
117 3 => {
118 let mut from: MailboxList = u.arbitrary()?;
119 from.0.push(u.arbitrary()?);
120 Ok(From::InvalidMissingSender { from })
121 }
122 _ => unreachable!(),
123 }
124 }
125}
126
127#[derive(Clone, ContainsUtf8, Debug, PartialEq, ToStatic)]
128#[cfg_attr(feature = "arbitrary", derive(Arbitrary, FuzzEq))]
129pub enum TraceField<'a> {
130 Received(Unstructured<'a>),
137 ReturnPath(ReturnPath<'a>),
138}
139
140impl<'a> Imf<'a> {
141 pub fn new() -> Self {
142 Self {
143 date: DateTimeOpt::InvalidMissing,
144 from: From::InvalidMissingFrom { sender: None },
145 reply_to: vec![],
146 to: vec![],
147 cc: vec![],
148 bcc: None,
149 msg_id: None,
150 in_reply_to: vec![],
151 references: vec![],
152 subject: None,
153 comments: vec![],
154 keywords: vec![],
155 trace: vec![],
156 mime_version: None,
157 }
158 }
159
160 pub fn from_or_sender(&self) -> Option<&MailboxRef<'a>> {
161 match &self.from {
162 From::Single {
163 from: _,
164 sender: Some(sender),
165 } => Some(sender),
166 From::Single { from, sender: None } => Some(from),
167 From::Multiple { from: _, sender } => Some(sender),
168 From::InvalidMissingFrom { sender } => sender.as_ref(),
169 From::InvalidMissingSender { from: _ } => None,
170 }
171 }
172
173 pub fn from(&self) -> Option<MailboxList<'a>> {
174 match &self.from {
175 From::Single { from, .. } => Some(MailboxList(vec![from.clone()])),
176 From::Multiple { from, .. } => Some(from.clone()),
177 From::InvalidMissingFrom { sender: _ } => None,
178 From::InvalidMissingSender { from } => Some(from.clone()),
179 }
180 }
181
182 pub fn sender(&self) -> Option<MailboxRef<'a>> {
183 match &self.from {
184 From::Single { sender, .. } => sender.clone(),
185 From::Multiple { sender, .. } => Some(sender.clone()),
186 From::InvalidMissingFrom { sender } => sender.clone(),
187 From::InvalidMissingSender { from: _ } => None,
188 }
189 }
190
191 pub fn get_field(&self, f: field::Entry) -> Option<field::Field<'a>> {
192 match f {
193 field::Entry::Date => match &self.date {
194 DateTimeOpt::Some(d) => Some(field::Field::Date(d.clone())),
195 DateTimeOpt::InvalidMissing => None,
196 },
197 field::Entry::From => self.from().map(field::Field::From),
198 field::Entry::Sender => self.sender().map(field::Field::Sender),
199 field::Entry::ReplyTo => {
200 if !self.reply_to.is_empty() {
201 Some(field::Field::ReplyTo(self.reply_to.clone()))
202 } else {
203 None
204 }
205 }
206 field::Entry::To => {
207 if !self.to.is_empty() {
208 Some(field::Field::To(self.to.clone()))
209 } else {
210 None
211 }
212 }
213 field::Entry::Cc => {
214 if !self.cc.is_empty() {
215 Some(field::Field::Cc(self.cc.clone()))
216 } else {
217 None
218 }
219 }
220 field::Entry::Bcc => self.bcc.clone().map(field::Field::Bcc),
221 field::Entry::MessageID => self.msg_id.clone().map(field::Field::MessageID),
222 field::Entry::InReplyTo => {
223 if !self.in_reply_to.is_empty() {
224 Some(field::Field::InReplyTo(self.in_reply_to.clone()))
225 } else {
226 None
227 }
228 }
229 field::Entry::References => {
230 if !self.references.is_empty() {
231 Some(field::Field::References(self.references.clone()))
232 } else {
233 None
234 }
235 }
236 field::Entry::Subject => self.subject.clone().map(field::Field::Subject),
237 field::Entry::Comments(i) => Some(field::Field::Comments(self.comments[i].clone())),
238 field::Entry::Keywords(i) => Some(field::Field::Keywords(self.keywords[i].clone())),
239 field::Entry::MIMEVersion => self.mime_version.clone().map(field::Field::MIMEVersion),
240 field::Entry::Trace(i) => match &self.trace[i] {
241 TraceField::Received(r) => Some(field::Field::Received(r.clone())),
242 TraceField::ReturnPath(p) => Some(field::Field::ReturnPath(p.clone())),
243 },
244 }
245 }
246
247 pub fn field_entries(&self) -> (Vec<field::Entry>, HashSet<field::Entry>) {
254 let mut trace = vec![];
255 for i in 0..self.trace.len() {
256 trace.push(field::Entry::Trace(i))
257 }
258
259 let mut fs = HashSet::default();
260 if let DateTimeOpt::Some(_) = &self.date {
261 fs.insert(field::Entry::Date);
262 }
263 match &self.from {
264 From::Single { from: _, sender } => {
265 fs.insert(field::Entry::From);
266 if sender.is_some() {
267 fs.insert(field::Entry::Sender);
268 }
269 }
270 From::Multiple { from: _, sender: _ } => {
271 fs.insert(field::Entry::From);
272 fs.insert(field::Entry::Sender);
273 }
274 From::InvalidMissingFrom { sender } => {
275 if sender.is_some() {
276 fs.insert(field::Entry::Sender);
277 }
278 }
279 From::InvalidMissingSender { from: _ } => {
280 fs.insert(field::Entry::From);
281 }
282 }
283 if !self.reply_to.is_empty() {
284 fs.insert(field::Entry::ReplyTo);
285 }
286 if !self.to.is_empty() {
287 fs.insert(field::Entry::To);
288 }
289 if !self.cc.is_empty() {
290 fs.insert(field::Entry::Cc);
291 }
292 if self.bcc.is_some() {
293 fs.insert(field::Entry::Bcc);
294 }
295 if self.msg_id.is_some() {
296 fs.insert(field::Entry::MessageID);
297 }
298 if !self.in_reply_to.is_empty() {
299 fs.insert(field::Entry::InReplyTo);
300 }
301 if !self.references.is_empty() {
302 fs.insert(field::Entry::References);
303 }
304 if self.subject.is_some() {
305 fs.insert(field::Entry::Subject);
306 }
307 for i in 0..self.comments.len() {
308 fs.insert(field::Entry::Comments(i));
309 }
310 for i in 0..self.keywords.len() {
311 fs.insert(field::Entry::Keywords(i));
312 }
313 for i in 0..self.keywords.len() {
314 fs.insert(field::Entry::Keywords(i));
315 }
316 fs.insert(field::Entry::MIMEVersion);
317
318 (trace, fs)
319 }
320}
321
322impl<'a> Default for Imf<'a> {
323 fn default() -> Self {
324 Self::new()
325 }
326}
327
328#[derive(Debug, Default, PartialEq, ToStatic)]
329pub struct PartialImf<'a> {
330 date: Option<DateTime>,
331 from: Option<MailboxList<'a>>,
332 sender: Option<MailboxRef<'a>>,
333 reply_to: Option<Vec<AddressRef<'a>>>,
334 to: Option<Vec<AddressRef<'a>>>,
335 cc: Option<Vec<AddressRef<'a>>>,
336 bcc: Option<Vec<AddressRef<'a>>>,
337 msg_id: Option<MessageID<'a>>,
338 in_reply_to: Option<Vec<MessageID<'a>>>,
339 references: Option<Vec<MessageID<'a>>>,
340 subject: Option<Unstructured<'a>>,
341 comments: Vec<Unstructured<'a>>,
342 keywords: Vec<PhraseList<'a>>,
343 trace: Vec<TraceField<'a>>,
344 trace_complete: bool,
345 mime_version: Option<Version>,
346}
347
348#[derive(Clone, Copy, Debug)]
349pub enum AddFieldErr {
350 NoEntry,
353 Conflict,
356}
357
358impl<'a> PartialImf<'a> {
359 pub fn add_field(&mut self, f: Field<'a>) -> Result<Entry, AddFieldErr> {
360 match &f {
361 Field::ReturnPath(_) | Field::Received(_) => {
363 if self.trace_complete {
364 return Err(AddFieldErr::Conflict);
366 }
367 }
368 _ => {
370 self.trace_complete = true;
373 }
374 }
375 match f {
376 Field::Date(date) => set_if_new(&mut self.date, date, Entry::Date),
377 Field::From(from) => set_if_new(&mut self.from, from, Entry::From),
378 Field::Sender(sender) => set_if_new(&mut self.sender, sender, Entry::Sender),
379 Field::ReplyTo(reply_to) => set_if_new(&mut self.reply_to, reply_to, Entry::ReplyTo),
380 Field::To(to) => set_or_extend(&mut self.to, to, Entry::To),
381 Field::Cc(cc) => set_or_extend(&mut self.cc, cc, Entry::Cc),
382 Field::Bcc(bcc) => set_or_extend(&mut self.bcc, bcc, Entry::Bcc),
383 Field::MessageID(id) => set_if_new(&mut self.msg_id, id, Entry::MessageID),
384 Field::InReplyTo(in_reply_to) => {
385 set_if_new(&mut self.in_reply_to, in_reply_to, Entry::InReplyTo)
386 }
387 Field::References(refs) => set_if_new(&mut self.references, refs, Entry::References),
388 Field::Subject(subject) => set_if_new(&mut self.subject, subject, Entry::Subject),
389 Field::Comments(comments) => {
390 let idx = self.comments.len();
391 self.comments.push(comments);
392 Ok(Entry::Comments(idx))
393 }
394 Field::Keywords(kwds) => {
395 let idx = self.keywords.len();
396 self.keywords.push(kwds);
397 Ok(Entry::Keywords(idx))
398 }
399 Field::Received(received) => {
400 let idx = self.trace.len();
401 self.trace.push(TraceField::Received(received));
402 Ok(Entry::Trace(idx))
403 }
404 Field::ReturnPath(path) => {
405 let idx = self.trace.len();
406 self.trace.push(TraceField::ReturnPath(path));
407 Ok(Entry::Trace(idx))
408 }
409 Field::MIMEVersion(version) => {
410 set_if_new(&mut self.mime_version, version, Entry::MIMEVersion)
411 }
412 }
413 }
414
415 pub fn to_imf(self) -> Imf<'a> {
416 let date = match self.date {
417 Some(dt) => DateTimeOpt::Some(dt),
418 None => DateTimeOpt::InvalidMissing,
419 };
420 let from = match (self.from, self.sender) {
421 (None, sender) => From::InvalidMissingFrom { sender },
422 (Some(mut l), sender) if l.0.len() == 1 => From::Single {
423 from: l.0.pop().unwrap(),
424 sender,
425 },
426 (Some(l), Some(sender)) => From::Multiple { from: l, sender },
427 (Some(l), None) => From::InvalidMissingSender { from: l },
428 };
429
430 Imf {
431 date,
432 from,
433 reply_to: self.reply_to.unwrap_or_default(),
434 to: self.to.unwrap_or_default(),
435 cc: self.cc.unwrap_or_default(),
436 bcc: self.bcc,
437 msg_id: self.msg_id,
438 in_reply_to: self.in_reply_to.unwrap_or_default(),
439 references: self.references.unwrap_or_default(),
440 subject: self.subject,
441 comments: self.comments,
442 keywords: self.keywords,
443 trace: self.trace,
444 mime_version: self.mime_version,
445 }
446 }
447}
448
449fn set_if_new<T: PartialEq, U>(o: &mut Option<T>, x: T, y: U) -> Result<U, AddFieldErr> {
450 match *o {
451 None => {
452 *o = Some(x);
453 Ok(y)
454 }
455 Some(_) => Err(AddFieldErr::Conflict),
456 }
457}
458
459fn set_or_extend<T, U>(o: &mut Option<Vec<T>>, x: Vec<T>, y: U) -> Result<U, AddFieldErr> {
460 match o {
461 None => {
462 *o = Some(x);
463 Ok(y)
464 }
465 Some(v) => {
466 v.extend(x);
467 Err(AddFieldErr::NoEntry)
468 }
469 }
470}