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