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
210macro_rules! header_names {
211 ($($variant:ident = $id:literal, $name:literal, $lc:literal;)+) => {
212 #[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#[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(Address<'x>),
445
446 Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
448
449 TextList(
451 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
452 Vec<Cow<'x, str>>,
453 ),
454
455 DateTime(DateTime),
457
458 ContentType(ContentType<'x>),
460
461 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 List(Vec<Addr<'x>>),
477 Group(Vec<Group<'x>>),
479}
480
481#[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#[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#[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 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 HTTP,
654 HTTPS,
655 IMAP,
656 POP3,
657 Local, }
659
660pub trait MimeHeaders<'x> {
662 fn content_description(&self) -> Option<&str>;
664 fn content_disposition(&self) -> Option<&ContentType<'_>>;
666 fn content_id(&self) -> Option<&str>;
668 fn content_transfer_encoding(&self) -> Option<&str>;
670 fn content_type(&self) -> Option<&ContentType<'_>>;
672 fn content_language(&self) -> &HeaderValue<'_>;
674 fn content_location(&self) -> Option<&str>;
676 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 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}