mail_parser/core/
header.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use core::fmt;
8use std::hash::Hash;
9use std::net::IpAddr;
10use std::{borrow::Cow, fmt::Display};
11
12use crate::{
13    Address, Attribute, ContentType, DateTime, GetHeader, Greeting, Header, HeaderName,
14    HeaderValue, Host, Message, MessagePart, MessagePartId, MimeHeaders, PartType, Protocol,
15    Received, TlsVersion,
16};
17
18impl<'x> Header<'x> {
19    /// Returns the header name
20    pub fn name(&self) -> &str {
21        self.name.as_str()
22    }
23
24    /// Returns the parsed header value
25    pub fn value(&self) -> &HeaderValue<'x> {
26        &self.value
27    }
28
29    /// Returns the raw offset start
30    pub fn offset_start(&self) -> u32 {
31        self.offset_start
32    }
33
34    /// Returns the raw offset end
35    pub fn offset_end(&self) -> u32 {
36        self.offset_end
37    }
38
39    /// Returns the raw offset of the header name
40    pub fn offset_field(&self) -> u32 {
41        self.offset_field
42    }
43
44    /// Returns an owned version of the header
45    pub fn into_owned(self) -> Header<'static> {
46        Header {
47            name: self.name.into_owned(),
48            value: self.value.into_owned(),
49            offset_field: self.offset_field,
50            offset_start: self.offset_start,
51            offset_end: self.offset_end,
52        }
53    }
54}
55
56impl<'x> HeaderValue<'x> {
57    pub fn is_empty(&self) -> bool {
58        *self == HeaderValue::Empty
59    }
60
61    pub fn unwrap_text(self) -> Cow<'x, str> {
62        match self {
63            HeaderValue::Text(s) => s,
64            _ => panic!("HeaderValue::unwrap_text called on non-Text value"),
65        }
66    }
67
68    pub fn unwrap_text_list(self) -> Vec<Cow<'x, str>> {
69        match self {
70            HeaderValue::TextList(l) => l,
71            HeaderValue::Text(s) => vec![s],
72            _ => panic!("HeaderValue::unwrap_text_list called on non-TextList value"),
73        }
74    }
75
76    pub fn unwrap_datetime(self) -> DateTime {
77        match self {
78            HeaderValue::DateTime(d) => d,
79            _ => panic!("HeaderValue::unwrap_datetime called on non-DateTime value"),
80        }
81    }
82
83    pub fn unwrap_address(self) -> Address<'x> {
84        match self {
85            HeaderValue::Address(a) => a,
86            _ => panic!("HeaderValue::unwrap_address called on non-Address value"),
87        }
88    }
89
90    pub fn unwrap_content_type(self) -> ContentType<'x> {
91        match self {
92            HeaderValue::ContentType(c) => c,
93            _ => panic!("HeaderValue::unwrap_content_type called on non-ContentType value"),
94        }
95    }
96
97    pub fn unwrap_received(self) -> Received<'x> {
98        match self {
99            HeaderValue::Received(r) => *r,
100            _ => panic!("HeaderValue::unwrap_received called on non-Received value"),
101        }
102    }
103
104    pub fn into_text(self) -> Option<Cow<'x, str>> {
105        match self {
106            HeaderValue::Text(s) => Some(s),
107            _ => None,
108        }
109    }
110
111    pub fn into_text_list(self) -> Option<Vec<Cow<'x, str>>> {
112        match self {
113            HeaderValue::Text(s) => Some(vec![s]),
114            HeaderValue::TextList(l) => Some(l),
115            _ => None,
116        }
117    }
118
119    pub fn into_address(self) -> Option<Address<'x>> {
120        match self {
121            HeaderValue::Address(a) => Some(a),
122            _ => None,
123        }
124    }
125
126    pub fn into_datetime(self) -> Option<DateTime> {
127        match self {
128            HeaderValue::DateTime(d) => Some(d),
129            _ => None,
130        }
131    }
132
133    pub fn into_content_type(self) -> Option<ContentType<'x>> {
134        match self {
135            HeaderValue::ContentType(c) => Some(c),
136            _ => None,
137        }
138    }
139
140    pub fn into_received(self) -> Option<Received<'x>> {
141        match self {
142            HeaderValue::Received(r) => Some(*r),
143            _ => None,
144        }
145    }
146
147    pub fn as_text(&self) -> Option<&str> {
148        match *self {
149            HeaderValue::Text(ref s) => Some(s),
150            HeaderValue::TextList(ref l) => l.last()?.as_ref().into(),
151            _ => None,
152        }
153    }
154
155    pub fn as_text_list(&self) -> Option<&[Cow<'x, str>]> {
156        match *self {
157            HeaderValue::Text(ref s) => Some(std::slice::from_ref(s)),
158            HeaderValue::TextList(ref l) => Some(l.as_slice()),
159            _ => None,
160        }
161    }
162
163    pub fn as_address(&self) -> Option<&Address<'x>> {
164        match *self {
165            HeaderValue::Address(ref a) => Some(a),
166            _ => None,
167        }
168    }
169
170    pub fn as_received(&self) -> Option<&Received<'x>> {
171        match *self {
172            HeaderValue::Received(ref r) => Some(r),
173            _ => None,
174        }
175    }
176
177    pub fn as_content_type(&self) -> Option<&ContentType<'x>> {
178        match *self {
179            HeaderValue::ContentType(ref c) => Some(c),
180            _ => None,
181        }
182    }
183
184    pub fn as_datetime(&self) -> Option<&DateTime> {
185        match *self {
186            HeaderValue::DateTime(ref d) => Some(d),
187            _ => None,
188        }
189    }
190
191    pub fn into_owned(self) -> HeaderValue<'static> {
192        match self {
193            HeaderValue::Address(addr) => HeaderValue::Address(addr.into_owned()),
194            HeaderValue::Text(text) => HeaderValue::Text(text.into_owned().into()),
195            HeaderValue::TextList(list) => HeaderValue::TextList(
196                list.into_iter()
197                    .map(|text| text.into_owned().into())
198                    .collect(),
199            ),
200            HeaderValue::DateTime(datetime) => HeaderValue::DateTime(datetime),
201            HeaderValue::ContentType(ct) => HeaderValue::ContentType(ContentType {
202                c_type: ct.c_type.into_owned().into(),
203                c_subtype: ct.c_subtype.map(|s| s.into_owned().into()),
204                attributes: ct.attributes.map(|attributes| {
205                    attributes
206                        .into_iter()
207                        .map(|a| Attribute {
208                            name: a.name.into_owned().into(),
209                            value: a.value.into_owned().into(),
210                        })
211                        .collect()
212                }),
213            }),
214            HeaderValue::Received(rcvd) => HeaderValue::Received(Box::new(rcvd.into_owned())),
215            HeaderValue::Empty => HeaderValue::Empty,
216        }
217    }
218
219    pub fn len(&self) -> usize {
220        match self {
221            HeaderValue::Text(text) => text.len(),
222            HeaderValue::TextList(list) => list.iter().map(|t| t.len()).sum(),
223            HeaderValue::Address(Address::List(list)) => list
224                .iter()
225                .map(|a| {
226                    a.name.as_ref().map_or(0, |a| a.len())
227                        + a.address.as_ref().map_or(0, |a| a.len())
228                })
229                .sum(),
230            HeaderValue::Address(Address::Group(grouplist)) => grouplist
231                .iter()
232                .flat_map(|g| g.addresses.iter())
233                .map(|a| {
234                    a.name.as_ref().map_or(0, |a| a.len())
235                        + a.address.as_ref().map_or(0, |a| a.len())
236                })
237                .sum(),
238            HeaderValue::DateTime(_) => 24,
239            HeaderValue::ContentType(ct) => {
240                ct.c_type.len()
241                    + ct.c_subtype.as_ref().map_or(0, |s| s.len())
242                    + ct.attributes.as_ref().map_or(0, |at| {
243                        at.iter().map(|a| a.name.len() + a.value.len()).sum()
244                    })
245            }
246            HeaderValue::Received(_) => 1,
247            HeaderValue::Empty => 0,
248        }
249    }
250}
251
252impl PartialEq for HeaderName<'_> {
253    fn eq(&self, other: &Self) -> bool {
254        match (self, other) {
255            (Self::Other(a), Self::Other(b)) => a.eq_ignore_ascii_case(b),
256            (Self::Subject, Self::Subject) => true,
257            (Self::From, Self::From) => true,
258            (Self::To, Self::To) => true,
259            (Self::Cc, Self::Cc) => true,
260            (Self::Date, Self::Date) => true,
261            (Self::Bcc, Self::Bcc) => true,
262            (Self::ReplyTo, Self::ReplyTo) => true,
263            (Self::Sender, Self::Sender) => true,
264            (Self::Comments, Self::Comments) => true,
265            (Self::InReplyTo, Self::InReplyTo) => true,
266            (Self::Keywords, Self::Keywords) => true,
267            (Self::Received, Self::Received) => true,
268            (Self::MessageId, Self::MessageId) => true,
269            (Self::References, Self::References) => true,
270            (Self::ReturnPath, Self::ReturnPath) => true,
271            (Self::MimeVersion, Self::MimeVersion) => true,
272            (Self::ContentDescription, Self::ContentDescription) => true,
273            (Self::ContentId, Self::ContentId) => true,
274            (Self::ContentLanguage, Self::ContentLanguage) => true,
275            (Self::ContentLocation, Self::ContentLocation) => true,
276            (Self::ContentTransferEncoding, Self::ContentTransferEncoding) => true,
277            (Self::ContentType, Self::ContentType) => true,
278            (Self::ContentDisposition, Self::ContentDisposition) => true,
279            (Self::ResentTo, Self::ResentTo) => true,
280            (Self::ResentFrom, Self::ResentFrom) => true,
281            (Self::ResentBcc, Self::ResentBcc) => true,
282            (Self::ResentCc, Self::ResentCc) => true,
283            (Self::ResentSender, Self::ResentSender) => true,
284            (Self::ResentDate, Self::ResentDate) => true,
285            (Self::ResentMessageId, Self::ResentMessageId) => true,
286            (Self::ListArchive, Self::ListArchive) => true,
287            (Self::ListHelp, Self::ListHelp) => true,
288            (Self::ListId, Self::ListId) => true,
289            (Self::ListOwner, Self::ListOwner) => true,
290            (Self::ListPost, Self::ListPost) => true,
291            (Self::ListSubscribe, Self::ListSubscribe) => true,
292            (Self::ListUnsubscribe, Self::ListUnsubscribe) => true,
293            (Self::ArcAuthenticationResults, Self::ArcAuthenticationResults) => true,
294            (Self::ArcMessageSignature, Self::ArcMessageSignature) => true,
295            (Self::ArcSeal, Self::ArcSeal) => true,
296            (Self::DkimSignature, Self::DkimSignature) => true,
297            _ => false,
298        }
299    }
300}
301
302impl Hash for HeaderName<'_> {
303    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
304        match self {
305            HeaderName::Other(value) => {
306                for ch in value.as_bytes() {
307                    ch.to_ascii_lowercase().hash(state)
308                }
309            }
310            _ => self.id().hash(state),
311        }
312    }
313}
314
315impl Eq for HeaderName<'_> {}
316
317impl<'x> From<HeaderName<'x>> for u8 {
318    fn from(name: HeaderName<'x>) -> Self {
319        name.id()
320    }
321}
322
323impl HeaderName<'_> {
324    pub fn to_owned(&self) -> HeaderName<'static> {
325        match self {
326            HeaderName::Other(name) => HeaderName::Other(name.to_string().into()),
327            HeaderName::Subject => HeaderName::Subject,
328            HeaderName::From => HeaderName::From,
329            HeaderName::To => HeaderName::To,
330            HeaderName::Cc => HeaderName::Cc,
331            HeaderName::Date => HeaderName::Date,
332            HeaderName::Bcc => HeaderName::Bcc,
333            HeaderName::ReplyTo => HeaderName::ReplyTo,
334            HeaderName::Sender => HeaderName::Sender,
335            HeaderName::Comments => HeaderName::Comments,
336            HeaderName::InReplyTo => HeaderName::InReplyTo,
337            HeaderName::Keywords => HeaderName::Keywords,
338            HeaderName::Received => HeaderName::Received,
339            HeaderName::MessageId => HeaderName::MessageId,
340            HeaderName::References => HeaderName::References,
341            HeaderName::ReturnPath => HeaderName::ReturnPath,
342            HeaderName::MimeVersion => HeaderName::MimeVersion,
343            HeaderName::ContentDescription => HeaderName::ContentDescription,
344            HeaderName::ContentId => HeaderName::ContentId,
345            HeaderName::ContentLanguage => HeaderName::ContentLanguage,
346            HeaderName::ContentLocation => HeaderName::ContentLocation,
347            HeaderName::ContentTransferEncoding => HeaderName::ContentTransferEncoding,
348            HeaderName::ContentType => HeaderName::ContentType,
349            HeaderName::ContentDisposition => HeaderName::ContentDisposition,
350            HeaderName::ResentTo => HeaderName::ResentTo,
351            HeaderName::ResentFrom => HeaderName::ResentFrom,
352            HeaderName::ResentBcc => HeaderName::ResentBcc,
353            HeaderName::ResentCc => HeaderName::ResentCc,
354            HeaderName::ResentSender => HeaderName::ResentSender,
355            HeaderName::ResentDate => HeaderName::ResentDate,
356            HeaderName::ResentMessageId => HeaderName::ResentMessageId,
357            HeaderName::ListArchive => HeaderName::ListArchive,
358            HeaderName::ListHelp => HeaderName::ListHelp,
359            HeaderName::ListId => HeaderName::ListId,
360            HeaderName::ListOwner => HeaderName::ListOwner,
361            HeaderName::ListPost => HeaderName::ListPost,
362            HeaderName::ListSubscribe => HeaderName::ListSubscribe,
363            HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
364            HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
365            HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
366            HeaderName::ArcSeal => HeaderName::ArcSeal,
367            HeaderName::DkimSignature => HeaderName::DkimSignature,
368        }
369    }
370
371    pub fn into_owned(self) -> HeaderName<'static> {
372        match self {
373            HeaderName::Other(name) => HeaderName::Other(name.into_owned().into()),
374            HeaderName::Subject => HeaderName::Subject,
375            HeaderName::From => HeaderName::From,
376            HeaderName::To => HeaderName::To,
377            HeaderName::Cc => HeaderName::Cc,
378            HeaderName::Date => HeaderName::Date,
379            HeaderName::Bcc => HeaderName::Bcc,
380            HeaderName::ReplyTo => HeaderName::ReplyTo,
381            HeaderName::Sender => HeaderName::Sender,
382            HeaderName::Comments => HeaderName::Comments,
383            HeaderName::InReplyTo => HeaderName::InReplyTo,
384            HeaderName::Keywords => HeaderName::Keywords,
385            HeaderName::Received => HeaderName::Received,
386            HeaderName::MessageId => HeaderName::MessageId,
387            HeaderName::References => HeaderName::References,
388            HeaderName::ReturnPath => HeaderName::ReturnPath,
389            HeaderName::MimeVersion => HeaderName::MimeVersion,
390            HeaderName::ContentDescription => HeaderName::ContentDescription,
391            HeaderName::ContentId => HeaderName::ContentId,
392            HeaderName::ContentLanguage => HeaderName::ContentLanguage,
393            HeaderName::ContentLocation => HeaderName::ContentLocation,
394            HeaderName::ContentTransferEncoding => HeaderName::ContentTransferEncoding,
395            HeaderName::ContentType => HeaderName::ContentType,
396            HeaderName::ContentDisposition => HeaderName::ContentDisposition,
397            HeaderName::ResentTo => HeaderName::ResentTo,
398            HeaderName::ResentFrom => HeaderName::ResentFrom,
399            HeaderName::ResentBcc => HeaderName::ResentBcc,
400            HeaderName::ResentCc => HeaderName::ResentCc,
401            HeaderName::ResentSender => HeaderName::ResentSender,
402            HeaderName::ResentDate => HeaderName::ResentDate,
403            HeaderName::ResentMessageId => HeaderName::ResentMessageId,
404            HeaderName::ListArchive => HeaderName::ListArchive,
405            HeaderName::ListHelp => HeaderName::ListHelp,
406            HeaderName::ListId => HeaderName::ListId,
407            HeaderName::ListOwner => HeaderName::ListOwner,
408            HeaderName::ListPost => HeaderName::ListPost,
409            HeaderName::ListSubscribe => HeaderName::ListSubscribe,
410            HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
411            HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
412            HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
413            HeaderName::ArcSeal => HeaderName::ArcSeal,
414            HeaderName::DkimSignature => HeaderName::DkimSignature,
415        }
416    }
417
418    pub fn into_string(self) -> String {
419        match self {
420            HeaderName::Other(name) => name.into_owned(),
421            _ => self.as_str().to_string(),
422        }
423    }
424
425    pub fn as_str(&self) -> &str {
426        match self {
427            HeaderName::Other(other) => other.as_ref(),
428            _ => self.as_static_str(),
429        }
430    }
431
432    pub fn as_static_str(&self) -> &'static str {
433        match self {
434            HeaderName::Subject => "Subject",
435            HeaderName::From => "From",
436            HeaderName::To => "To",
437            HeaderName::Cc => "Cc",
438            HeaderName::Date => "Date",
439            HeaderName::Bcc => "Bcc",
440            HeaderName::ReplyTo => "Reply-To",
441            HeaderName::Sender => "Sender",
442            HeaderName::Comments => "Comments",
443            HeaderName::InReplyTo => "In-Reply-To",
444            HeaderName::Keywords => "Keywords",
445            HeaderName::Received => "Received",
446            HeaderName::MessageId => "Message-ID",
447            HeaderName::References => "References",
448            HeaderName::ReturnPath => "Return-Path",
449            HeaderName::MimeVersion => "MIME-Version",
450            HeaderName::ContentDescription => "Content-Description",
451            HeaderName::ContentId => "Content-ID",
452            HeaderName::ContentLanguage => "Content-Language",
453            HeaderName::ContentLocation => "Content-Location",
454            HeaderName::ContentTransferEncoding => "Content-Transfer-Encoding",
455            HeaderName::ContentType => "Content-Type",
456            HeaderName::ContentDisposition => "Content-Disposition",
457            HeaderName::ResentTo => "Resent-To",
458            HeaderName::ResentFrom => "Resent-From",
459            HeaderName::ResentBcc => "Resent-Bcc",
460            HeaderName::ResentCc => "Resent-Cc",
461            HeaderName::ResentSender => "Resent-Sender",
462            HeaderName::ResentDate => "Resent-Date",
463            HeaderName::ResentMessageId => "Resent-Message-ID",
464            HeaderName::ListArchive => "List-Archive",
465            HeaderName::ListHelp => "List-Help",
466            HeaderName::ListId => "List-ID",
467            HeaderName::ListOwner => "List-Owner",
468            HeaderName::ListPost => "List-Post",
469            HeaderName::ListSubscribe => "List-Subscribe",
470            HeaderName::ListUnsubscribe => "List-Unsubscribe",
471            HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results",
472            HeaderName::ArcMessageSignature => "ARC-Message-Signature",
473            HeaderName::ArcSeal => "ARC-Seal",
474            HeaderName::DkimSignature => "DKIM-Signature",
475            HeaderName::Other(_) => "",
476        }
477    }
478
479    pub fn len(&self) -> usize {
480        match self {
481            HeaderName::Subject => "Subject".len(),
482            HeaderName::From => "From".len(),
483            HeaderName::To => "To".len(),
484            HeaderName::Cc => "Cc".len(),
485            HeaderName::Date => "Date".len(),
486            HeaderName::Bcc => "Bcc".len(),
487            HeaderName::ReplyTo => "Reply-To".len(),
488            HeaderName::Sender => "Sender".len(),
489            HeaderName::Comments => "Comments".len(),
490            HeaderName::InReplyTo => "In-Reply-To".len(),
491            HeaderName::Keywords => "Keywords".len(),
492            HeaderName::Received => "Received".len(),
493            HeaderName::MessageId => "Message-ID".len(),
494            HeaderName::References => "References".len(),
495            HeaderName::ReturnPath => "Return-Path".len(),
496            HeaderName::MimeVersion => "MIME-Version".len(),
497            HeaderName::ContentDescription => "Content-Description".len(),
498            HeaderName::ContentId => "Content-ID".len(),
499            HeaderName::ContentLanguage => "Content-Language".len(),
500            HeaderName::ContentLocation => "Content-Location".len(),
501            HeaderName::ContentTransferEncoding => "Content-Transfer-Encoding".len(),
502            HeaderName::ContentType => "Content-Type".len(),
503            HeaderName::ContentDisposition => "Content-Disposition".len(),
504            HeaderName::ResentTo => "Resent-To".len(),
505            HeaderName::ResentFrom => "Resent-From".len(),
506            HeaderName::ResentBcc => "Resent-Bcc".len(),
507            HeaderName::ResentCc => "Resent-Cc".len(),
508            HeaderName::ResentSender => "Resent-Sender".len(),
509            HeaderName::ResentDate => "Resent-Date".len(),
510            HeaderName::ResentMessageId => "Resent-Message-ID".len(),
511            HeaderName::ListArchive => "List-Archive".len(),
512            HeaderName::ListHelp => "List-Help".len(),
513            HeaderName::ListId => "List-ID".len(),
514            HeaderName::ListOwner => "List-Owner".len(),
515            HeaderName::ListPost => "List-Post".len(),
516            HeaderName::ListSubscribe => "List-Subscribe".len(),
517            HeaderName::ListUnsubscribe => "List-Unsubscribe".len(),
518            HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results".len(),
519            HeaderName::ArcMessageSignature => "ARC-Message-Signature".len(),
520            HeaderName::ArcSeal => "ARC-Seal".len(),
521            HeaderName::DkimSignature => "DKIM-Signature".len(),
522            HeaderName::Other(other) => other.len(),
523        }
524    }
525
526    /// Returns true if it is a MIME header.
527    pub fn is_mime_header(&self) -> bool {
528        matches!(
529            self,
530            HeaderName::ContentDescription
531                | HeaderName::ContentId
532                | HeaderName::ContentLanguage
533                | HeaderName::ContentLocation
534                | HeaderName::ContentTransferEncoding
535                | HeaderName::ContentType
536                | HeaderName::ContentDisposition
537        )
538    }
539
540    /// Returns true if it is an `Other` header name
541    pub fn is_other(&self) -> bool {
542        matches!(self, HeaderName::Other(_))
543    }
544
545    pub fn is_empty(&self) -> bool {
546        false
547    }
548
549    pub fn id(&self) -> u8 {
550        match self {
551            HeaderName::Subject => 0,
552            HeaderName::From => 1,
553            HeaderName::To => 2,
554            HeaderName::Cc => 3,
555            HeaderName::Date => 4,
556            HeaderName::Bcc => 5,
557            HeaderName::ReplyTo => 6,
558            HeaderName::Sender => 7,
559            HeaderName::Comments => 8,
560            HeaderName::InReplyTo => 9,
561            HeaderName::Keywords => 10,
562            HeaderName::Received => 11,
563            HeaderName::MessageId => 12,
564            HeaderName::References => 13,
565            HeaderName::ReturnPath => 14,
566            HeaderName::MimeVersion => 15,
567            HeaderName::ContentDescription => 16,
568            HeaderName::ContentId => 17,
569            HeaderName::ContentLanguage => 18,
570            HeaderName::ContentLocation => 19,
571            HeaderName::ContentTransferEncoding => 20,
572            HeaderName::ContentType => 21,
573            HeaderName::ContentDisposition => 22,
574            HeaderName::ResentTo => 23,
575            HeaderName::ResentFrom => 24,
576            HeaderName::ResentBcc => 25,
577            HeaderName::ResentCc => 26,
578            HeaderName::ResentSender => 27,
579            HeaderName::ResentDate => 28,
580            HeaderName::ResentMessageId => 29,
581            HeaderName::ListArchive => 30,
582            HeaderName::ListHelp => 31,
583            HeaderName::ListId => 32,
584            HeaderName::ListOwner => 33,
585            HeaderName::ListPost => 34,
586            HeaderName::ListSubscribe => 35,
587            HeaderName::ListUnsubscribe => 36,
588            HeaderName::Other(_) => 37,
589            HeaderName::ArcAuthenticationResults => 38,
590            HeaderName::ArcMessageSignature => 39,
591            HeaderName::ArcSeal => 40,
592            HeaderName::DkimSignature => 41,
593        }
594    }
595}
596
597impl<'x> MimeHeaders<'x> for Message<'x> {
598    fn content_description(&self) -> Option<&str> {
599        self.parts[0]
600            .headers
601            .header_value(&HeaderName::ContentDescription)
602            .and_then(|header| header.as_text())
603    }
604
605    fn content_disposition(&self) -> Option<&ContentType<'x>> {
606        self.parts[0]
607            .headers
608            .header_value(&HeaderName::ContentDisposition)
609            .and_then(|header| header.as_content_type())
610    }
611
612    fn content_id(&self) -> Option<&str> {
613        self.parts[0]
614            .headers
615            .header_value(&HeaderName::ContentId)
616            .and_then(|header| header.as_text())
617    }
618
619    fn content_transfer_encoding(&self) -> Option<&str> {
620        self.parts[0]
621            .headers
622            .header_value(&HeaderName::ContentTransferEncoding)
623            .and_then(|header| header.as_text())
624    }
625
626    fn content_type(&self) -> Option<&ContentType<'x>> {
627        self.parts[0]
628            .headers
629            .header_value(&HeaderName::ContentType)
630            .and_then(|header| header.as_content_type())
631    }
632
633    fn content_language(&self) -> &HeaderValue<'x> {
634        self.parts[0]
635            .headers
636            .header_value(&HeaderName::ContentLanguage)
637            .unwrap_or(&HeaderValue::Empty)
638    }
639
640    fn content_location(&self) -> Option<&str> {
641        self.parts[0]
642            .headers
643            .header_value(&HeaderName::ContentLocation)
644            .and_then(|header| header.as_text())
645    }
646}
647
648impl<'x> MessagePart<'x> {
649    /// Returns the body part's contents as a `u8` slice
650    pub fn contents(&self) -> &[u8] {
651        match &self.body {
652            PartType::Text(text) | PartType::Html(text) => text.as_bytes(),
653            PartType::Binary(bin) | PartType::InlineBinary(bin) => bin.as_ref(),
654            PartType::Message(message) => message.raw_message(),
655            PartType::Multipart(_) => b"",
656        }
657    }
658
659    /// Returns the body part's contents as a `str`
660    pub fn text_contents(&self) -> Option<&str> {
661        match &self.body {
662            PartType::Text(text) | PartType::Html(text) => text.as_ref().into(),
663            PartType::Binary(bin) | PartType::InlineBinary(bin) => {
664                std::str::from_utf8(bin.as_ref()).ok()
665            }
666            PartType::Message(message) => std::str::from_utf8(message.raw_message()).ok(),
667            PartType::Multipart(_) => None,
668        }
669    }
670
671    /// Returns the nested message
672    pub fn message(&self) -> Option<&Message<'x>> {
673        if let PartType::Message(message) = &self.body {
674            Some(message)
675        } else {
676            None
677        }
678    }
679
680    /// Returns the sub parts ids of a MIME part
681    pub fn sub_parts(&self) -> Option<&[MessagePartId]> {
682        if let PartType::Multipart(parts) = &self.body {
683            Some(parts.as_ref())
684        } else {
685            None
686        }
687    }
688
689    /// Returns the body part's length
690    pub fn len(&self) -> usize {
691        match &self.body {
692            PartType::Text(text) | PartType::Html(text) => text.len(),
693            PartType::Binary(bin) | PartType::InlineBinary(bin) => bin.len(),
694            PartType::Message(message) => message.raw_message().len(),
695            PartType::Multipart(_) => 0,
696        }
697    }
698
699    /// Returns `true` when the body part MIME type is text/*
700    pub fn is_text(&self) -> bool {
701        matches!(self.body, PartType::Text(_) | PartType::Html(_))
702    }
703
704    /// Returns `true` when the body part MIME type is text/html
705    pub fn is_text_html(&self) -> bool {
706        matches!(self.body, PartType::Html(_))
707    }
708
709    /// Returns `true` when the part is binary
710    pub fn is_binary(&self) -> bool {
711        matches!(self.body, PartType::Binary(_) | PartType::InlineBinary(_))
712    }
713
714    /// Returns `true` when the part is multipart
715    pub fn is_multipart(&self) -> bool {
716        matches!(self.body, PartType::Multipart(_))
717    }
718
719    /// Returns `true` when the part is a nested message
720    pub fn is_message(&self) -> bool {
721        matches!(self.body, PartType::Message(_))
722    }
723
724    /// Returns `true` when the body part is empty
725    pub fn is_empty(&self) -> bool {
726        self.len() == 0
727    }
728
729    /// Get the message headers
730    pub fn headers(&self) -> &[Header<'x>] {
731        &self.headers
732    }
733
734    /// Returns the body raw length
735    pub fn raw_len(&self) -> u32 {
736        self.offset_end.saturating_sub(self.offset_header)
737    }
738
739    /// Get the raw header offset of this part
740    pub fn raw_header_offset(&self) -> u32 {
741        self.offset_header
742    }
743
744    /// Get the raw body offset of this part
745    pub fn raw_body_offset(&self) -> u32 {
746        self.offset_body
747    }
748
749    /// Get the raw body end offset of this part
750    pub fn raw_end_offset(&self) -> u32 {
751        self.offset_end
752    }
753
754    /// Returns an owned version of the this part
755    pub fn into_owned(self) -> MessagePart<'static> {
756        MessagePart {
757            headers: self.headers.into_iter().map(|h| h.into_owned()).collect(),
758            is_encoding_problem: self.is_encoding_problem,
759            body: match self.body {
760                PartType::Text(v) => PartType::Text(v.into_owned().into()),
761                PartType::Html(v) => PartType::Html(v.into_owned().into()),
762                PartType::Binary(v) => PartType::Binary(v.into_owned().into()),
763                PartType::InlineBinary(v) => PartType::InlineBinary(v.into_owned().into()),
764                PartType::Message(v) => PartType::Message(v.into_owned()),
765                PartType::Multipart(v) => PartType::Multipart(v),
766            },
767            encoding: self.encoding,
768            offset_header: self.offset_header,
769            offset_body: self.offset_body,
770            offset_end: self.offset_end,
771        }
772    }
773}
774
775impl fmt::Display for MessagePart<'_> {
776    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
777        fmt.write_str(self.text_contents().unwrap_or("[no contents]"))
778    }
779}
780
781impl<'x> MimeHeaders<'x> for MessagePart<'x> {
782    fn content_description(&self) -> Option<&str> {
783        self.headers
784            .header_value(&HeaderName::ContentDescription)
785            .and_then(|header| header.as_text())
786    }
787
788    fn content_disposition(&self) -> Option<&ContentType<'x>> {
789        self.headers
790            .header_value(&HeaderName::ContentDisposition)
791            .and_then(|header| header.as_content_type())
792    }
793
794    fn content_id(&self) -> Option<&str> {
795        self.headers
796            .header_value(&HeaderName::ContentId)
797            .and_then(|header| header.as_text())
798    }
799
800    fn content_transfer_encoding(&self) -> Option<&str> {
801        self.headers
802            .header_value(&HeaderName::ContentTransferEncoding)
803            .and_then(|header| header.as_text())
804    }
805
806    fn content_type(&self) -> Option<&ContentType<'x>> {
807        self.headers
808            .header_value(&HeaderName::ContentType)
809            .and_then(|header| header.as_content_type())
810    }
811
812    fn content_language(&self) -> &HeaderValue<'x> {
813        self.headers
814            .header_value(&HeaderName::ContentLanguage)
815            .unwrap_or(&HeaderValue::Empty)
816    }
817
818    fn content_location(&self) -> Option<&str> {
819        self.headers
820            .header_value(&HeaderName::ContentLocation)
821            .and_then(|header| header.as_text())
822    }
823}
824
825/// An RFC2047 Content-Type or RFC2183 Content-Disposition MIME header field.
826impl<'x> ContentType<'x> {
827    /// Returns the type
828    pub fn ctype(&self) -> &str {
829        &self.c_type
830    }
831
832    /// Returns the sub-type
833    pub fn subtype(&self) -> Option<&str> {
834        self.c_subtype.as_ref()?.as_ref().into()
835    }
836
837    /// Returns an attribute by name
838    pub fn attribute(&self, name: &str) -> Option<&str> {
839        self.attributes
840            .as_ref()?
841            .iter()
842            .find(|a| a.name == name)?
843            .value
844            .as_ref()
845            .into()
846    }
847
848    /// Removes an attribute by name
849    pub fn remove_attribute(&mut self, name: &str) -> Option<Cow<'x, str>> {
850        let attributes = self.attributes.as_mut()?;
851
852        attributes
853            .iter()
854            .position(|a| a.name == name)
855            .map(|pos| attributes.swap_remove(pos).value)
856    }
857
858    /// Returns all attributes
859    pub fn attributes(&self) -> Option<&[Attribute<'x>]> {
860        self.attributes.as_deref()
861    }
862
863    /// Returns `true` when the provided attribute name is present
864    pub fn has_attribute(&self, name: &str) -> bool {
865        self.attributes
866            .as_ref()
867            .is_some_and(|attr| attr.iter().any(|a| a.name == name))
868    }
869
870    /// Returns ```true``` if the Content-Disposition type is "attachment"
871    pub fn is_attachment(&self) -> bool {
872        self.c_type.eq_ignore_ascii_case("attachment")
873    }
874
875    /// Returns ```true``` if the Content-Disposition type is "inline"
876    pub fn is_inline(&self) -> bool {
877        self.c_type.eq_ignore_ascii_case("inline")
878    }
879}
880
881/// A Received header
882impl<'x> Received<'x> {
883    pub fn into_owned(self) -> Received<'static> {
884        Received {
885            from: self.from.map(|s| s.into_owned()),
886            from_ip: self.from_ip,
887            from_iprev: self.from_iprev.map(|s| s.into_owned().into()),
888            by: self.by.map(|s| s.into_owned()),
889            for_: self.for_.map(|s| s.into_owned().into()),
890            with: self.with,
891            tls_version: self.tls_version,
892            tls_cipher: self.tls_cipher.map(|s| s.into_owned().into()),
893            id: self.id.map(|s| s.into_owned().into()),
894            ident: self.ident.map(|s| s.into_owned().into()),
895            helo: self.helo.map(|s| s.into_owned()),
896            helo_cmd: self.helo_cmd,
897            via: self.via.map(|s| s.into_owned().into()),
898            date: self.date,
899        }
900    }
901
902    /// Returns the hostname or IP address of the machine that originated the message
903    pub fn from(&self) -> Option<&Host<'x>> {
904        self.from.as_ref()
905    }
906
907    /// Returns the IP address of the machine that originated the message
908    pub fn from_ip(&self) -> Option<IpAddr> {
909        self.from_ip
910    }
911
912    /// Returns the reverse DNS hostname of the machine that originated the message
913    pub fn from_iprev(&self) -> Option<&str> {
914        self.from_iprev.as_ref().map(|s| s.as_ref())
915    }
916
917    /// Returns the hostname or IP address of the machine that received the message
918    pub fn by(&self) -> Option<&Host<'x>> {
919        self.by.as_ref()
920    }
921
922    /// Returns the email address of the user that the message was received for
923    pub fn for_(&self) -> Option<&str> {
924        self.for_.as_ref().map(|s| s.as_ref())
925    }
926
927    /// Returns the protocol that was used to receive the message
928    pub fn with(&self) -> Option<Protocol> {
929        self.with
930    }
931
932    /// Returns the TLS version that was used to receive the message
933    pub fn tls_version(&self) -> Option<TlsVersion> {
934        self.tls_version
935    }
936
937    /// Returns the TLS cipher that was used to receive the message
938    pub fn tls_cipher(&self) -> Option<&str> {
939        self.tls_cipher.as_ref().map(|s| s.as_ref())
940    }
941
942    /// Returns the message ID of the message that was received
943    pub fn id(&self) -> Option<&str> {
944        self.id.as_ref().map(|s| s.as_ref())
945    }
946
947    /// Returns the identity of the user that sent the message
948    pub fn ident(&self) -> Option<&str> {
949        self.ident.as_ref().map(|s| s.as_ref())
950    }
951
952    /// Returns the EHLO/LHLO/HELO hostname or IP address of the machine that sent the message
953    pub fn helo(&self) -> Option<&Host<'x>> {
954        self.helo.as_ref()
955    }
956
957    /// Returns the EHLO/LHLO/HELO command that was sent by the client
958    pub fn helo_cmd(&self) -> Option<Greeting> {
959        self.helo_cmd
960    }
961
962    /// Returns the link type over which the message was received
963    pub fn via(&self) -> Option<&str> {
964        self.via.as_ref().map(|s| s.as_ref())
965    }
966
967    /// Returns the date and time when the message was received
968    pub fn date(&self) -> Option<DateTime> {
969        self.date
970    }
971}
972
973/// A hostname or IP address.
974impl Host<'_> {
975    pub fn into_owned(self) -> Host<'static> {
976        match self {
977            Host::Name(name) => Host::Name(name.into_owned().into()),
978            Host::IpAddr(ip) => Host::IpAddr(ip),
979        }
980    }
981}
982
983impl<'x> GetHeader<'x> for Vec<Header<'x>> {
984    fn header_value(&self, name: &HeaderName<'_>) -> Option<&HeaderValue<'x>> {
985        self.iter()
986            .rev()
987            .find(|header| &header.name == name)
988            .map(|header| &header.value)
989    }
990
991    fn header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header<'x>> {
992        let name = name.into();
993        self.iter().rev().find(|header| header.name == name)
994    }
995}
996
997impl<'x> From<&'x str> for HeaderName<'x> {
998    fn from(value: &'x str) -> Self {
999        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1000    }
1001}
1002
1003impl<'x> From<Cow<'x, str>> for HeaderName<'x> {
1004    fn from(value: Cow<'x, str>) -> Self {
1005        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1006    }
1007}
1008
1009impl From<String> for HeaderName<'_> {
1010    fn from(value: String) -> Self {
1011        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1012    }
1013}
1014
1015impl From<HeaderName<'_>> for String {
1016    fn from(header: HeaderName<'_>) -> Self {
1017        header.to_string()
1018    }
1019}
1020
1021impl<'x> From<HeaderName<'x>> for Cow<'x, str> {
1022    fn from(header: HeaderName<'x>) -> Self {
1023        match header {
1024            HeaderName::Other(value) => value,
1025            _ => Cow::Borrowed(header.as_static_str()),
1026        }
1027    }
1028}
1029
1030impl From<u8> for HeaderName<'_> {
1031    fn from(value: u8) -> Self {
1032        match value {
1033            0 => HeaderName::Subject,
1034            1 => HeaderName::From,
1035            2 => HeaderName::To,
1036            3 => HeaderName::Cc,
1037            4 => HeaderName::Date,
1038            5 => HeaderName::Bcc,
1039            6 => HeaderName::ReplyTo,
1040            7 => HeaderName::Sender,
1041            8 => HeaderName::Comments,
1042            9 => HeaderName::InReplyTo,
1043            10 => HeaderName::Keywords,
1044            11 => HeaderName::Received,
1045            12 => HeaderName::MessageId,
1046            13 => HeaderName::References,
1047            14 => HeaderName::ReturnPath,
1048            15 => HeaderName::MimeVersion,
1049            16 => HeaderName::ContentDescription,
1050            17 => HeaderName::ContentId,
1051            18 => HeaderName::ContentLanguage,
1052            19 => HeaderName::ContentLocation,
1053            20 => HeaderName::ContentTransferEncoding,
1054            21 => HeaderName::ContentType,
1055            22 => HeaderName::ContentDisposition,
1056            23 => HeaderName::ResentTo,
1057            24 => HeaderName::ResentFrom,
1058            25 => HeaderName::ResentBcc,
1059            26 => HeaderName::ResentCc,
1060            27 => HeaderName::ResentSender,
1061            28 => HeaderName::ResentDate,
1062            29 => HeaderName::ResentMessageId,
1063            30 => HeaderName::ListArchive,
1064            31 => HeaderName::ListHelp,
1065            32 => HeaderName::ListId,
1066            33 => HeaderName::ListOwner,
1067            34 => HeaderName::ListPost,
1068            35 => HeaderName::ListSubscribe,
1069            36 => HeaderName::ListUnsubscribe,
1070            38 => HeaderName::ArcAuthenticationResults,
1071            39 => HeaderName::ArcMessageSignature,
1072            40 => HeaderName::ArcSeal,
1073            41 => HeaderName::DkimSignature,
1074            _ => HeaderName::Other("".into()),
1075        }
1076    }
1077}
1078
1079impl From<DateTime> for i64 {
1080    fn from(value: DateTime) -> Self {
1081        value.to_timestamp()
1082    }
1083}
1084
1085impl TlsVersion {
1086    pub fn as_str(&self) -> &'static str {
1087        match self {
1088            TlsVersion::SSLv2 => "SSLv2",
1089            TlsVersion::SSLv3 => "SSLv3",
1090            TlsVersion::TLSv1_0 => "TLSv1.0",
1091            TlsVersion::TLSv1_1 => "TLSv1.1",
1092            TlsVersion::TLSv1_2 => "TLSv1.2",
1093            TlsVersion::TLSv1_3 => "TLSv1.3",
1094            TlsVersion::DTLSv1_0 => "DTLSv1.0",
1095            TlsVersion::DTLSv1_2 => "DTLSv1.2",
1096            TlsVersion::DTLSv1_3 => "DTLSv1.3",
1097        }
1098    }
1099}
1100
1101impl Greeting {
1102    pub fn as_str(&self) -> &'static str {
1103        match self {
1104            Greeting::Helo => "HELO",
1105            Greeting::Ehlo => "EHLO",
1106            Greeting::Lhlo => "LHLO",
1107        }
1108    }
1109}
1110
1111impl Protocol {
1112    pub fn as_str(&self) -> &'static str {
1113        match self {
1114            Protocol::SMTP => "SMTP",
1115            Protocol::LMTP => "LMTP",
1116            Protocol::ESMTP => "ESMTP",
1117            Protocol::ESMTPS => "ESMTPS",
1118            Protocol::ESMTPA => "ESMTPA",
1119            Protocol::ESMTPSA => "ESMTPSA",
1120            Protocol::LMTPA => "LMTPA",
1121            Protocol::LMTPS => "LMTPS",
1122            Protocol::LMTPSA => "LMTPSA",
1123            Protocol::UTF8SMTP => "UTF8SMTP",
1124            Protocol::UTF8SMTPA => "UTF8SMTPA",
1125            Protocol::UTF8SMTPS => "UTF8SMTPS",
1126            Protocol::UTF8SMTPSA => "UTF8SMTPSA",
1127            Protocol::UTF8LMTP => "UTF8LMTP",
1128            Protocol::UTF8LMTPA => "UTF8LMTPA",
1129            Protocol::UTF8LMTPS => "UTF8LMTPS",
1130            Protocol::UTF8LMTPSA => "UTF8LMTPSA",
1131            Protocol::HTTP => "HTTP",
1132            Protocol::HTTPS => "HTTPS",
1133            Protocol::IMAP => "IMAP",
1134            Protocol::POP3 => "POP3",
1135            Protocol::MMS => "MMS",
1136            Protocol::Local => "Local",
1137        }
1138    }
1139}
1140
1141impl Display for Host<'_> {
1142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143        match self {
1144            Host::Name(name) => name.fmt(f),
1145            Host::IpAddr(ip) => ip.fmt(f),
1146        }
1147    }
1148}
1149
1150impl Display for Protocol {
1151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1152        f.write_str(self.as_str())
1153    }
1154}
1155
1156impl Display for Greeting {
1157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158        f.write_str(self.as_str())
1159    }
1160}
1161
1162impl Display for TlsVersion {
1163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1164        f.write_str(self.as_str())
1165    }
1166}
1167
1168impl Display for HeaderName<'_> {
1169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1170        write!(f, "{}", self.as_str())
1171    }
1172}