#[forbid(unsafe_code)]
pub mod decoders;
pub mod parsers;
use std::{borrow::Cow, collections::HashMap, fmt};
use decoders::html::{html_to_text, text_to_html};
use parsers::fields::thread::thread_name;
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Message<'x> {
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "HashMap::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub headers_rfc: RfcHeaders<'x>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "HashMap::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub headers_other: OtherHeaders<'x>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub headers_offsets: Vec<HeaderOffset<'x>>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub html_body: Vec<MessagePartId>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub text_body: Vec<MessagePartId>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub attachments: Vec<MessagePartId>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
#[cfg_attr(feature = "serde_support", serde(borrow))]
pub parts: Vec<MessagePart<'x>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub structure: MessageStructure,
pub offset_header: usize,
pub offset_body: usize,
pub offset_end: usize,
#[cfg_attr(feature = "serde_support", serde(skip))]
pub raw_message: Cow<'x, [u8]>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum MessageStructure {
Part(MessagePartId),
List(Vec<MessageStructure>),
MultiPart((MessagePartId, Vec<MessageStructure>)),
}
impl Default for MessageStructure {
fn default() -> Self {
MessageStructure::Part(0)
}
}
#[derive(Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Part<'x, T> {
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "HashMap::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub headers: RfcHeaders<'x>,
pub body: T,
}
impl<'x, T> Part<'x, T> {
pub fn new(headers: RfcHeaders<'x>, body: T) -> Self {
Self { headers, body }
}
pub fn get_body(&self) -> &T {
&self.body
}
}
pub type MessagePartId = usize;
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum MessagePart<'x> {
Text(Part<'x, Cow<'x, str>>),
Html(Part<'x, Cow<'x, str>>),
#[cfg_attr(feature = "serde_support", serde(borrow))]
Binary(Part<'x, Cow<'x, [u8]>>),
#[cfg_attr(feature = "serde_support", serde(borrow))]
InlineBinary(Part<'x, Cow<'x, [u8]>>),
Message(Part<'x, MessageAttachment<'x>>),
Multipart(RfcHeaders<'x>),
}
impl<'x> MessagePart<'x> {
pub fn unwrap_text(&self) -> &Part<'x, Cow<'x, str>> {
match self {
MessagePart::Text(part) => part,
MessagePart::Html(part) => part,
_ => panic!("Expected text part."),
}
}
pub fn unwrap_binary(&self) -> &Part<'x, Cow<'x, [u8]>> {
match self {
MessagePart::Binary(part) => part,
MessagePart::InlineBinary(part) => part,
_ => panic!("Expected binary part."),
}
}
pub fn unwrap_message(&self) -> &Message {
match self {
MessagePart::Message(part) => match &part.body {
MessageAttachment::Parsed(message) => message.as_ref(),
MessageAttachment::Raw(_) => panic!(
"This message part has not been parsed yet, use parse_message() instead."
),
},
_ => panic!("Expected message part."),
}
}
pub fn parse_message(&'x self) -> Option<Message<'x>> {
match self {
MessagePart::Message(part) => match &part.body {
MessageAttachment::Parsed(_) => None,
MessageAttachment::Raw(raw_message) => Message::parse(raw_message.as_ref()),
},
_ => panic!("Expected message part."),
}
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Addr<'x> {
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub name: Option<Cow<'x, str>>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub address: Option<Cow<'x, str>>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Group<'x> {
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub name: Option<Cow<'x, str>>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Vec::is_empty")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub addresses: Vec<Addr<'x>>,
}
pub type RfcHeaders<'x> = HashMap<HeaderName, HeaderValue<'x>>;
pub type OtherHeaders<'x> = HashMap<Cow<'x, str>, HeaderValue<'x>>;
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct HeaderOffset<'x> {
pub name: HeaderOffsetName<'x>,
pub start: usize,
pub end: usize,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum HeaderOffsetName<'x> {
Rfc(HeaderName),
Other(Cow<'x, str>),
}
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", serde(rename_all = "snake_case"))]
pub enum HeaderName {
Subject = 0,
From = 1,
To = 2,
Cc = 3,
Date = 4,
Bcc = 5,
ReplyTo = 6,
Sender = 7,
Comments = 8,
InReplyTo = 9,
Keywords = 10,
Received = 11,
MessageId = 12,
References = 13,
ReturnPath = 14,
MimeVersion = 15,
ContentDescription = 16,
ContentId = 17,
ContentLanguage = 18,
ContentLocation = 19,
ContentTransferEncoding = 20,
ContentType = 21,
ContentDisposition = 22,
ResentTo = 23,
ResentFrom = 24,
ResentBcc = 25,
ResentCc = 26,
ResentSender = 27,
ResentDate = 28,
ResentMessageId = 29,
ListArchive = 30,
ListHelp = 31,
ListId = 32,
ListOwner = 33,
ListPost = 34,
ListSubscribe = 35,
ListUnsubscribe = 36,
Other = 37,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum HeaderValue<'x> {
Address(Addr<'x>),
AddressList(Vec<Addr<'x>>),
Group(Group<'x>),
GroupList(Vec<Group<'x>>),
Text(Cow<'x, str>),
TextList(Vec<Cow<'x, str>>),
DateTime(DateTime),
ContentType(ContentType<'x>),
Collection(Vec<HeaderValue<'x>>),
Empty,
}
impl<'x> Default for HeaderValue<'x> {
fn default() -> Self {
HeaderValue::Empty
}
}
impl<'x> HeaderValue<'x> {
pub fn is_empty(&self) -> bool {
*self == HeaderValue::Empty
}
pub fn unwrap_text(&mut self) -> Cow<'x, str> {
match std::mem::take(self) {
HeaderValue::Text(s) => s,
_ => panic!("HeaderValue::unwrap_text called on non-Text value"),
}
}
pub fn unwrap_datetime(&mut self) -> DateTime {
match std::mem::take(self) {
HeaderValue::DateTime(d) => d,
_ => panic!("HeaderValue::unwrap_datetime called on non-DateTime value"),
}
}
pub fn unwrap_content_type(&mut self) -> ContentType<'x> {
match std::mem::take(self) {
HeaderValue::ContentType(c) => c,
_ => panic!("HeaderValue::unwrap_content_type called on non-ContentType value"),
}
}
pub fn as_text_ref(&self) -> Option<&str> {
match *self {
HeaderValue::Text(ref s) => Some(s),
HeaderValue::TextList(ref l) => l.get(0)?.as_ref().into(),
_ => None,
}
}
pub fn get_content_type(&self) -> &ContentType<'x> {
match *self {
HeaderValue::ContentType(ref ct) => ct,
_ => panic!("HeaderValue::get_content_type called on non-ContentType value"),
}
}
pub fn as_content_type_ref(&self) -> Option<&ContentType> {
match *self {
HeaderValue::ContentType(ref c) => Some(c),
_ => None,
}
}
pub fn as_datetime_ref(&self) -> Option<&DateTime> {
match *self {
HeaderValue::DateTime(ref d) => Some(d),
_ => None,
}
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct ContentType<'x> {
pub c_type: Cow<'x, str>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub c_subtype: Option<Cow<'x, str>>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
#[cfg_attr(feature = "serde_support", serde(default))]
pub attributes: Option<HashMap<Cow<'x, str>, Cow<'x, str>>>,
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct DateTime {
pub year: u32,
pub month: u32,
pub day: u32,
pub hour: u32,
pub minute: u32,
pub second: u32,
pub tz_before_gmt: bool,
pub tz_hour: u32,
pub tz_minute: u32,
}
impl<'x> Message<'x> {
pub fn get_headers_rfc(&self) -> impl Iterator<Item = (&HeaderName, &HeaderValue<'x>)> {
self.headers_rfc.iter()
}
pub fn get_bcc(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Bcc)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_cc(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Cc)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_comments(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Comments)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_date(&self) -> Option<&DateTime> {
self.headers_rfc
.get(&HeaderName::Date)
.and_then(|header| header.as_datetime_ref())
}
pub fn get_from(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::From)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_in_reply_to(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::InReplyTo)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_keywords(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Keywords)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_archive(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListArchive)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_help(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListHelp)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_id(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListId)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_owner(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListOwner)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_post(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListPost)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_subscribe(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListSubscribe)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_list_unsubscribe(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ListUnsubscribe)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_message_id(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::MessageId)
.and_then(|header| header.as_text_ref())
}
pub fn get_mime_version(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::MimeVersion)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_received(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Received)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_references(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::References)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_reply_to(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ReplyTo)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_bcc(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentBcc)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_cc(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentTo)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_date(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentDate)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_from(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentFrom)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_message_id(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentMessageId)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_sender(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentSender)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_resent_to(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ResentTo)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_return_path(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ReturnPath)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_sender(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::Sender)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_subject(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::Subject)
.and_then(|header| header.as_text_ref())
}
pub fn get_thread_name(&self) -> Option<&str> {
thread_name(self.get_subject()?).into()
}
pub fn get_to(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::To)
.unwrap_or(&HeaderValue::Empty)
}
pub fn get_other(&self, name: &'x str) -> &HeaderValue<'x> {
self.headers_other
.get(&Cow::from(name))
.unwrap_or(&HeaderValue::Empty)
}
fn get_part(&self, list: &'x [MessagePartId], pos: usize) -> Option<&'x dyn BodyPart> {
match self.parts.get(*list.get(pos)?)? {
MessagePart::Text(v) => Some(v),
MessagePart::Html(v) => Some(v),
MessagePart::Binary(v) => Some(v),
MessagePart::InlineBinary(v) => Some(v),
MessagePart::Message(v) => Some(v),
_ => None,
}
}
pub fn get_html_body(&'x self, pos: usize) -> Option<Cow<'x, str>> {
match self.parts.get(*self.html_body.get(pos)?)? {
MessagePart::Html(html) => Some(html.body.as_ref().into()),
MessagePart::Text(text) => Some(text_to_html(text.body.as_ref()).into()),
_ => None,
}
}
pub fn get_text_body(&'x self, pos: usize) -> Option<Cow<'x, str>> {
match self.parts.get(*self.text_body.get(pos)?)? {
MessagePart::Text(text) => Some(text.body.as_ref().into()),
MessagePart::Html(html) => Some(html_to_text(html.body.as_ref()).into()),
_ => None,
}
}
pub fn get_html_part(&self, pos: usize) -> Option<&Part<Cow<'x, str>>> {
match self.parts.get(*self.html_body.get(pos)?)? {
MessagePart::Html(html) => Some(html),
MessagePart::Text(text) => Some(text),
_ => None,
}
}
pub fn get_text_part(&self, pos: usize) -> Option<&Part<Cow<'x, str>>> {
match self.parts.get(*self.text_body.get(pos)?)? {
MessagePart::Html(html) => Some(html),
MessagePart::Text(text) => Some(text),
_ => None,
}
}
pub fn get_attachment(&self, pos: usize) -> Option<&MessagePart<'x>> {
self.parts.get(*self.attachments.get(pos)?)
}
pub fn get_text_body_count(&self) -> usize {
self.text_body.len()
}
pub fn get_html_body_count(&self) -> usize {
self.html_body.len()
}
pub fn get_attachment_count(&self) -> usize {
self.attachments.len()
}
pub fn get_text_bodies(&'x self) -> BodyPartIterator<'x> {
BodyPartIterator::new(self, &self.text_body)
}
pub fn get_html_bodies(&'x self) -> BodyPartIterator<'x> {
BodyPartIterator::new(self, &self.html_body)
}
pub fn get_attachments(&'x self) -> AttachmentIterator<'x> {
AttachmentIterator::new(self)
}
}
pub trait MimeHeaders<'x> {
fn get_content_description(&self) -> Option<&str>;
fn get_content_disposition(&self) -> Option<&ContentType>;
fn get_content_id(&self) -> Option<&str>;
fn get_content_transfer_encoding(&self) -> Option<&str>;
fn get_content_type(&self) -> Option<&ContentType>;
fn get_content_language(&self) -> &HeaderValue<'x>;
fn get_content_location(&self) -> Option<&str>;
fn get_attachment_name(&self) -> Option<&str> {
self.get_content_disposition()
.and_then(|cd| cd.get_attribute("filename"))
.or_else(|| {
self.get_content_type()
.and_then(|ct| ct.get_attribute("name"))
})
}
}
impl<'x> MimeHeaders<'x> for Message<'x> {
fn get_content_description(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::ContentDescription)
.and_then(|header| header.as_text_ref())
}
fn get_content_disposition(&self) -> Option<&ContentType> {
self.headers_rfc
.get(&HeaderName::ContentDisposition)
.and_then(|header| header.as_content_type_ref())
}
fn get_content_id(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::ContentId)
.and_then(|header| header.as_text_ref())
}
fn get_content_transfer_encoding(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::ContentTransferEncoding)
.and_then(|header| header.as_text_ref())
}
fn get_content_type(&self) -> Option<&ContentType> {
self.headers_rfc
.get(&HeaderName::ContentType)
.and_then(|header| header.as_content_type_ref())
}
fn get_content_language(&self) -> &HeaderValue<'x> {
self.headers_rfc
.get(&HeaderName::ContentLanguage)
.unwrap_or(&HeaderValue::Empty)
}
fn get_content_location(&self) -> Option<&str> {
self.headers_rfc
.get(&HeaderName::ContentLocation)
.and_then(|header| header.as_text_ref())
}
}
pub trait BodyPart<'x>: fmt::Display + MimeHeaders<'x> {
fn get_contents(&'x self) -> &'x [u8];
fn get_text_contents(&'x self) -> &'x str;
fn len(&self) -> usize;
fn is_text(&self) -> bool;
fn is_binary(&self) -> bool;
fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<'x> BodyPart<'x> for Part<'x, Cow<'x, str>> {
fn get_contents(&'x self) -> &'x [u8] {
self.body.as_bytes()
}
fn is_text(&self) -> bool {
true
}
fn is_binary(&self) -> bool {
false
}
fn get_text_contents(&'x self) -> &'x str {
self.body.as_ref()
}
fn len(&self) -> usize {
self.body.len()
}
}
impl<'x> fmt::Display for Part<'x, Cow<'x, str>> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.body.as_ref())
}
}
impl<'x> BodyPart<'x> for Part<'x, Cow<'x, [u8]>> {
fn get_contents(&'x self) -> &'x [u8] {
self.body.as_ref()
}
fn get_text_contents(&'x self) -> &'x str {
std::str::from_utf8(self.body.as_ref()).unwrap_or("")
}
fn is_text(&self) -> bool {
false
}
fn is_binary(&self) -> bool {
true
}
fn len(&self) -> usize {
self.body.len()
}
}
impl<'x> fmt::Display for Part<'x, Cow<'x, [u8]>> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[binary contents]")
}
}
impl<'x> BodyPart<'x> for Part<'x, MessageAttachment<'x>> {
fn get_contents(&'x self) -> &'x [u8] {
match &self.body {
MessageAttachment::Parsed(msg) => msg.raw_message.as_ref(),
MessageAttachment::Raw(raw) => raw.as_ref(),
}
}
fn get_text_contents(&'x self) -> &'x str {
std::str::from_utf8(self.get_contents()).unwrap_or("")
}
fn is_text(&self) -> bool {
false
}
fn is_binary(&self) -> bool {
true
}
fn len(&self) -> usize {
match &self.body {
MessageAttachment::Parsed(msg) => msg.raw_message.len(),
MessageAttachment::Raw(raw) => raw.len(),
}
}
}
impl<'x> fmt::Display for Part<'x, MessageAttachment<'x>> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.get_text_contents())
}
}
impl<'x, T> MimeHeaders<'x> for Part<'x, T> {
fn get_content_description(&self) -> Option<&str> {
self.headers
.get(&HeaderName::ContentDescription)
.and_then(|header| header.as_text_ref())
}
fn get_content_disposition(&self) -> Option<&ContentType> {
self.headers
.get(&HeaderName::ContentDisposition)
.and_then(|header| header.as_content_type_ref())
}
fn get_content_id(&self) -> Option<&str> {
self.headers
.get(&HeaderName::ContentId)
.and_then(|header| header.as_text_ref())
}
fn get_content_transfer_encoding(&self) -> Option<&str> {
self.headers
.get(&HeaderName::ContentTransferEncoding)
.and_then(|header| header.as_text_ref())
}
fn get_content_type(&self) -> Option<&ContentType> {
self.headers
.get(&HeaderName::ContentType)
.and_then(|header| header.as_content_type_ref())
}
fn get_content_language(&self) -> &HeaderValue<'x> {
self.headers
.get(&HeaderName::ContentLanguage)
.unwrap_or(&HeaderValue::Empty)
}
fn get_content_location(&self) -> Option<&str> {
self.headers
.get(&HeaderName::ContentLocation)
.and_then(|header| header.as_text_ref())
}
}
#[doc(hidden)]
pub struct BodyPartIterator<'x> {
message: &'x Message<'x>,
list: &'x [MessagePartId],
pos: isize,
}
#[doc(hidden)]
pub struct AttachmentIterator<'x> {
message: &'x Message<'x>,
pos: isize,
}
impl<'x> BodyPartIterator<'x> {
fn new(message: &'x Message<'x>, list: &'x [MessagePartId]) -> BodyPartIterator<'x> {
BodyPartIterator {
message,
list,
pos: -1,
}
}
}
impl<'x> Iterator for BodyPartIterator<'x> {
type Item = &'x dyn BodyPart<'x>;
fn next(&mut self) -> Option<Self::Item> {
self.pos += 1;
self.message.get_part(self.list, self.pos as usize)
}
}
impl<'x> AttachmentIterator<'x> {
fn new(message: &'x Message<'x>) -> AttachmentIterator<'x> {
AttachmentIterator { message, pos: -1 }
}
}
impl<'x> Iterator for AttachmentIterator<'x> {
type Item = &'x MessagePart<'x>;
fn next(&mut self) -> Option<Self::Item> {
self.pos += 1;
self.message.get_attachment(self.pos as usize)
}
}
impl<'x> ContentType<'x> {
pub fn get_type(&'x self) -> &'x str {
&self.c_type
}
pub fn get_subtype(&'x self) -> Option<&'x str> {
self.c_subtype.as_ref()?.as_ref().into()
}
pub fn get_attribute(&'x self, name: &str) -> Option<&'x str> {
self.attributes.as_ref()?.get(name)?.as_ref().into()
}
pub fn has_attribute(&'x self, name: &str) -> bool {
self.attributes
.as_ref()
.map_or_else(|| false, |attr| attr.contains_key(name))
}
pub fn is_attachment(&'x self) -> bool {
self.c_type.eq_ignore_ascii_case("attachment")
}
pub fn is_inline(&'x self) -> bool {
self.c_type.eq_ignore_ascii_case("inline")
}
}
#[derive(Debug, PartialEq)]
pub enum MessageAttachment<'x> {
Parsed(Box<Message<'x>>),
Raw(Cow<'x, [u8]>),
}
impl<'x> Default for MessageAttachment<'x> {
fn default() -> Self {
MessageAttachment::Raw((&[] as &[u8]).into())
}
}
impl<'x> MessageAttachment<'x> {
pub fn parse_raw(&'x self) -> Option<Message<'x>> {
if let MessageAttachment::Raw(raw) = self {
Message::parse(raw.as_ref())
} else {
None
}
}
pub fn as_ref(&self) -> Option<&Message> {
if let MessageAttachment::Parsed(message) = self {
message.as_ref().into()
} else {
None
}
}
pub fn is_parsed(&self) -> bool {
matches!(self, MessageAttachment::Parsed(_))
}
}
impl<'x> Part<'x, MessageAttachment<'x>> {
pub fn parse_raw(&'x self) -> Option<Message<'x>> {
self.body.parse_raw()
}
pub fn as_ref(&self) -> Option<&Message> {
self.body.as_ref()
}
pub fn is_parsed(&self) -> bool {
self.body.is_parsed()
}
}
impl<'x> Serialize for MessageAttachment<'x> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MessageAttachment::Parsed(message) => message.serialize(serializer),
MessageAttachment::Raw(raw) => Message::parse(raw.as_ref())
.ok_or_else(|| serde::ser::Error::custom("Failed to parse message attachment."))?
.serialize(serializer),
}
}
}
impl<'x, 'de> Deserialize<'de> for MessageAttachment<'x> {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("Deserializing message attachments is not supported at this time.")
}
}