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 = $tag:literal, id = $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 #[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#[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(Address<'x>),
446
447 Text(#[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::AsOwned))] Cow<'x, str>),
449
450 TextList(
452 #[cfg_attr(feature = "rkyv", rkyv(with = rkyv::with::Map<rkyv::with::AsOwned>))]
453 Vec<Cow<'x, str>>,
454 ),
455
456 DateTime(DateTime),
458
459 ContentType(ContentType<'x>),
461
462 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 List(Vec<Addr<'x>>),
478 Group(Vec<Group<'x>>),
480}
481
482#[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#[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#[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 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 HTTP,
655 HTTPS,
656 IMAP,
657 POP3,
658 Local, }
660
661pub trait MimeHeaders<'x> {
663 fn content_description(&self) -> Option<&str>;
665 fn content_disposition(&self) -> Option<&ContentType<'_>>;
667 fn content_id(&self) -> Option<&str>;
669 fn content_transfer_encoding(&self) -> Option<&str>;
671 fn content_type(&self) -> Option<&ContentType<'_>>;
673 fn content_language(&self) -> &HeaderValue<'_>;
675 fn content_location(&self) -> Option<&str>;
677 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 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}