use std::{borrow::Cow, convert::TryInto};
use crate::{
decoders::html::{html_to_text, text_to_html},
parsers::{
fields::thread::thread_name,
preview::{preview_html, preview_text},
MessageStream,
},
Address, AttachmentIterator, BodyPartIterator, DateTime, GetHeader, Header, HeaderForm,
HeaderName, HeaderValue, Message, MessageParser, MessagePart, PartType, Received,
};
impl<'x> Message<'x> {
pub fn root_part(&self) -> &MessagePart<'x> {
&self.parts[0]
}
pub fn header(&self, header: impl Into<HeaderName<'x>>) -> Option<&HeaderValue> {
self.parts[0].headers.header(header).map(|h| &h.value)
}
pub fn remove_header(&mut self, header: impl Into<HeaderName<'x>>) -> Option<HeaderValue> {
let header = header.into();
let headers = &mut self.parts[0].headers;
headers
.iter()
.position(|h| h.name == header)
.map(|pos| headers.swap_remove(pos).value)
}
pub fn header_raw(&self, header: impl Into<HeaderName<'x>>) -> Option<&str> {
self.parts[0]
.headers
.header(header)
.and_then(|h| std::str::from_utf8(&self.raw_message[h.offset_start..h.offset_end]).ok())
}
pub fn header_as(
&self,
header: impl Into<HeaderName<'x>>,
form: HeaderForm,
) -> Vec<HeaderValue> {
let header = header.into();
let mut results = Vec::new();
for header_ in &self.parts[0].headers {
if header_.name == header {
results.push(
self.raw_message
.get(header_.offset_start..header_.offset_end)
.map_or(HeaderValue::Empty, |bytes| match form {
HeaderForm::Raw => HeaderValue::Text(
std::str::from_utf8(bytes).unwrap_or_default().trim().into(),
),
HeaderForm::Text => MessageStream::new(bytes).parse_unstructured(),
HeaderForm::Addresses => MessageStream::new(bytes).parse_address(),
HeaderForm::GroupedAddresses => {
MessageStream::new(bytes).parse_address()
}
HeaderForm::MessageIds => MessageStream::new(bytes).parse_id(),
HeaderForm::Date => MessageStream::new(bytes).parse_date(),
HeaderForm::URLs => MessageStream::new(bytes).parse_address(),
}),
);
}
}
results
}
pub fn headers(&self) -> &[Header] {
&self.parts[0].headers
}
pub fn header_values<'y: 'x>(
&'y self,
name: impl Into<HeaderName<'x>>,
) -> impl Iterator<Item = &HeaderValue<'x>> {
let name = name.into();
self.parts[0].headers.iter().filter_map(move |header| {
if header.name == name {
Some(&header.value)
} else {
None
}
})
}
pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &str)> {
self.parts[0].headers.iter().filter_map(move |header| {
Some((
header.name.as_str(),
std::str::from_utf8(&self.raw_message[header.offset_start..header.offset_end])
.ok()?,
))
})
}
pub fn raw_message(&self) -> &[u8] {
let part = &self.parts[0];
self.raw_message
.get(part.offset_header..part.offset_end)
.unwrap_or_default()
}
pub fn bcc<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::Bcc)
.and_then(|a| a.as_address())
}
pub fn cc<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::Cc)
.and_then(|a| a.as_address())
}
pub fn comments(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::Comments)
.unwrap_or(&HeaderValue::Empty)
}
pub fn date(&self) -> Option<&DateTime> {
self.parts[0]
.headers
.header_value(&HeaderName::Date)
.and_then(|header| header.as_datetime())
}
pub fn from<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::From)
.and_then(|a| a.as_address())
}
pub fn in_reply_to(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::InReplyTo)
.unwrap_or(&HeaderValue::Empty)
}
pub fn keywords(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::Keywords)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_archive(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListArchive)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_help(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListHelp)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_id(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListId)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_owner(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListOwner)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_post(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListPost)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_subscribe(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListSubscribe)
.unwrap_or(&HeaderValue::Empty)
}
pub fn list_unsubscribe(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ListUnsubscribe)
.unwrap_or(&HeaderValue::Empty)
}
pub fn message_id(&self) -> Option<&str> {
self.parts[0]
.headers
.header_value(&HeaderName::MessageId)
.and_then(|header| header.as_text())
}
pub fn mime_version(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::MimeVersion)
.unwrap_or(&HeaderValue::Empty)
}
pub fn received(&self) -> Option<&Received> {
self.parts[0]
.headers
.header_value(&HeaderName::Received)
.and_then(|header| header.as_received())
}
pub fn references(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::References)
.unwrap_or(&HeaderValue::Empty)
}
pub fn reply_to<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ReplyTo)
.and_then(|a| a.as_address())
}
pub fn resent_bcc<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ResentBcc)
.and_then(|a| a.as_address())
}
pub fn resent_cc<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ResentTo)
.and_then(|a| a.as_address())
}
pub fn resent_date(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ResentDate)
.unwrap_or(&HeaderValue::Empty)
}
pub fn resent_from<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ResentFrom)
.and_then(|a| a.as_address())
}
pub fn resent_message_id(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ResentMessageId)
.unwrap_or(&HeaderValue::Empty)
}
pub fn resent_sender<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ResentSender)
.and_then(|a| a.as_address())
}
pub fn resent_to<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::ResentTo)
.and_then(|a| a.as_address())
}
pub fn return_path(&self) -> &HeaderValue {
self.parts[0]
.headers
.header_value(&HeaderName::ReturnPath)
.unwrap_or(&HeaderValue::Empty)
}
pub fn return_address(&self) -> Option<&str> {
match self.parts[0].headers.header_value(&HeaderName::ReturnPath) {
Some(HeaderValue::Text(text)) => Some(text.as_ref()),
Some(HeaderValue::TextList(text_list)) => text_list.last().map(|t| t.as_ref()),
_ => match self.parts[0].headers.header_value(&HeaderName::From) {
Some(HeaderValue::Address(addr)) => addr.first()?.address.as_deref(),
_ => None,
},
}
}
pub fn sender<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::Sender)
.and_then(|a| a.as_address())
}
pub fn subject(&self) -> Option<&str> {
self.parts[0]
.headers
.header_value(&HeaderName::Subject)
.and_then(|header| header.as_text())
}
pub fn thread_name(&self) -> Option<&str> {
thread_name(self.subject()?).into()
}
pub fn to<'y: 'x>(&'y self) -> Option<&Address<'x>> {
self.parts[0]
.headers
.header_value(&HeaderName::To)
.and_then(|a| a.as_address())
}
pub fn body_preview(&self, preview_len: usize) -> Option<Cow<'x, str>> {
if !self.text_body.is_empty() {
preview_text(self.body_text(0)?, preview_len).into()
} else if !self.html_body.is_empty() {
preview_html(self.body_html(0)?, preview_len).into()
} else {
None
}
}
pub fn body_html(&'x self, pos: usize) -> Option<Cow<'x, str>> {
let part = self.parts.get(*self.html_body.get(pos)?)?;
match &part.body {
PartType::Html(html) => Some(html.as_ref().into()),
PartType::Text(text) => Some(text_to_html(text.as_ref()).into()),
_ => None,
}
}
pub fn body_text(&'x self, pos: usize) -> Option<Cow<'x, str>> {
let part = self.parts.get(*self.text_body.get(pos)?)?;
match &part.body {
PartType::Text(text) => Some(text.as_ref().into()),
PartType::Html(html) => Some(html_to_text(html.as_ref()).into()),
_ => None,
}
}
pub fn part(&self, pos: usize) -> Option<&MessagePart> {
self.parts.get(pos)
}
pub fn html_part(&self, pos: usize) -> Option<&MessagePart> {
self.parts.get(*self.html_body.get(pos)?)
}
pub fn text_part(&self, pos: usize) -> Option<&MessagePart> {
self.parts.get(*self.text_body.get(pos)?)
}
pub fn attachment(&self, pos: usize) -> Option<&MessagePart<'x>> {
self.parts.get(*self.attachments.get(pos)?)
}
pub fn text_body_count(&self) -> usize {
self.text_body.len()
}
pub fn html_body_count(&self) -> usize {
self.html_body.len()
}
pub fn attachment_count(&self) -> usize {
self.attachments.len()
}
pub fn text_bodies(&'x self) -> BodyPartIterator<'x> {
BodyPartIterator::new(self, &self.text_body)
}
pub fn html_bodies(&'x self) -> BodyPartIterator<'x> {
BodyPartIterator::new(self, &self.html_body)
}
pub fn attachments(&'x self) -> AttachmentIterator<'x> {
AttachmentIterator::new(self)
}
pub fn into_owned(self) -> Message<'static> {
Message {
html_body: self.html_body,
text_body: self.text_body,
attachments: self.attachments,
parts: self.parts.into_iter().map(|p| p.into_owned()).collect(),
raw_message: self.raw_message.into_owned().into(),
}
}
}
impl<'x> TryInto<Message<'x>> for &'x [u8] {
type Error = ();
fn try_into(self) -> Result<Message<'x>, Self::Error> {
MessageParser::default().parse(self).ok_or(())
}
}