pub mod format;
pub mod table;
pub mod utils;
use std::{
io::{self, Read},
path::{Path, PathBuf},
};
use mail_builder::{
MessageBuilder,
headers::{
HeaderType, address::Address as BuilderAddress, content_type::ContentType, date::Date,
text::Text,
},
mime::MimePart,
};
use mail_parser::{
Addr, Address, Group, HeaderValue, Message as ParsedMessage, MessageParser, MessagePart,
PartType,
};
pub struct Message {
inner: ParsedMessage<'static>,
path: Option<PathBuf>,
}
impl Message {
pub fn from_path(path: impl AsRef<Path>) -> io::Result<Self> {
let path = path.as_ref().to_path_buf();
let bytes = std::fs::read(&path)?;
Ok(Self {
inner: MessageParser::new().parse(&bytes).unwrap().into_owned(),
path: Some(path),
})
}
pub fn from_reader<R: Read>(mut r: R) -> io::Result<Self> {
let mut bytes = Vec::new();
r.read_to_end(&mut bytes)?;
Ok(Self {
inner: MessageParser::new().parse(&bytes).unwrap().into_owned(),
path: None,
})
}
pub fn path(&self) -> &Path {
self.path.as_ref().unwrap()
}
pub fn message(&self) -> &ParsedMessage<'_> {
&self.inner
}
pub fn builder(&self) -> MessageBuilder<'_> {
let mut builder = MessageBuilder::new();
builder = builder.body(message_part_to_mime_part(
self.inner.root_part(),
&self.inner,
));
builder
}
}
fn message_part_to_mime_part<'a>(part: &'a MessagePart, parsed: &'a ParsedMessage) -> MimePart<'a> {
let mut mime_part = match &part.body {
PartType::Text(s) | PartType::Html(s) => MimePart::raw(s.as_ref()),
PartType::Binary(b) | PartType::InlineBinary(b) => MimePart::raw(b.as_ref()),
PartType::Multipart(idxs) => MimePart::raw(
idxs.iter()
.filter_map(|i| parsed.part(*i))
.map(|part| message_part_to_mime_part(part, parsed))
.collect::<Vec<MimePart>>(),
),
PartType::Message(m) => MimePart::raw(
m.parts
.iter()
.map(|part| message_part_to_mime_part(part, parsed))
.collect::<Vec<MimePart>>(),
),
};
for header in part.headers() {
if header.name.as_str() == "MIME-Version" {
continue;
}
mime_part = mime_part.header(
header.name.as_str(),
parsed_header_to_builder_header(&header.value),
);
}
mime_part
}
fn parsed_header_to_builder_header<'a>(header_value: &'a HeaderValue) -> HeaderType<'a> {
match header_value {
HeaderValue::ContentType(ct) => {
let mut content_type = ContentType::new(ct.c_type.as_ref());
if let Some(attributes) = ct.attributes() {
for attribute in attributes {
content_type =
content_type.attribute(attribute.name.clone(), attribute.value.clone());
}
}
HeaderType::ContentType(content_type)
}
HeaderValue::Text(s) => HeaderType::Text(Text::new(s.as_ref())),
HeaderValue::DateTime(dt) => HeaderType::Date(Date::new(dt.to_timestamp())),
HeaderValue::Address(address) => {
HeaderType::Address(parse_address_to_builder_address(address))
}
_ => HeaderType::Text(Text::new("")),
}
}
fn parse_address_to_builder_address<'a>(addr: &'a Address) -> BuilderAddress<'a> {
match addr {
Address::List(addrs) => {
BuilderAddress::new_list(addrs.iter().map(parse_addr_to_builder_address).collect())
}
Address::Group(groups) => {
BuilderAddress::new_list(groups.iter().map(parse_group_to_builder_address).collect())
}
}
}
fn parse_group_to_builder_address<'a>(group: &'a Group) -> BuilderAddress<'a> {
BuilderAddress::new_group(
group.name.as_deref(),
group
.addresses
.iter()
.map(parse_addr_to_builder_address)
.collect(),
)
}
fn parse_addr_to_builder_address<'a>(addr: &'a Addr) -> BuilderAddress<'a> {
BuilderAddress::new_address(
addr.name.as_deref(),
addr.address.as_deref().unwrap_or_default(),
)
}