1use 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 pub fn name(&self) -> &str {
21 self.name.as_str()
22 }
23
24 pub fn value(&self) -> &HeaderValue<'x> {
26 &self.value
27 }
28
29 pub fn offset_start(&self) -> u32 {
31 self.offset_start
32 }
33
34 pub fn offset_end(&self) -> u32 {
36 self.offset_end
37 }
38
39 pub fn offset_field(&self) -> u32 {
41 self.offset_field
42 }
43
44 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 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 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 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 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 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 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 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 pub fn is_text(&self) -> bool {
701 matches!(self.body, PartType::Text(_) | PartType::Html(_))
702 }
703
704 pub fn is_text_html(&self) -> bool {
706 matches!(self.body, PartType::Html(_))
707 }
708
709 pub fn is_binary(&self) -> bool {
711 matches!(self.body, PartType::Binary(_) | PartType::InlineBinary(_))
712 }
713
714 pub fn is_multipart(&self) -> bool {
716 matches!(self.body, PartType::Multipart(_))
717 }
718
719 pub fn is_message(&self) -> bool {
721 matches!(self.body, PartType::Message(_))
722 }
723
724 pub fn is_empty(&self) -> bool {
726 self.len() == 0
727 }
728
729 pub fn headers(&self) -> &[Header<'x>] {
731 &self.headers
732 }
733
734 pub fn raw_len(&self) -> u32 {
736 self.offset_end.saturating_sub(self.offset_header)
737 }
738
739 pub fn raw_header_offset(&self) -> u32 {
741 self.offset_header
742 }
743
744 pub fn raw_body_offset(&self) -> u32 {
746 self.offset_body
747 }
748
749 pub fn raw_end_offset(&self) -> u32 {
751 self.offset_end
752 }
753
754 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
825impl<'x> ContentType<'x> {
827 pub fn ctype(&self) -> &str {
829 &self.c_type
830 }
831
832 pub fn subtype(&self) -> Option<&str> {
834 self.c_subtype.as_ref()?.as_ref().into()
835 }
836
837 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 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 pub fn attributes(&self) -> Option<&[Attribute<'x>]> {
860 self.attributes.as_deref()
861 }
862
863 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 pub fn is_attachment(&self) -> bool {
872 self.c_type.eq_ignore_ascii_case("attachment")
873 }
874
875 pub fn is_inline(&self) -> bool {
877 self.c_type.eq_ignore_ascii_case("inline")
878 }
879}
880
881impl<'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 pub fn from(&self) -> Option<&Host<'x>> {
904 self.from.as_ref()
905 }
906
907 pub fn from_ip(&self) -> Option<IpAddr> {
909 self.from_ip
910 }
911
912 pub fn from_iprev(&self) -> Option<&str> {
914 self.from_iprev.as_ref().map(|s| s.as_ref())
915 }
916
917 pub fn by(&self) -> Option<&Host<'x>> {
919 self.by.as_ref()
920 }
921
922 pub fn for_(&self) -> Option<&str> {
924 self.for_.as_ref().map(|s| s.as_ref())
925 }
926
927 pub fn with(&self) -> Option<Protocol> {
929 self.with
930 }
931
932 pub fn tls_version(&self) -> Option<TlsVersion> {
934 self.tls_version
935 }
936
937 pub fn tls_cipher(&self) -> Option<&str> {
939 self.tls_cipher.as_ref().map(|s| s.as_ref())
940 }
941
942 pub fn id(&self) -> Option<&str> {
944 self.id.as_ref().map(|s| s.as_ref())
945 }
946
947 pub fn ident(&self) -> Option<&str> {
949 self.ident.as_ref().map(|s| s.as_ref())
950 }
951
952 pub fn helo(&self) -> Option<&Host<'x>> {
954 self.helo.as_ref()
955 }
956
957 pub fn helo_cmd(&self) -> Option<Greeting> {
959 self.helo_cmd
960 }
961
962 pub fn via(&self) -> Option<&str> {
964 self.via.as_ref().map(|s| s.as_ref())
965 }
966
967 pub fn date(&self) -> Option<DateTime> {
969 self.date
970 }
971}
972
973impl 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}