Skip to main content

mail_parser/
lib.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6#![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/// RFC5322/RFC822 message parser.
18#[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/// An RFC5322/RFC822 message.
28#[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/// MIME Message Part
51#[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    //#[cfg_attr(feature = "rkyv", rkyv(omit_bounds))]
63    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/// MIME Part encoding type
72#[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
96/// Unique ID representing a MIME part within a message.
97pub type MessagePartId = u32;
98
99/// A text, binary or nested e-mail MIME message part.
100///
101/// - Text: Any text/* part
102/// - Binary: Any other part type that is not text.
103/// - Message: Nested RFC5322 message.
104/// - MultiPart: Multipart part.
105///
106#[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    /// Any text/* part
133    Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
134
135    /// A text/html part
136    Html(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
137
138    /// Any other part type that is not text.
139    Binary(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, [u8]>),
140
141    /// Any inline binary data that.
142    InlineBinary(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, [u8]>),
143
144    /// Nested RFC5322 message.
145    Message(#[cfg_attr(feature = "rkyv", rkyv(omit_bounds))] Message<'x>),
146
147    /// Multipart part
148    Multipart(Vec<MessagePartId>),
149}
150
151impl Default for PartType<'_> {
152    fn default() -> Self {
153        PartType::Multipart(Vec::with_capacity(0))
154    }
155}
156
157/// An RFC5322 or RFC2369 internet address.
158#[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    /// The address name including comments
166    #[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    /// An e-mail address (RFC5322/RFC2369) or URL (RFC2369)
171    #[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/// An RFC5322 address group.
177#[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    /// Group name
185    #[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    /// Addresses member of the group
190    #[cfg_attr(feature = "serde", serde(default))]
191    pub addresses: Vec<Addr<'x>>,
192}
193
194/// A message header.
195#[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
210macro_rules! header_names {
211    ($($variant:ident = $id:literal, $name:literal, $lc:literal;)+) => {
212        /// A header field
213        #[derive(Debug, Clone, PartialOrd, Ord)]
214        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215        #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
216        #[cfg_attr(
217            feature = "rkyv",
218            derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
219        )]
220        #[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq)))]
221        #[non_exhaustive]
222        pub enum HeaderName<'x> {
223            $($variant,)+
224            Other(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
225        }
226
227        impl HeaderName<'_> {
228            pub fn as_static_str(&self) -> &'static str {
229                match self {
230                    $(HeaderName::$variant => $name,)+
231                    HeaderName::Other(_) => "",
232                }
233            }
234
235            pub fn id(&self) -> u8 {
236                match self {
237                    $(HeaderName::$variant => $id,)+
238                    HeaderName::Other(_) => 37,
239                }
240            }
241
242            pub fn to_owned(&self) -> HeaderName<'static> {
243                match self {
244                    $(HeaderName::$variant => HeaderName::$variant,)+
245                    HeaderName::Other(name) => HeaderName::Other(name.to_string().into()),
246                }
247            }
248
249            pub fn into_owned(self) -> HeaderName<'static> {
250                match self {
251                    $(HeaderName::$variant => HeaderName::$variant,)+
252                    HeaderName::Other(name) => HeaderName::Other(name.into_owned().into()),
253                }
254            }
255        }
256
257        pub(crate) fn header_map(name: &[u8]) -> Option<HeaderName<'static>> {
258            hashify::tiny_map!(name,
259                $($lc => HeaderName::$variant,)+
260            )
261        }
262
263        #[cfg(feature = "rkyv")]
264        impl ArchivedHeaderName<'_> {
265            pub fn as_static_str(&self) -> &'static str {
266                match self {
267                    $(ArchivedHeaderName::$variant => $name,)+
268                    ArchivedHeaderName::Other(_) => "",
269                }
270            }
271
272            pub fn id(&self) -> u8 {
273                match self {
274                    $(ArchivedHeaderName::$variant => $id,)+
275                    ArchivedHeaderName::Other(_) => 37,
276                }
277            }
278        }
279
280        #[cfg(feature = "rkyv")]
281        impl From<&ArchivedHeaderName<'_>> for HeaderName<'static> {
282            fn from(value: &ArchivedHeaderName<'_>) -> Self {
283                match value {
284                    $(ArchivedHeaderName::$variant => HeaderName::$variant,)+
285                    ArchivedHeaderName::Other(name) => HeaderName::Other(name.to_string().into()),
286                }
287            }
288        }
289    };
290}
291
292header_names! {
293    Subject = 0, "Subject", "subject";
294    From = 1, "From", "from";
295    To = 2, "To", "to";
296    Cc = 3, "Cc", "cc";
297    Date = 4, "Date", "date";
298    Bcc = 5, "Bcc", "bcc";
299    ReplyTo = 6, "Reply-To", "reply-to";
300    Sender = 7, "Sender", "sender";
301    Comments = 8, "Comments", "comments";
302    InReplyTo = 9, "In-Reply-To", "in-reply-to";
303    Keywords = 10, "Keywords", "keywords";
304    Received = 11, "Received", "received";
305    MessageId = 12, "Message-ID", "message-id";
306    References = 13, "References", "references";
307    ReturnPath = 14, "Return-Path", "return-path";
308    MimeVersion = 15, "MIME-Version", "mime-version";
309    ContentDescription = 16, "Content-Description", "content-description";
310    ContentId = 17, "Content-ID", "content-id";
311    ContentLanguage = 18, "Content-Language", "content-language";
312    ContentLocation = 19, "Content-Location", "content-location";
313    ContentTransferEncoding = 20, "Content-Transfer-Encoding", "content-transfer-encoding";
314    ContentType = 21, "Content-Type", "content-type";
315    ContentDisposition = 22, "Content-Disposition", "content-disposition";
316    ResentTo = 23, "Resent-To", "resent-to";
317    ResentFrom = 24, "Resent-From", "resent-from";
318    ResentBcc = 25, "Resent-Bcc", "resent-bcc";
319    ResentCc = 26, "Resent-Cc", "resent-cc";
320    ResentSender = 27, "Resent-Sender", "resent-sender";
321    ResentDate = 28, "Resent-Date", "resent-date";
322    ResentMessageId = 29, "Resent-Message-ID", "resent-message-id";
323    ListArchive = 30, "List-Archive", "list-archive";
324    ListHelp = 31, "List-Help", "list-help";
325    ListId = 32, "List-ID", "list-id";
326    ListOwner = 33, "List-Owner", "list-owner";
327    ListPost = 34, "List-Post", "list-post";
328    ListSubscribe = 35, "List-Subscribe", "list-subscribe";
329    ListUnsubscribe = 36, "List-Unsubscribe", "list-unsubscribe";
330    ArcAuthenticationResults = 38, "ARC-Authentication-Results", "arc-authentication-results";
331    ArcMessageSignature = 39, "ARC-Message-Signature", "arc-message-signature";
332    ArcSeal = 40, "ARC-Seal", "arc-seal";
333    DkimSignature = 41, "DKIM-Signature", "dkim-signature";
334    Dkim2Signature = 42, "DKIM2-Signature", "dkim2-signature";
335    MessageInstance = 43, "Message-Instance", "message-instance";
336    AcceptLanguage = 44, "Accept-Language", "accept-language";
337    AlternateRecipient = 45, "Alternate-Recipient", "alternate-recipient";
338    ArchivedAt = 46, "Archived-At", "archived-at";
339    AuthenticationResults = 47, "Authentication-Results", "authentication-results";
340    AutoSubmitted = 48, "Auto-Submitted", "auto-submitted";
341    Autoforwarded = 49, "Autoforwarded", "autoforwarded";
342    Autosubmitted = 50, "Autosubmitted", "autosubmitted";
343    ContentAlternative = 51, "Content-Alternative", "content-alternative";
344    ContentDuration = 52, "Content-Duration", "content-duration";
345    ContentFeatures = 53, "Content-features", "content-features";
346    ContentMd5 = 54, "Content-MD5", "content-md5";
347    ContentTranslationType = 55, "Content-Translation-Type", "content-translation-type";
348    Conversion = 56, "Conversion", "conversion";
349    ConversionWithLoss = 57, "Conversion-With-Loss", "conversion-with-loss";
350    DlExpansionHistory = 58, "DL-Expansion-History", "dl-expansion-history";
351    DeferredDelivery = 59, "Deferred-Delivery", "deferred-delivery";
352    DeliveryDate = 60, "Delivery-Date", "delivery-date";
353    DiscardedX400IpmsExtensions = 61, "Discarded-X400-IPMS-Extensions", "discarded-x400-ipms-extensions";
354    DiscardedX400MtsExtensions = 62, "Discarded-X400-MTS-Extensions", "discarded-x400-mts-extensions";
355    DiscloseRecipients = 63, "Disclose-Recipients", "disclose-recipients";
356    DispositionNotificationOptions = 64, "Disposition-Notification-Options", "disposition-notification-options";
357    DispositionNotificationTo = 65, "Disposition-Notification-To", "disposition-notification-to";
358    DowngradedFinalRecipient = 66, "Downgraded-Final-Recipient", "downgraded-final-recipient";
359    DowngradedInReplyTo = 67, "Downgraded-In-Reply-To", "downgraded-in-reply-to";
360    DowngradedMessageId = 68, "Downgraded-Message-Id", "downgraded-message-id";
361    DowngradedOriginalRecipient = 69, "Downgraded-Original-Recipient", "downgraded-original-recipient";
362    DowngradedReferences = 70, "Downgraded-References", "downgraded-references";
363    Encoding = 71, "Encoding", "encoding";
364    Expires = 72, "Expires", "expires";
365    GenerateDeliveryReport = 73, "Generate-Delivery-Report", "generate-delivery-report";
366    HpOuter = 74, "HP-Outer", "hp-outer";
367    Importance = 75, "Importance", "importance";
368    IncompleteCopy = 76, "Incomplete-Copy", "incomplete-copy";
369    Language = 77, "Language", "language";
370    LatestDeliveryTime = 78, "Latest-Delivery-Time", "latest-delivery-time";
371    ListUnsubscribePost = 79, "List-Unsubscribe-Post", "list-unsubscribe-post";
372    MessageContext = 80, "Message-Context", "message-context";
373    MessageType = 81, "Message-Type", "message-type";
374    MmhsExemptedAddress = 82, "MMHS-Exempted-Address", "mmhs-exempted-address";
375    MmhsExtendedAuthorisationInfo = 83, "MMHS-Extended-Authorisation-Info", "mmhs-extended-authorisation-info";
376    MmhsSubjectIndicatorCodes = 84, "MMHS-Subject-Indicator-Codes", "mmhs-subject-indicator-codes";
377    MmhsHandlingInstructions = 85, "MMHS-Handling-Instructions", "mmhs-handling-instructions";
378    MmhsMessageInstructions = 86, "MMHS-Message-Instructions", "mmhs-message-instructions";
379    MmhsCodressMessageIndicator = 87, "MMHS-Codress-Message-Indicator", "mmhs-codress-message-indicator";
380    MmhsOriginatorReference = 88, "MMHS-Originator-Reference", "mmhs-originator-reference";
381    MmhsPrimaryPrecedence = 89, "MMHS-Primary-Precedence", "mmhs-primary-precedence";
382    MmhsCopyPrecedence = 90, "MMHS-Copy-Precedence", "mmhs-copy-precedence";
383    MmhsMessageType = 91, "MMHS-Message-Type", "mmhs-message-type";
384    MmhsOtherRecipientsIndicatorTo = 92, "MMHS-Other-Recipients-Indicator-To", "mmhs-other-recipients-indicator-to";
385    MmhsOtherRecipientsIndicatorCc = 93, "MMHS-Other-Recipients-Indicator-CC", "mmhs-other-recipients-indicator-cc";
386    MmhsAcp127MessageIdentifier = 94, "MMHS-Acp127-Message-Identifier", "mmhs-acp127-message-identifier";
387    MmhsOriginatorPlad = 95, "MMHS-Originator-PLAD", "mmhs-originator-plad";
388    MtPriority = 96, "MT-Priority", "mt-priority";
389    Organization = 97, "Organization", "organization";
390    OriginalEncodedInformationTypes = 98, "Original-Encoded-Information-Types", "original-encoded-information-types";
391    OriginalFrom = 99, "Original-From", "original-from";
392    OriginalMessageId = 100, "Original-Message-ID", "original-message-id";
393    OriginalRecipient = 101, "Original-Recipient", "original-recipient";
394    OriginatorReturnAddress = 102, "Originator-Return-Address", "originator-return-address";
395    OriginalSubject = 103, "Original-Subject", "original-subject";
396    PicsLabel = 104, "PICS-Label", "pics-label";
397    PreventNonDeliveryReport = 105, "Prevent-NonDelivery-Report", "prevent-nondelivery-report";
398    Priority = 106, "Priority", "priority";
399    ReceivedSpf = 107, "Received-SPF", "received-spf";
400    ReplyBy = 108, "Reply-By", "reply-by";
401    RequireRecipientValidSince = 109, "Require-Recipient-Valid-Since", "require-recipient-valid-since";
402    Sensitivity = 110, "Sensitivity", "sensitivity";
403    Solicitation = 111, "Solicitation", "solicitation";
404    Supersedes = 112, "Supersedes", "supersedes";
405    TlsReportDomain = 113, "TLS-Report-Domain", "tls-report-domain";
406    TlsReportSubmitter = 114, "TLS-Report-Submitter", "tls-report-submitter";
407    TlsRequired = 115, "TLS-Required", "tls-required";
408    VbrInfo = 116, "VBR-Info", "vbr-info";
409    X400ContentIdentifier = 117, "X400-Content-Identifier", "x400-content-identifier";
410    X400ContentReturn = 118, "X400-Content-Return", "x400-content-return";
411    X400ContentType = 119, "X400-Content-Type", "x400-content-type";
412    X400MtsIdentifier = 120, "X400-MTS-Identifier", "x400-mts-identifier";
413    X400Originator = 121, "X400-Originator", "x400-originator";
414    X400Received = 122, "X400-Received", "x400-received";
415    X400Recipients = 123, "X400-Recipients", "x400-recipients";
416    X400Trace = 124, "X400-Trace", "x400-trace";
417    ApparentlyTo = 125, "Apparently-To", "apparently-to";
418    Author = 126, "Author", "author";
419    CfblAddress = 127, "CFBL-Address", "cfbl-address";
420    CfblFeedbackId = 128, "CFBL-Feedback-ID", "cfbl-feedback-id";
421    DeliveredTo = 129, "Delivered-To", "delivered-to";
422    EdiintFeatures = 130, "EDIINT-Features", "ediint-features";
423    EesstVersion = 131, "Eesst-Version", "eesst-version";
424    ErrorsTo = 132, "Errors-To", "errors-to";
425    Face = 133, "Face", "face";
426    FormSub = 134, "Form-Sub", "form-sub";
427    JabberId = 135, "Jabber-ID", "jabber-id";
428    MmhsAuthorizingUsers = 136, "MMHS-Authorizing-Users", "mmhs-authorizing-users";
429    Privicon = 137, "Privicon", "privicon";
430    SioLabel = 138, "SIO-Label", "sio-label";
431    SioLabelHistory = 139, "SIO-Label-History", "sio-label-history";
432    WrongRecipient = 140, "Wrong-Recipient", "wrong-recipient";
433}
434
435/// Parsed header value.
436#[derive(Debug, PartialEq, Eq, Clone, Default)]
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
438#[cfg_attr(
439    feature = "rkyv",
440    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
441)]
442pub enum HeaderValue<'x> {
443    /// Address list or group
444    Address(Address<'x>),
445
446    /// String
447    Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
448
449    /// List of strings
450    TextList(
451        #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
452        Vec<Cow<'x, str>>,
453    ),
454
455    /// Datetime
456    DateTime(DateTime),
457
458    /// Content-Type or Content-Disposition header
459    ContentType(ContentType<'x>),
460
461    /// Received header
462    Received(Box<Received<'x>>),
463
464    #[default]
465    Empty,
466}
467
468#[derive(Debug, PartialEq, Eq, Clone)]
469#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
470#[cfg_attr(
471    feature = "rkyv",
472    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
473)]
474pub enum Address<'x> {
475    /// Address list
476    List(Vec<Addr<'x>>),
477    /// Group of addresses
478    Group(Vec<Group<'x>>),
479}
480
481/// Header form
482#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
483pub enum HeaderForm {
484    Raw,
485    Text,
486    Addresses,
487    GroupedAddresses,
488    MessageIds,
489    Date,
490    URLs,
491}
492/// An RFC2047 Content-Type or RFC2183 Content-Disposition MIME header field.
493#[derive(Debug, PartialEq, Eq, Clone)]
494#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
495#[cfg_attr(
496    feature = "rkyv",
497    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
498)]
499pub struct ContentType<'x> {
500    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
501    pub c_type: Cow<'x, str>,
502    #[cfg_attr(feature = "serde", serde(default))]
503    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
504    pub c_subtype: Option<Cow<'x, str>>,
505    #[cfg_attr(feature = "serde", serde(default))]
506    pub attributes: Option<Vec<Attribute<'x>>>,
507}
508
509#[derive(Debug, PartialEq, Eq, Clone)]
510#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
511#[cfg_attr(
512    feature = "rkyv",
513    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
514)]
515pub struct Attribute<'x> {
516    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
517    pub name: Cow<'x, str>,
518    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))]
519    pub value: Cow<'x, str>,
520}
521
522/// An RFC5322 datetime.
523#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
524#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
525#[cfg_attr(
526    feature = "rkyv",
527    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
528)]
529pub struct DateTime {
530    pub year: u16,
531    pub month: u8,
532    pub day: u8,
533    pub hour: u8,
534    pub minute: u8,
535    pub second: u8,
536    pub tz_before_gmt: bool,
537    pub tz_hour: u8,
538    pub tz_minute: u8,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq, Default)]
542#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
543#[cfg_attr(
544    feature = "rkyv",
545    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
546)]
547pub struct Received<'x> {
548    #[cfg_attr(feature = "serde", serde(default))]
549    pub from: Option<Host<'x>>,
550    #[cfg_attr(feature = "serde", serde(default))]
551    pub from_ip: Option<IpAddr>,
552    #[cfg_attr(feature = "serde", serde(default))]
553    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
554    pub from_iprev: Option<Cow<'x, str>>,
555    #[cfg_attr(feature = "serde", serde(default))]
556    pub by: Option<Host<'x>>,
557    #[cfg_attr(feature = "serde", serde(default))]
558    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
559    pub for_: Option<Cow<'x, str>>,
560    #[cfg_attr(feature = "serde", serde(default))]
561    pub with: Option<Protocol>,
562    #[cfg_attr(feature = "serde", serde(default))]
563    pub tls_version: Option<TlsVersion>,
564    #[cfg_attr(feature = "serde", serde(default))]
565    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
566    pub tls_cipher: Option<Cow<'x, str>>,
567    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
568    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
569    pub id: Option<Cow<'x, str>>,
570    #[cfg_attr(feature = "serde", serde(default))]
571    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
572    pub ident: Option<Cow<'x, str>>,
573    #[cfg_attr(feature = "serde", serde(default))]
574    pub helo: Option<Host<'x>>,
575    #[cfg_attr(feature = "serde", serde(default))]
576    pub helo_cmd: Option<Greeting>,
577    #[cfg_attr(feature = "serde", serde(default))]
578    #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
579    pub via: Option<Cow<'x, str>>,
580    pub date: Option<DateTime>,
581}
582
583#[derive(Debug, Clone, PartialEq, Eq)]
584#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
585#[cfg_attr(
586    feature = "rkyv",
587    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
588)]
589pub enum Host<'x> {
590    Name(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
591    IpAddr(IpAddr),
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
596#[cfg_attr(
597    feature = "rkyv",
598    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
599)]
600pub enum TlsVersion {
601    SSLv2,
602    SSLv3,
603    TLSv1_0,
604    TLSv1_1,
605    TLSv1_2,
606    TLSv1_3,
607    DTLSv1_0,
608    DTLSv1_2,
609    DTLSv1_3,
610}
611
612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
613#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
614#[cfg_attr(
615    feature = "rkyv",
616    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
617)]
618pub enum Greeting {
619    Helo,
620    Ehlo,
621    Lhlo,
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq)]
625#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
626#[cfg_attr(
627    feature = "rkyv",
628    derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive)
629)]
630#[allow(clippy::upper_case_acronyms)]
631pub enum Protocol {
632    // IANA Mail Transmission Types
633    SMTP,
634    ESMTP,
635    ESMTPA,
636    ESMTPS,
637    ESMTPSA,
638    LMTP,
639    LMTPA,
640    LMTPS,
641    LMTPSA,
642    MMS,
643    UTF8SMTP,
644    UTF8SMTPA,
645    UTF8SMTPS,
646    UTF8SMTPSA,
647    UTF8LMTP,
648    UTF8LMTPA,
649    UTF8LMTPS,
650    UTF8LMTPSA,
651
652    // Non-Standard Mail Transmission Types
653    HTTP,
654    HTTPS,
655    IMAP,
656    POP3,
657    Local, // includes stdin, socket, etc.
658}
659
660/// MIME Header field access trait
661pub trait MimeHeaders<'x> {
662    /// Returns the Content-Description field
663    fn content_description(&self) -> Option<&str>;
664    /// Returns the Content-Disposition field
665    fn content_disposition(&self) -> Option<&ContentType<'_>>;
666    /// Returns the Content-ID field
667    fn content_id(&self) -> Option<&str>;
668    /// Returns the Content-Encoding field
669    fn content_transfer_encoding(&self) -> Option<&str>;
670    /// Returns the Content-Type field
671    fn content_type(&self) -> Option<&ContentType<'_>>;
672    /// Returns the Content-Language field
673    fn content_language(&self) -> &HeaderValue<'_>;
674    /// Returns the Content-Location field
675    fn content_location(&self) -> Option<&str>;
676    /// Returns the attachment name, if any.
677    fn attachment_name(&self) -> Option<&str> {
678        self.content_disposition()
679            .and_then(|cd| cd.attribute("filename"))
680            .or_else(|| self.content_type().and_then(|ct| ct.attribute("name")))
681    }
682    // Returns true is the content type matches
683    fn is_content_type(&self, type_: &str, subtype: &str) -> bool {
684        self.content_type().is_some_and(|ct| {
685            ct.c_type.eq_ignore_ascii_case(type_)
686                && ct
687                    .c_subtype
688                    .as_ref()
689                    .is_some_and(|st| st.eq_ignore_ascii_case(subtype))
690        })
691    }
692}
693
694pub trait GetHeader<'x> {
695    fn header_value(&self, name: &HeaderName<'_>) -> Option<&HeaderValue<'x>>;
696    fn header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header<'x>>;
697}
698
699struct BodyPartIterator<'x> {
700    message: &'x Message<'x>,
701    list: &'x [MessagePartId],
702    pos: i32,
703}
704
705struct AttachmentIterator<'x> {
706    message: &'x Message<'x>,
707    pos: i32,
708}