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