Skip to main content

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            (Self::Dkim2Signature, Self::Dkim2Signature) => true,
298            (Self::MessageInstance, Self::MessageInstance) => true,
299            _ => false,
300        }
301    }
302}
303
304impl Hash for HeaderName<'_> {
305    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
306        match self {
307            HeaderName::Other(value) => {
308                for ch in value.as_bytes() {
309                    ch.to_ascii_lowercase().hash(state)
310                }
311            }
312            _ => self.id().hash(state),
313        }
314    }
315}
316
317impl Eq for HeaderName<'_> {}
318
319impl<'x> From<HeaderName<'x>> for u8 {
320    fn from(name: HeaderName<'x>) -> Self {
321        name.id()
322    }
323}
324
325impl HeaderName<'_> {
326    pub fn to_owned(&self) -> HeaderName<'static> {
327        match self {
328            HeaderName::Other(name) => HeaderName::Other(name.to_string().into()),
329            HeaderName::Subject => HeaderName::Subject,
330            HeaderName::From => HeaderName::From,
331            HeaderName::To => HeaderName::To,
332            HeaderName::Cc => HeaderName::Cc,
333            HeaderName::Date => HeaderName::Date,
334            HeaderName::Bcc => HeaderName::Bcc,
335            HeaderName::ReplyTo => HeaderName::ReplyTo,
336            HeaderName::Sender => HeaderName::Sender,
337            HeaderName::Comments => HeaderName::Comments,
338            HeaderName::InReplyTo => HeaderName::InReplyTo,
339            HeaderName::Keywords => HeaderName::Keywords,
340            HeaderName::Received => HeaderName::Received,
341            HeaderName::MessageId => HeaderName::MessageId,
342            HeaderName::References => HeaderName::References,
343            HeaderName::ReturnPath => HeaderName::ReturnPath,
344            HeaderName::MimeVersion => HeaderName::MimeVersion,
345            HeaderName::ContentDescription => HeaderName::ContentDescription,
346            HeaderName::ContentId => HeaderName::ContentId,
347            HeaderName::ContentLanguage => HeaderName::ContentLanguage,
348            HeaderName::ContentLocation => HeaderName::ContentLocation,
349            HeaderName::ContentTransferEncoding => HeaderName::ContentTransferEncoding,
350            HeaderName::ContentType => HeaderName::ContentType,
351            HeaderName::ContentDisposition => HeaderName::ContentDisposition,
352            HeaderName::ResentTo => HeaderName::ResentTo,
353            HeaderName::ResentFrom => HeaderName::ResentFrom,
354            HeaderName::ResentBcc => HeaderName::ResentBcc,
355            HeaderName::ResentCc => HeaderName::ResentCc,
356            HeaderName::ResentSender => HeaderName::ResentSender,
357            HeaderName::ResentDate => HeaderName::ResentDate,
358            HeaderName::ResentMessageId => HeaderName::ResentMessageId,
359            HeaderName::ListArchive => HeaderName::ListArchive,
360            HeaderName::ListHelp => HeaderName::ListHelp,
361            HeaderName::ListId => HeaderName::ListId,
362            HeaderName::ListOwner => HeaderName::ListOwner,
363            HeaderName::ListPost => HeaderName::ListPost,
364            HeaderName::ListSubscribe => HeaderName::ListSubscribe,
365            HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
366            HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
367            HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
368            HeaderName::ArcSeal => HeaderName::ArcSeal,
369            HeaderName::DkimSignature => HeaderName::DkimSignature,
370            HeaderName::Dkim2Signature => HeaderName::Dkim2Signature,
371            HeaderName::MessageInstance => HeaderName::MessageInstance,
372        }
373    }
374
375    pub fn into_owned(self) -> HeaderName<'static> {
376        match self {
377            HeaderName::Other(name) => HeaderName::Other(name.into_owned().into()),
378            HeaderName::Subject => HeaderName::Subject,
379            HeaderName::From => HeaderName::From,
380            HeaderName::To => HeaderName::To,
381            HeaderName::Cc => HeaderName::Cc,
382            HeaderName::Date => HeaderName::Date,
383            HeaderName::Bcc => HeaderName::Bcc,
384            HeaderName::ReplyTo => HeaderName::ReplyTo,
385            HeaderName::Sender => HeaderName::Sender,
386            HeaderName::Comments => HeaderName::Comments,
387            HeaderName::InReplyTo => HeaderName::InReplyTo,
388            HeaderName::Keywords => HeaderName::Keywords,
389            HeaderName::Received => HeaderName::Received,
390            HeaderName::MessageId => HeaderName::MessageId,
391            HeaderName::References => HeaderName::References,
392            HeaderName::ReturnPath => HeaderName::ReturnPath,
393            HeaderName::MimeVersion => HeaderName::MimeVersion,
394            HeaderName::ContentDescription => HeaderName::ContentDescription,
395            HeaderName::ContentId => HeaderName::ContentId,
396            HeaderName::ContentLanguage => HeaderName::ContentLanguage,
397            HeaderName::ContentLocation => HeaderName::ContentLocation,
398            HeaderName::ContentTransferEncoding => HeaderName::ContentTransferEncoding,
399            HeaderName::ContentType => HeaderName::ContentType,
400            HeaderName::ContentDisposition => HeaderName::ContentDisposition,
401            HeaderName::ResentTo => HeaderName::ResentTo,
402            HeaderName::ResentFrom => HeaderName::ResentFrom,
403            HeaderName::ResentBcc => HeaderName::ResentBcc,
404            HeaderName::ResentCc => HeaderName::ResentCc,
405            HeaderName::ResentSender => HeaderName::ResentSender,
406            HeaderName::ResentDate => HeaderName::ResentDate,
407            HeaderName::ResentMessageId => HeaderName::ResentMessageId,
408            HeaderName::ListArchive => HeaderName::ListArchive,
409            HeaderName::ListHelp => HeaderName::ListHelp,
410            HeaderName::ListId => HeaderName::ListId,
411            HeaderName::ListOwner => HeaderName::ListOwner,
412            HeaderName::ListPost => HeaderName::ListPost,
413            HeaderName::ListSubscribe => HeaderName::ListSubscribe,
414            HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
415            HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
416            HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
417            HeaderName::ArcSeal => HeaderName::ArcSeal,
418            HeaderName::DkimSignature => HeaderName::DkimSignature,
419            HeaderName::Dkim2Signature => HeaderName::Dkim2Signature,
420            HeaderName::MessageInstance => HeaderName::MessageInstance,
421        }
422    }
423
424    pub fn into_string(self) -> String {
425        match self {
426            HeaderName::Other(name) => name.into_owned(),
427            _ => self.as_str().to_string(),
428        }
429    }
430
431    pub fn as_str(&self) -> &str {
432        match self {
433            HeaderName::Other(other) => other.as_ref(),
434            _ => self.as_static_str(),
435        }
436    }
437
438    pub fn as_static_str(&self) -> &'static str {
439        match self {
440            HeaderName::Subject => "Subject",
441            HeaderName::From => "From",
442            HeaderName::To => "To",
443            HeaderName::Cc => "Cc",
444            HeaderName::Date => "Date",
445            HeaderName::Bcc => "Bcc",
446            HeaderName::ReplyTo => "Reply-To",
447            HeaderName::Sender => "Sender",
448            HeaderName::Comments => "Comments",
449            HeaderName::InReplyTo => "In-Reply-To",
450            HeaderName::Keywords => "Keywords",
451            HeaderName::Received => "Received",
452            HeaderName::MessageId => "Message-ID",
453            HeaderName::References => "References",
454            HeaderName::ReturnPath => "Return-Path",
455            HeaderName::MimeVersion => "MIME-Version",
456            HeaderName::ContentDescription => "Content-Description",
457            HeaderName::ContentId => "Content-ID",
458            HeaderName::ContentLanguage => "Content-Language",
459            HeaderName::ContentLocation => "Content-Location",
460            HeaderName::ContentTransferEncoding => "Content-Transfer-Encoding",
461            HeaderName::ContentType => "Content-Type",
462            HeaderName::ContentDisposition => "Content-Disposition",
463            HeaderName::ResentTo => "Resent-To",
464            HeaderName::ResentFrom => "Resent-From",
465            HeaderName::ResentBcc => "Resent-Bcc",
466            HeaderName::ResentCc => "Resent-Cc",
467            HeaderName::ResentSender => "Resent-Sender",
468            HeaderName::ResentDate => "Resent-Date",
469            HeaderName::ResentMessageId => "Resent-Message-ID",
470            HeaderName::ListArchive => "List-Archive",
471            HeaderName::ListHelp => "List-Help",
472            HeaderName::ListId => "List-ID",
473            HeaderName::ListOwner => "List-Owner",
474            HeaderName::ListPost => "List-Post",
475            HeaderName::ListSubscribe => "List-Subscribe",
476            HeaderName::ListUnsubscribe => "List-Unsubscribe",
477            HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results",
478            HeaderName::ArcMessageSignature => "ARC-Message-Signature",
479            HeaderName::ArcSeal => "ARC-Seal",
480            HeaderName::DkimSignature => "DKIM-Signature",
481            HeaderName::Dkim2Signature => "DKIM2-Signature",
482            HeaderName::MessageInstance => "Message-Instance",
483            HeaderName::Other(_) => "",
484        }
485    }
486
487    pub fn len(&self) -> usize {
488        match self {
489            HeaderName::Subject => "Subject".len(),
490            HeaderName::From => "From".len(),
491            HeaderName::To => "To".len(),
492            HeaderName::Cc => "Cc".len(),
493            HeaderName::Date => "Date".len(),
494            HeaderName::Bcc => "Bcc".len(),
495            HeaderName::ReplyTo => "Reply-To".len(),
496            HeaderName::Sender => "Sender".len(),
497            HeaderName::Comments => "Comments".len(),
498            HeaderName::InReplyTo => "In-Reply-To".len(),
499            HeaderName::Keywords => "Keywords".len(),
500            HeaderName::Received => "Received".len(),
501            HeaderName::MessageId => "Message-ID".len(),
502            HeaderName::References => "References".len(),
503            HeaderName::ReturnPath => "Return-Path".len(),
504            HeaderName::MimeVersion => "MIME-Version".len(),
505            HeaderName::ContentDescription => "Content-Description".len(),
506            HeaderName::ContentId => "Content-ID".len(),
507            HeaderName::ContentLanguage => "Content-Language".len(),
508            HeaderName::ContentLocation => "Content-Location".len(),
509            HeaderName::ContentTransferEncoding => "Content-Transfer-Encoding".len(),
510            HeaderName::ContentType => "Content-Type".len(),
511            HeaderName::ContentDisposition => "Content-Disposition".len(),
512            HeaderName::ResentTo => "Resent-To".len(),
513            HeaderName::ResentFrom => "Resent-From".len(),
514            HeaderName::ResentBcc => "Resent-Bcc".len(),
515            HeaderName::ResentCc => "Resent-Cc".len(),
516            HeaderName::ResentSender => "Resent-Sender".len(),
517            HeaderName::ResentDate => "Resent-Date".len(),
518            HeaderName::ResentMessageId => "Resent-Message-ID".len(),
519            HeaderName::ListArchive => "List-Archive".len(),
520            HeaderName::ListHelp => "List-Help".len(),
521            HeaderName::ListId => "List-ID".len(),
522            HeaderName::ListOwner => "List-Owner".len(),
523            HeaderName::ListPost => "List-Post".len(),
524            HeaderName::ListSubscribe => "List-Subscribe".len(),
525            HeaderName::ListUnsubscribe => "List-Unsubscribe".len(),
526            HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results".len(),
527            HeaderName::ArcMessageSignature => "ARC-Message-Signature".len(),
528            HeaderName::ArcSeal => "ARC-Seal".len(),
529            HeaderName::DkimSignature => "DKIM-Signature".len(),
530            HeaderName::Dkim2Signature => "DKIM2-Signature".len(),
531            HeaderName::MessageInstance => "Message-Instance".len(),
532            HeaderName::Other(other) => other.len(),
533        }
534    }
535
536    /// Returns true if it is a MIME header.
537    pub fn is_mime_header(&self) -> bool {
538        matches!(
539            self,
540            HeaderName::ContentDescription
541                | HeaderName::ContentId
542                | HeaderName::ContentLanguage
543                | HeaderName::ContentLocation
544                | HeaderName::ContentTransferEncoding
545                | HeaderName::ContentType
546                | HeaderName::ContentDisposition
547        )
548    }
549
550    /// Returns true if it is an `Other` header name
551    pub fn is_other(&self) -> bool {
552        matches!(self, HeaderName::Other(_))
553    }
554
555    pub fn is_empty(&self) -> bool {
556        false
557    }
558
559    pub fn id(&self) -> u8 {
560        match self {
561            HeaderName::Subject => 0,
562            HeaderName::From => 1,
563            HeaderName::To => 2,
564            HeaderName::Cc => 3,
565            HeaderName::Date => 4,
566            HeaderName::Bcc => 5,
567            HeaderName::ReplyTo => 6,
568            HeaderName::Sender => 7,
569            HeaderName::Comments => 8,
570            HeaderName::InReplyTo => 9,
571            HeaderName::Keywords => 10,
572            HeaderName::Received => 11,
573            HeaderName::MessageId => 12,
574            HeaderName::References => 13,
575            HeaderName::ReturnPath => 14,
576            HeaderName::MimeVersion => 15,
577            HeaderName::ContentDescription => 16,
578            HeaderName::ContentId => 17,
579            HeaderName::ContentLanguage => 18,
580            HeaderName::ContentLocation => 19,
581            HeaderName::ContentTransferEncoding => 20,
582            HeaderName::ContentType => 21,
583            HeaderName::ContentDisposition => 22,
584            HeaderName::ResentTo => 23,
585            HeaderName::ResentFrom => 24,
586            HeaderName::ResentBcc => 25,
587            HeaderName::ResentCc => 26,
588            HeaderName::ResentSender => 27,
589            HeaderName::ResentDate => 28,
590            HeaderName::ResentMessageId => 29,
591            HeaderName::ListArchive => 30,
592            HeaderName::ListHelp => 31,
593            HeaderName::ListId => 32,
594            HeaderName::ListOwner => 33,
595            HeaderName::ListPost => 34,
596            HeaderName::ListSubscribe => 35,
597            HeaderName::ListUnsubscribe => 36,
598            HeaderName::Other(_) => 37,
599            HeaderName::ArcAuthenticationResults => 38,
600            HeaderName::ArcMessageSignature => 39,
601            HeaderName::ArcSeal => 40,
602            HeaderName::DkimSignature => 41,
603            HeaderName::Dkim2Signature => 42,
604            HeaderName::MessageInstance => 43,
605        }
606    }
607}
608
609impl<'x> MimeHeaders<'x> for Message<'x> {
610    fn content_description(&self) -> Option<&str> {
611        self.parts[0]
612            .headers
613            .header_value(&HeaderName::ContentDescription)
614            .and_then(|header| header.as_text())
615    }
616
617    fn content_disposition(&self) -> Option<&ContentType<'x>> {
618        self.parts[0]
619            .headers
620            .header_value(&HeaderName::ContentDisposition)
621            .and_then(|header| header.as_content_type())
622    }
623
624    fn content_id(&self) -> Option<&str> {
625        self.parts[0]
626            .headers
627            .header_value(&HeaderName::ContentId)
628            .and_then(|header| header.as_text())
629    }
630
631    fn content_transfer_encoding(&self) -> Option<&str> {
632        self.parts[0]
633            .headers
634            .header_value(&HeaderName::ContentTransferEncoding)
635            .and_then(|header| header.as_text())
636    }
637
638    fn content_type(&self) -> Option<&ContentType<'x>> {
639        self.parts[0]
640            .headers
641            .header_value(&HeaderName::ContentType)
642            .and_then(|header| header.as_content_type())
643    }
644
645    fn content_language(&self) -> &HeaderValue<'x> {
646        self.parts[0]
647            .headers
648            .header_value(&HeaderName::ContentLanguage)
649            .unwrap_or(&HeaderValue::Empty)
650    }
651
652    fn content_location(&self) -> Option<&str> {
653        self.parts[0]
654            .headers
655            .header_value(&HeaderName::ContentLocation)
656            .and_then(|header| header.as_text())
657    }
658}
659
660impl<'x> MessagePart<'x> {
661    /// Returns the body part's contents as a `u8` slice
662    pub fn contents(&self) -> &[u8] {
663        match &self.body {
664            PartType::Text(text) | PartType::Html(text) => text.as_bytes(),
665            PartType::Binary(bin) | PartType::InlineBinary(bin) => bin.as_ref(),
666            PartType::Message(message) => message.raw_message(),
667            PartType::Multipart(_) => b"",
668        }
669    }
670
671    /// Returns the body part's contents as a `str`
672    pub fn text_contents(&self) -> Option<&str> {
673        match &self.body {
674            PartType::Text(text) | PartType::Html(text) => text.as_ref().into(),
675            PartType::Binary(bin) | PartType::InlineBinary(bin) => {
676                std::str::from_utf8(bin.as_ref()).ok()
677            }
678            PartType::Message(message) => std::str::from_utf8(message.raw_message()).ok(),
679            PartType::Multipart(_) => None,
680        }
681    }
682
683    /// Returns the nested message
684    pub fn message(&self) -> Option<&Message<'x>> {
685        if let PartType::Message(message) = &self.body {
686            Some(message)
687        } else {
688            None
689        }
690    }
691
692    /// Returns the sub parts ids of a MIME part
693    pub fn sub_parts(&self) -> Option<&[MessagePartId]> {
694        if let PartType::Multipart(parts) = &self.body {
695            Some(parts.as_ref())
696        } else {
697            None
698        }
699    }
700
701    /// Returns the body part's length
702    pub fn len(&self) -> usize {
703        match &self.body {
704            PartType::Text(text) | PartType::Html(text) => text.len(),
705            PartType::Binary(bin) | PartType::InlineBinary(bin) => bin.len(),
706            PartType::Message(message) => message.raw_message().len(),
707            PartType::Multipart(_) => 0,
708        }
709    }
710
711    /// Returns `true` when the body part MIME type is text/*
712    pub fn is_text(&self) -> bool {
713        matches!(self.body, PartType::Text(_) | PartType::Html(_))
714    }
715
716    /// Returns `true` when the body part MIME type is text/html
717    pub fn is_text_html(&self) -> bool {
718        matches!(self.body, PartType::Html(_))
719    }
720
721    /// Returns `true` when the part is binary
722    pub fn is_binary(&self) -> bool {
723        matches!(self.body, PartType::Binary(_) | PartType::InlineBinary(_))
724    }
725
726    /// Returns `true` when the part is multipart
727    pub fn is_multipart(&self) -> bool {
728        matches!(self.body, PartType::Multipart(_))
729    }
730
731    /// Returns `true` when the part is a nested message
732    pub fn is_message(&self) -> bool {
733        matches!(self.body, PartType::Message(_))
734    }
735
736    /// Returns `true` when the body part is empty
737    pub fn is_empty(&self) -> bool {
738        self.len() == 0
739    }
740
741    /// Get the message headers
742    pub fn headers(&self) -> &[Header<'x>] {
743        &self.headers
744    }
745
746    /// Returns the body raw length
747    pub fn raw_len(&self) -> u32 {
748        self.offset_end.saturating_sub(self.offset_header)
749    }
750
751    /// Get the raw header offset of this part
752    pub fn raw_header_offset(&self) -> u32 {
753        self.offset_header
754    }
755
756    /// Get the raw body offset of this part
757    pub fn raw_body_offset(&self) -> u32 {
758        self.offset_body
759    }
760
761    /// Get the raw body end offset of this part
762    pub fn raw_end_offset(&self) -> u32 {
763        self.offset_end
764    }
765
766    /// Returns an owned version of the this part
767    pub fn into_owned(self) -> MessagePart<'static> {
768        MessagePart {
769            headers: self.headers.into_iter().map(|h| h.into_owned()).collect(),
770            is_encoding_problem: self.is_encoding_problem,
771            body: match self.body {
772                PartType::Text(v) => PartType::Text(v.into_owned().into()),
773                PartType::Html(v) => PartType::Html(v.into_owned().into()),
774                PartType::Binary(v) => PartType::Binary(v.into_owned().into()),
775                PartType::InlineBinary(v) => PartType::InlineBinary(v.into_owned().into()),
776                PartType::Message(v) => PartType::Message(v.into_owned()),
777                PartType::Multipart(v) => PartType::Multipart(v),
778            },
779            encoding: self.encoding,
780            offset_header: self.offset_header,
781            offset_body: self.offset_body,
782            offset_end: self.offset_end,
783        }
784    }
785}
786
787impl fmt::Display for MessagePart<'_> {
788    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
789        fmt.write_str(self.text_contents().unwrap_or("[no contents]"))
790    }
791}
792
793impl<'x> MimeHeaders<'x> for MessagePart<'x> {
794    fn content_description(&self) -> Option<&str> {
795        self.headers
796            .header_value(&HeaderName::ContentDescription)
797            .and_then(|header| header.as_text())
798    }
799
800    fn content_disposition(&self) -> Option<&ContentType<'x>> {
801        self.headers
802            .header_value(&HeaderName::ContentDisposition)
803            .and_then(|header| header.as_content_type())
804    }
805
806    fn content_id(&self) -> Option<&str> {
807        self.headers
808            .header_value(&HeaderName::ContentId)
809            .and_then(|header| header.as_text())
810    }
811
812    fn content_transfer_encoding(&self) -> Option<&str> {
813        self.headers
814            .header_value(&HeaderName::ContentTransferEncoding)
815            .and_then(|header| header.as_text())
816    }
817
818    fn content_type(&self) -> Option<&ContentType<'x>> {
819        self.headers
820            .header_value(&HeaderName::ContentType)
821            .and_then(|header| header.as_content_type())
822    }
823
824    fn content_language(&self) -> &HeaderValue<'x> {
825        self.headers
826            .header_value(&HeaderName::ContentLanguage)
827            .unwrap_or(&HeaderValue::Empty)
828    }
829
830    fn content_location(&self) -> Option<&str> {
831        self.headers
832            .header_value(&HeaderName::ContentLocation)
833            .and_then(|header| header.as_text())
834    }
835}
836
837/// An RFC2047 Content-Type or RFC2183 Content-Disposition MIME header field.
838impl<'x> ContentType<'x> {
839    /// Returns the type
840    pub fn ctype(&self) -> &str {
841        &self.c_type
842    }
843
844    /// Returns the sub-type
845    pub fn subtype(&self) -> Option<&str> {
846        self.c_subtype.as_ref()?.as_ref().into()
847    }
848
849    /// Returns an attribute by name
850    pub fn attribute(&self, name: &str) -> Option<&str> {
851        self.attributes
852            .as_ref()?
853            .iter()
854            .find(|a| a.name == name)?
855            .value
856            .as_ref()
857            .into()
858    }
859
860    /// Removes an attribute by name
861    pub fn remove_attribute(&mut self, name: &str) -> Option<Cow<'x, str>> {
862        let attributes = self.attributes.as_mut()?;
863
864        attributes
865            .iter()
866            .position(|a| a.name == name)
867            .map(|pos| attributes.swap_remove(pos).value)
868    }
869
870    /// Returns all attributes
871    pub fn attributes(&self) -> Option<&[Attribute<'x>]> {
872        self.attributes.as_deref()
873    }
874
875    /// Returns `true` when the provided attribute name is present
876    pub fn has_attribute(&self, name: &str) -> bool {
877        self.attributes
878            .as_ref()
879            .is_some_and(|attr| attr.iter().any(|a| a.name == name))
880    }
881
882    /// Returns ```true``` if the Content-Disposition type is "attachment"
883    pub fn is_attachment(&self) -> bool {
884        self.c_type.eq_ignore_ascii_case("attachment")
885    }
886
887    /// Returns ```true``` if the Content-Disposition type is "inline"
888    pub fn is_inline(&self) -> bool {
889        self.c_type.eq_ignore_ascii_case("inline")
890    }
891}
892
893/// A Received header
894impl<'x> Received<'x> {
895    pub fn into_owned(self) -> Received<'static> {
896        Received {
897            from: self.from.map(|s| s.into_owned()),
898            from_ip: self.from_ip,
899            from_iprev: self.from_iprev.map(|s| s.into_owned().into()),
900            by: self.by.map(|s| s.into_owned()),
901            for_: self.for_.map(|s| s.into_owned().into()),
902            with: self.with,
903            tls_version: self.tls_version,
904            tls_cipher: self.tls_cipher.map(|s| s.into_owned().into()),
905            id: self.id.map(|s| s.into_owned().into()),
906            ident: self.ident.map(|s| s.into_owned().into()),
907            helo: self.helo.map(|s| s.into_owned()),
908            helo_cmd: self.helo_cmd,
909            via: self.via.map(|s| s.into_owned().into()),
910            date: self.date,
911        }
912    }
913
914    /// Returns the hostname or IP address of the machine that originated the message
915    pub fn from(&self) -> Option<&Host<'x>> {
916        self.from.as_ref()
917    }
918
919    /// Returns the IP address of the machine that originated the message
920    pub fn from_ip(&self) -> Option<IpAddr> {
921        self.from_ip
922    }
923
924    /// Returns the reverse DNS hostname of the machine that originated the message
925    pub fn from_iprev(&self) -> Option<&str> {
926        self.from_iprev.as_ref().map(|s| s.as_ref())
927    }
928
929    /// Returns the hostname or IP address of the machine that received the message
930    pub fn by(&self) -> Option<&Host<'x>> {
931        self.by.as_ref()
932    }
933
934    /// Returns the email address of the user that the message was received for
935    pub fn for_(&self) -> Option<&str> {
936        self.for_.as_ref().map(|s| s.as_ref())
937    }
938
939    /// Returns the protocol that was used to receive the message
940    pub fn with(&self) -> Option<Protocol> {
941        self.with
942    }
943
944    /// Returns the TLS version that was used to receive the message
945    pub fn tls_version(&self) -> Option<TlsVersion> {
946        self.tls_version
947    }
948
949    /// Returns the TLS cipher that was used to receive the message
950    pub fn tls_cipher(&self) -> Option<&str> {
951        self.tls_cipher.as_ref().map(|s| s.as_ref())
952    }
953
954    /// Returns the message ID of the message that was received
955    pub fn id(&self) -> Option<&str> {
956        self.id.as_ref().map(|s| s.as_ref())
957    }
958
959    /// Returns the identity of the user that sent the message
960    pub fn ident(&self) -> Option<&str> {
961        self.ident.as_ref().map(|s| s.as_ref())
962    }
963
964    /// Returns the EHLO/LHLO/HELO hostname or IP address of the machine that sent the message
965    pub fn helo(&self) -> Option<&Host<'x>> {
966        self.helo.as_ref()
967    }
968
969    /// Returns the EHLO/LHLO/HELO command that was sent by the client
970    pub fn helo_cmd(&self) -> Option<Greeting> {
971        self.helo_cmd
972    }
973
974    /// Returns the link type over which the message was received
975    pub fn via(&self) -> Option<&str> {
976        self.via.as_ref().map(|s| s.as_ref())
977    }
978
979    /// Returns the date and time when the message was received
980    pub fn date(&self) -> Option<DateTime> {
981        self.date
982    }
983}
984
985/// A hostname or IP address.
986impl Host<'_> {
987    pub fn into_owned(self) -> Host<'static> {
988        match self {
989            Host::Name(name) => Host::Name(name.into_owned().into()),
990            Host::IpAddr(ip) => Host::IpAddr(ip),
991        }
992    }
993}
994
995impl<'x> GetHeader<'x> for Vec<Header<'x>> {
996    fn header_value(&self, name: &HeaderName<'_>) -> Option<&HeaderValue<'x>> {
997        self.iter()
998            .rev()
999            .find(|header| &header.name == name)
1000            .map(|header| &header.value)
1001    }
1002
1003    fn header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header<'x>> {
1004        let name = name.into();
1005        self.iter().rev().find(|header| header.name == name)
1006    }
1007}
1008
1009impl<'x> From<&'x str> for HeaderName<'x> {
1010    fn from(value: &'x str) -> Self {
1011        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1012    }
1013}
1014
1015impl<'x> From<Cow<'x, str>> for HeaderName<'x> {
1016    fn from(value: Cow<'x, str>) -> Self {
1017        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1018    }
1019}
1020
1021impl From<String> for HeaderName<'_> {
1022    fn from(value: String) -> Self {
1023        HeaderName::parse(value).unwrap_or(HeaderName::Other("".into()))
1024    }
1025}
1026
1027impl From<HeaderName<'_>> for String {
1028    fn from(header: HeaderName<'_>) -> Self {
1029        header.to_string()
1030    }
1031}
1032
1033impl<'x> From<HeaderName<'x>> for Cow<'x, str> {
1034    fn from(header: HeaderName<'x>) -> Self {
1035        match header {
1036            HeaderName::Other(value) => value,
1037            _ => Cow::Borrowed(header.as_static_str()),
1038        }
1039    }
1040}
1041
1042impl From<u8> for HeaderName<'_> {
1043    fn from(value: u8) -> Self {
1044        match value {
1045            0 => HeaderName::Subject,
1046            1 => HeaderName::From,
1047            2 => HeaderName::To,
1048            3 => HeaderName::Cc,
1049            4 => HeaderName::Date,
1050            5 => HeaderName::Bcc,
1051            6 => HeaderName::ReplyTo,
1052            7 => HeaderName::Sender,
1053            8 => HeaderName::Comments,
1054            9 => HeaderName::InReplyTo,
1055            10 => HeaderName::Keywords,
1056            11 => HeaderName::Received,
1057            12 => HeaderName::MessageId,
1058            13 => HeaderName::References,
1059            14 => HeaderName::ReturnPath,
1060            15 => HeaderName::MimeVersion,
1061            16 => HeaderName::ContentDescription,
1062            17 => HeaderName::ContentId,
1063            18 => HeaderName::ContentLanguage,
1064            19 => HeaderName::ContentLocation,
1065            20 => HeaderName::ContentTransferEncoding,
1066            21 => HeaderName::ContentType,
1067            22 => HeaderName::ContentDisposition,
1068            23 => HeaderName::ResentTo,
1069            24 => HeaderName::ResentFrom,
1070            25 => HeaderName::ResentBcc,
1071            26 => HeaderName::ResentCc,
1072            27 => HeaderName::ResentSender,
1073            28 => HeaderName::ResentDate,
1074            29 => HeaderName::ResentMessageId,
1075            30 => HeaderName::ListArchive,
1076            31 => HeaderName::ListHelp,
1077            32 => HeaderName::ListId,
1078            33 => HeaderName::ListOwner,
1079            34 => HeaderName::ListPost,
1080            35 => HeaderName::ListSubscribe,
1081            36 => HeaderName::ListUnsubscribe,
1082            38 => HeaderName::ArcAuthenticationResults,
1083            39 => HeaderName::ArcMessageSignature,
1084            40 => HeaderName::ArcSeal,
1085            41 => HeaderName::DkimSignature,
1086            _ => HeaderName::Other("".into()),
1087        }
1088    }
1089}
1090
1091impl From<DateTime> for i64 {
1092    fn from(value: DateTime) -> Self {
1093        value.to_timestamp()
1094    }
1095}
1096
1097impl TlsVersion {
1098    pub fn as_str(&self) -> &'static str {
1099        match self {
1100            TlsVersion::SSLv2 => "SSLv2",
1101            TlsVersion::SSLv3 => "SSLv3",
1102            TlsVersion::TLSv1_0 => "TLSv1.0",
1103            TlsVersion::TLSv1_1 => "TLSv1.1",
1104            TlsVersion::TLSv1_2 => "TLSv1.2",
1105            TlsVersion::TLSv1_3 => "TLSv1.3",
1106            TlsVersion::DTLSv1_0 => "DTLSv1.0",
1107            TlsVersion::DTLSv1_2 => "DTLSv1.2",
1108            TlsVersion::DTLSv1_3 => "DTLSv1.3",
1109        }
1110    }
1111}
1112
1113impl Greeting {
1114    pub fn as_str(&self) -> &'static str {
1115        match self {
1116            Greeting::Helo => "HELO",
1117            Greeting::Ehlo => "EHLO",
1118            Greeting::Lhlo => "LHLO",
1119        }
1120    }
1121}
1122
1123impl Protocol {
1124    pub fn as_str(&self) -> &'static str {
1125        match self {
1126            Protocol::SMTP => "SMTP",
1127            Protocol::LMTP => "LMTP",
1128            Protocol::ESMTP => "ESMTP",
1129            Protocol::ESMTPS => "ESMTPS",
1130            Protocol::ESMTPA => "ESMTPA",
1131            Protocol::ESMTPSA => "ESMTPSA",
1132            Protocol::LMTPA => "LMTPA",
1133            Protocol::LMTPS => "LMTPS",
1134            Protocol::LMTPSA => "LMTPSA",
1135            Protocol::UTF8SMTP => "UTF8SMTP",
1136            Protocol::UTF8SMTPA => "UTF8SMTPA",
1137            Protocol::UTF8SMTPS => "UTF8SMTPS",
1138            Protocol::UTF8SMTPSA => "UTF8SMTPSA",
1139            Protocol::UTF8LMTP => "UTF8LMTP",
1140            Protocol::UTF8LMTPA => "UTF8LMTPA",
1141            Protocol::UTF8LMTPS => "UTF8LMTPS",
1142            Protocol::UTF8LMTPSA => "UTF8LMTPSA",
1143            Protocol::HTTP => "HTTP",
1144            Protocol::HTTPS => "HTTPS",
1145            Protocol::IMAP => "IMAP",
1146            Protocol::POP3 => "POP3",
1147            Protocol::MMS => "MMS",
1148            Protocol::Local => "Local",
1149        }
1150    }
1151}
1152
1153impl Display for Host<'_> {
1154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1155        match self {
1156            Host::Name(name) => name.fmt(f),
1157            Host::IpAddr(ip) => ip.fmt(f),
1158        }
1159    }
1160}
1161
1162impl Display for Protocol {
1163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1164        f.write_str(self.as_str())
1165    }
1166}
1167
1168impl Display for Greeting {
1169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1170        f.write_str(self.as_str())
1171    }
1172}
1173
1174impl Display for TlsVersion {
1175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1176        f.write_str(self.as_str())
1177    }
1178}
1179
1180impl Display for HeaderName<'_> {
1181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1182        write!(f, "{}", self.as_str())
1183    }
1184}