pub mod core;
pub mod decoders;
pub mod mailbox;
pub mod parsers;
use std::{borrow::Cow, collections::HashMap, hash::Hash, net::IpAddr};
use parsers::MessageStream;
#[cfg(feature = "serde_support")]
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MessageParser {
pub(crate) header_map: HashMap<HeaderName<'static>, HdrParseFnc>,
pub(crate) def_hdr_parse_fnc: HdrParseFnc,
}
pub(crate) type HdrParseFnc = for<'x> fn(&mut MessageStream<'x>) -> crate::HeaderValue<'x>;
#[derive(Debug, Default, PartialEq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Message<'x> {
#[cfg_attr(feature = "serde_support", serde(default))]
pub html_body: Vec<MessagePartId>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub text_body: Vec<MessagePartId>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub attachments: Vec<MessagePartId>,
#[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(skip))]
pub raw_message: Cow<'x, [u8]>,
}
#[derive(Debug, PartialEq, Default, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct MessagePart<'x> {
#[cfg_attr(feature = "serde_support", serde(default))]
pub headers: Vec<Header<'x>>,
pub is_encoding_problem: bool,
#[cfg_attr(feature = "serde_support", serde(default))]
#[cfg_attr(feature = "serde_support", serde(borrow))]
pub body: PartType<'x>,
#[cfg_attr(feature = "serde_support", serde(skip))]
pub encoding: Encoding,
pub offset_header: usize,
pub offset_body: usize,
pub offset_end: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum Encoding {
#[default]
None = 0,
QuotedPrintable = 1,
Base64 = 2,
}
impl From<u8> for Encoding {
fn from(v: u8) -> Self {
match v {
1 => Encoding::QuotedPrintable,
2 => Encoding::Base64,
_ => Encoding::None,
}
}
}
pub type MessagePartId = usize;
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum PartType<'x> {
Text(Cow<'x, str>),
Html(Cow<'x, str>),
#[cfg_attr(feature = "serde_support", serde(borrow))]
Binary(Cow<'x, [u8]>),
#[cfg_attr(feature = "serde_support", serde(borrow))]
InlineBinary(Cow<'x, [u8]>),
Message(Message<'x>),
Multipart(Vec<MessagePartId>),
}
impl<'x> Default for PartType<'x> {
fn default() -> Self {
PartType::Multipart(Vec::with_capacity(0))
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Addr<'x> {
#[cfg_attr(feature = "serde_support", serde(default))]
pub name: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub address: Option<Cow<'x, str>>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Group<'x> {
#[cfg_attr(feature = "serde_support", serde(default))]
pub name: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub addresses: Vec<Addr<'x>>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Header<'x> {
pub name: HeaderName<'x>,
pub value: HeaderValue<'x>,
pub offset_field: usize,
pub offset_start: usize,
pub offset_end: usize,
}
#[derive(Debug, Clone, PartialOrd, Ord)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde_support", serde(rename_all = "snake_case"))]
pub enum HeaderName<'x> {
Subject,
From,
To,
Cc,
Date,
Bcc,
ReplyTo,
Sender,
Comments,
InReplyTo,
Keywords,
Received,
MessageId,
References,
ReturnPath,
MimeVersion,
ContentDescription,
ContentId,
ContentLanguage,
ContentLocation,
ContentTransferEncoding,
ContentType,
ContentDisposition,
ResentTo,
ResentFrom,
ResentBcc,
ResentCc,
ResentSender,
ResentDate,
ResentMessageId,
ListArchive,
ListHelp,
ListId,
ListOwner,
ListPost,
ListSubscribe,
ListUnsubscribe,
Other(Cow<'x, str>),
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum HeaderValue<'x> {
Address(Address<'x>),
Text(Cow<'x, str>),
TextList(Vec<Cow<'x, str>>),
DateTime(DateTime),
ContentType(ContentType<'x>),
Received(Box<Received<'x>>),
#[default]
Empty,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum Address<'x> {
List(Vec<Addr<'x>>),
Group(Vec<Group<'x>>),
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum HeaderForm {
Raw,
Text,
Addresses,
GroupedAddresses,
MessageIds,
Date,
URLs,
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct ContentType<'x> {
pub c_type: Cow<'x, str>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub c_subtype: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub attributes: Option<Vec<(Cow<'x, str>, Cow<'x, str>)>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct DateTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub tz_before_gmt: bool,
pub tz_hour: u8,
pub tz_minute: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub struct Received<'x> {
#[cfg_attr(feature = "serde_support", serde(default))]
pub from: Option<Host<'x>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub from_ip: Option<IpAddr>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub from_iprev: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub by: Option<Host<'x>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub for_: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub with: Option<Protocol>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub tls_version: Option<TlsVersion>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub tls_cipher: Option<Cow<'x, str>>,
#[cfg_attr(
feature = "serde_support",
serde(skip_serializing_if = "Option::is_none")
)]
pub id: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub ident: Option<Cow<'x, str>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub helo: Option<Host<'x>>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub helo_cmd: Option<Greeting>,
#[cfg_attr(feature = "serde_support", serde(default))]
pub via: Option<Cow<'x, str>>,
pub date: Option<DateTime>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum Host<'x> {
Name(Cow<'x, str>),
IpAddr(IpAddr),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum TlsVersion {
SSLv2,
SSLv3,
TLSv1_0,
TLSv1_1,
TLSv1_2,
TLSv1_3,
DTLSv1_0,
DTLSv1_2,
DTLSv1_3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
pub enum Greeting {
Helo,
Ehlo,
Lhlo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
#[allow(clippy::upper_case_acronyms)]
pub enum Protocol {
SMTP,
ESMTP,
ESMTPA,
ESMTPS,
ESMTPSA,
LMTP,
LMTPA,
LMTPS,
LMTPSA,
MMS,
UTF8SMTP,
UTF8SMTPA,
UTF8SMTPS,
UTF8SMTPSA,
UTF8LMTP,
UTF8LMTPA,
UTF8LMTPS,
UTF8LMTPSA,
HTTP,
HTTPS,
IMAP,
POP3,
Local, }
pub trait MimeHeaders<'x> {
fn content_description(&self) -> Option<&str>;
fn content_disposition(&self) -> Option<&ContentType>;
fn content_id(&self) -> Option<&str>;
fn content_transfer_encoding(&self) -> Option<&str>;
fn content_type(&self) -> Option<&ContentType>;
fn content_language(&self) -> &HeaderValue;
fn content_location(&self) -> Option<&str>;
fn attachment_name(&self) -> Option<&str> {
self.content_disposition()
.and_then(|cd| cd.attribute("filename"))
.or_else(|| self.content_type().and_then(|ct| ct.attribute("name")))
}
fn is_content_type(&self, type_: &str, subtype: &str) -> bool {
self.content_type().map_or(false, |ct| {
ct.c_type.eq_ignore_ascii_case(type_)
&& ct
.c_subtype
.as_ref()
.map_or(false, |st| st.eq_ignore_ascii_case(subtype))
})
}
}
pub trait GetHeader<'x> {
fn header_value(&self, name: &HeaderName) -> Option<&HeaderValue>;
fn header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header>;
}
#[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,
}