1#![doc = include_str!("../README.md")]
7#![deny(rust_2018_idioms)]
8#[forbid(unsafe_code)]
9pub mod core;
10pub mod decoders;
11pub mod mailbox;
12pub mod parsers;
13
14use parsers::MessageStream;
15use std::{borrow::Cow, collections::HashMap, hash::Hash, net::IpAddr};
16
17#[derive(Debug, PartialEq, Eq, Clone)]
19#[allow(unpredictable_function_pointer_comparisons)]
20pub struct MessageParser {
21 pub(crate) header_map: HashMap<HeaderName<'static>, HdrParseFnc>,
22 pub(crate) def_hdr_parse_fnc: HdrParseFnc,
23}
24
25pub(crate) type HdrParseFnc = for<'x> fn(&mut MessageStream<'x>) -> crate::HeaderValue<'x>;
26
27#[derive(Debug, Default, PartialEq, Clone)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(
31 feature = "rkyv",
32 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
33)]
34pub struct Message<'x> {
35 #[cfg_attr(feature = "serde", serde(default))]
36 pub html_body: Vec<MessagePartId>,
37 #[cfg_attr(feature = "serde", serde(default))]
38 pub text_body: Vec<MessagePartId>,
39 #[cfg_attr(feature = "serde", serde(default))]
40 pub attachments: Vec<MessagePartId>,
41
42 #[cfg_attr(feature = "serde", serde(default))]
43 pub parts: Vec<MessagePart<'x>>,
44
45 #[cfg_attr(feature = "serde", serde(skip))]
46 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Skip))]
47 pub raw_message: Cow<'x, [u8]>,
48}
49
50#[derive(Debug, PartialEq, Default, Clone)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
53#[cfg_attr(
54 feature = "rkyv",
55 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
56)]
57pub struct MessagePart<'x> {
58 #[cfg_attr(feature = "serde", serde(default))]
59 pub headers: Vec<Header<'x>>,
60 pub is_encoding_problem: bool,
61 #[cfg_attr(feature = "serde", serde(default))]
62 pub body: PartType<'x>,
64 #[cfg_attr(feature = "serde", serde(skip))]
65 pub encoding: Encoding,
66 pub offset_header: u32,
67 pub offset_body: u32,
68 pub offset_end: u32,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
74#[cfg_attr(
75 feature = "rkyv",
76 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
77)]
78#[repr(u8)]
79pub enum Encoding {
80 #[default]
81 None = 0,
82 QuotedPrintable = 1,
83 Base64 = 2,
84}
85
86impl From<u8> for Encoding {
87 fn from(v: u8) -> Self {
88 match v {
89 1 => Encoding::QuotedPrintable,
90 2 => Encoding::Base64,
91 _ => Encoding::None,
92 }
93 }
94}
95
96pub type MessagePartId = u32;
98
99#[derive(Debug, PartialEq, Clone)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108#[cfg_attr(
109 feature = "rkyv",
110 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
111)]
112#[cfg_attr(
113 feature = "rkyv",
114 rkyv(serialize_bounds(
115 __S: rkyv::ser::Writer + rkyv::ser::Allocator,
116 __S::Error: rkyv::rancor::Source,
117 ))
118)]
119#[cfg_attr(
120 feature = "rkyv",
121 rkyv(deserialize_bounds(__D::Error: rkyv::rancor::Source))
122)]
123#[cfg_attr(
124 feature = "rkyv",
125 rkyv(bytecheck(
126 bounds(
127 __C: rkyv::validation::ArchiveContext,
128 )
129 ))
130)]
131pub enum PartType<'x> {
132 Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
134
135 Html(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
137
138 Binary(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, [u8]>),
140
141 InlineBinary(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, [u8]>),
143
144 Message(#[cfg_attr(feature = "rkyv", rkyv(omit_bounds))] Message<'x>),
146
147 Multipart(Vec<MessagePartId>),
149}
150
151impl Default for PartType<'_> {
152 fn default() -> Self {
153 PartType::Multipart(Vec::with_capacity(0))
154 }
155}
156
157#[derive(Debug, PartialEq, Eq, Clone)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160#[cfg_attr(
161 feature = "rkyv",
162 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
163)]
164pub struct Addr<'x> {
165 #[cfg_attr(feature = "serde", serde(default))]
167 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
168 pub name: Option<Cow<'x, str>>,
169
170 #[cfg_attr(feature = "serde", serde(default))]
172 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
173 pub address: Option<Cow<'x, str>>,
174}
175
176#[derive(Debug, PartialEq, Eq, Clone)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179#[cfg_attr(
180 feature = "rkyv",
181 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
182)]
183pub struct Group<'x> {
184 #[cfg_attr(feature = "serde", serde(default))]
186 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
187 pub name: Option<Cow<'x, str>>,
188
189 #[cfg_attr(feature = "serde", serde(default))]
191 pub addresses: Vec<Addr<'x>>,
192}
193
194#[derive(Debug, PartialEq, Eq, Clone)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197#[cfg_attr(
198 feature = "rkyv",
199 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
200)]
201#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq)))]
202pub struct Header<'x> {
203 pub name: HeaderName<'x>,
204 pub value: HeaderValue<'x>,
205 pub offset_field: u32,
206 pub offset_start: u32,
207 pub offset_end: u32,
208}
209
210#[derive(Debug, Clone, PartialOrd, Ord)]
212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
214#[cfg_attr(
215 feature = "rkyv",
216 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
217)]
218#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq)))]
219#[non_exhaustive]
220pub enum HeaderName<'x> {
221 Subject,
222 From,
223 To,
224 Cc,
225 Date,
226 Bcc,
227 ReplyTo,
228 Sender,
229 Comments,
230 InReplyTo,
231 Keywords,
232 Received,
233 MessageId,
234 References,
235 ReturnPath,
236 MimeVersion,
237 ContentDescription,
238 ContentId,
239 ContentLanguage,
240 ContentLocation,
241 ContentTransferEncoding,
242 ContentType,
243 ContentDisposition,
244 ResentTo,
245 ResentFrom,
246 ResentBcc,
247 ResentCc,
248 ResentSender,
249 ResentDate,
250 ResentMessageId,
251 ListArchive,
252 ListHelp,
253 ListId,
254 ListOwner,
255 ListPost,
256 ListSubscribe,
257 ListUnsubscribe,
258 Other(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
259 DkimSignature,
260 ArcAuthenticationResults,
261 ArcMessageSignature,
262 ArcSeal,
263 Dkim2Signature,
264 MessageInstance,
265}
266
267#[derive(Debug, PartialEq, Eq, Clone, Default)]
269#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
270#[cfg_attr(
271 feature = "rkyv",
272 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
273)]
274pub enum HeaderValue<'x> {
275 Address(Address<'x>),
277
278 Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
280
281 TextList(
283 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
284 Vec<Cow<'x, str>>,
285 ),
286
287 DateTime(DateTime),
289
290 ContentType(ContentType<'x>),
292
293 Received(Box<Received<'x>>),
295
296 #[default]
297 Empty,
298}
299
300#[derive(Debug, PartialEq, Eq, Clone)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302#[cfg_attr(
303 feature = "rkyv",
304 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
305)]
306pub enum Address<'x> {
307 List(Vec<Addr<'x>>),
309 Group(Vec<Group<'x>>),
311}
312
313#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
315pub enum HeaderForm {
316 Raw,
317 Text,
318 Addresses,
319 GroupedAddresses,
320 MessageIds,
321 Date,
322 URLs,
323}
324#[derive(Debug, PartialEq, Eq, Clone)]
326#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
327#[cfg_attr(
328 feature = "rkyv",
329 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
330)]
331pub struct ContentType<'x> {
332 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
333 pub c_type: Cow<'x, str>,
334 #[cfg_attr(feature = "serde", serde(default))]
335 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
336 pub c_subtype: Option<Cow<'x, str>>,
337 #[cfg_attr(feature = "serde", serde(default))]
338 pub attributes: Option<Vec<Attribute<'x>>>,
339}
340
341#[derive(Debug, PartialEq, Eq, Clone)]
342#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
343#[cfg_attr(
344 feature = "rkyv",
345 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
346)]
347pub struct Attribute<'x> {
348 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
349 pub name: Cow<'x, str>,
350 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
351 pub value: Cow<'x, str>,
352}
353
354#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357#[cfg_attr(
358 feature = "rkyv",
359 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
360)]
361pub struct DateTime {
362 pub year: u16,
363 pub month: u8,
364 pub day: u8,
365 pub hour: u8,
366 pub minute: u8,
367 pub second: u8,
368 pub tz_before_gmt: bool,
369 pub tz_hour: u8,
370 pub tz_minute: u8,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Default)]
374#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
375#[cfg_attr(
376 feature = "rkyv",
377 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
378)]
379pub struct Received<'x> {
380 #[cfg_attr(feature = "serde", serde(default))]
381 pub from: Option<Host<'x>>,
382 #[cfg_attr(feature = "serde", serde(default))]
383 pub from_ip: Option<IpAddr>,
384 #[cfg_attr(feature = "serde", serde(default))]
385 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
386 pub from_iprev: Option<Cow<'x, str>>,
387 #[cfg_attr(feature = "serde", serde(default))]
388 pub by: Option<Host<'x>>,
389 #[cfg_attr(feature = "serde", serde(default))]
390 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
391 pub for_: Option<Cow<'x, str>>,
392 #[cfg_attr(feature = "serde", serde(default))]
393 pub with: Option<Protocol>,
394 #[cfg_attr(feature = "serde", serde(default))]
395 pub tls_version: Option<TlsVersion>,
396 #[cfg_attr(feature = "serde", serde(default))]
397 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
398 pub tls_cipher: Option<Cow<'x, str>>,
399 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
400 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
401 pub id: Option<Cow<'x, str>>,
402 #[cfg_attr(feature = "serde", serde(default))]
403 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
404 pub ident: Option<Cow<'x, str>>,
405 #[cfg_attr(feature = "serde", serde(default))]
406 pub helo: Option<Host<'x>>,
407 #[cfg_attr(feature = "serde", serde(default))]
408 pub helo_cmd: Option<Greeting>,
409 #[cfg_attr(feature = "serde", serde(default))]
410 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
411 pub via: Option<Cow<'x, str>>,
412 pub date: Option<DateTime>,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq)]
416#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
417#[cfg_attr(
418 feature = "rkyv",
419 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
420)]
421pub enum Host<'x> {
422 Name(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
423 IpAddr(IpAddr),
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
428#[cfg_attr(
429 feature = "rkyv",
430 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
431)]
432pub enum TlsVersion {
433 SSLv2,
434 SSLv3,
435 TLSv1_0,
436 TLSv1_1,
437 TLSv1_2,
438 TLSv1_3,
439 DTLSv1_0,
440 DTLSv1_2,
441 DTLSv1_3,
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446#[cfg_attr(
447 feature = "rkyv",
448 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
449)]
450pub enum Greeting {
451 Helo,
452 Ehlo,
453 Lhlo,
454}
455
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
458#[cfg_attr(
459 feature = "rkyv",
460 derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
461)]
462#[allow(clippy::upper_case_acronyms)]
463pub enum Protocol {
464 SMTP,
466 ESMTP,
467 ESMTPA,
468 ESMTPS,
469 ESMTPSA,
470 LMTP,
471 LMTPA,
472 LMTPS,
473 LMTPSA,
474 MMS,
475 UTF8SMTP,
476 UTF8SMTPA,
477 UTF8SMTPS,
478 UTF8SMTPSA,
479 UTF8LMTP,
480 UTF8LMTPA,
481 UTF8LMTPS,
482 UTF8LMTPSA,
483
484 HTTP,
486 HTTPS,
487 IMAP,
488 POP3,
489 Local, }
491
492pub trait MimeHeaders<'x> {
494 fn content_description(&self) -> Option<&str>;
496 fn content_disposition(&self) -> Option<&ContentType<'_>>;
498 fn content_id(&self) -> Option<&str>;
500 fn content_transfer_encoding(&self) -> Option<&str>;
502 fn content_type(&self) -> Option<&ContentType<'_>>;
504 fn content_language(&self) -> &HeaderValue<'_>;
506 fn content_location(&self) -> Option<&str>;
508 fn attachment_name(&self) -> Option<&str> {
510 self.content_disposition()
511 .and_then(|cd| cd.attribute("filename"))
512 .or_else(|| self.content_type().and_then(|ct| ct.attribute("name")))
513 }
514 fn is_content_type(&self, type_: &str, subtype: &str) -> bool {
516 self.content_type().is_some_and(|ct| {
517 ct.c_type.eq_ignore_ascii_case(type_)
518 && ct
519 .c_subtype
520 .as_ref()
521 .is_some_and(|st| st.eq_ignore_ascii_case(subtype))
522 })
523 }
524}
525
526pub trait GetHeader<'x> {
527 fn header_value(&self, name: &HeaderName<'_>) -> Option<&HeaderValue<'x>>;
528 fn header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header<'x>>;
529}
530
531struct BodyPartIterator<'x> {
532 message: &'x Message<'x>,
533 list: &'x [MessagePartId],
534 pos: i32,
535}
536
537struct AttachmentIterator<'x> {
538 message: &'x Message<'x>,
539 pos: i32,
540}