use std::{
collections::HashSet,
convert::TryFrom,
hash::{Hash, Hasher},
};
use super::*;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GroupAddress {
pub raw: Vec<u8>,
pub display_name: StrBuilder,
pub mailbox_list: Vec<Address>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct MailboxAddress {
pub raw: Vec<u8>,
pub display_name: StrBuilder,
pub address_spec: StrBuilder,
}
impl Eq for MailboxAddress {}
impl PartialEq for MailboxAddress {
fn eq(&self, other: &Self) -> bool {
self.address_spec.display_bytes(&self.raw) == other.address_spec.display_bytes(&other.raw)
}
}
#[derive(Clone, Deserialize, Serialize)]
pub enum Address {
Mailbox(MailboxAddress),
Group(GroupAddress),
}
impl Address {
pub fn new(display_name: Option<String>, address: String) -> Self {
Self::Mailbox(if let Some(d) = display_name {
MailboxAddress {
raw: format!("{d} <{address}>").into_bytes(),
display_name: StrBuilder {
offset: 0,
length: d.len(),
},
address_spec: StrBuilder {
offset: d.len() + 2,
length: address.len(),
},
}
} else {
MailboxAddress {
raw: address.to_string().into_bytes(),
display_name: StrBuilder {
offset: 0,
length: 0,
},
address_spec: StrBuilder {
offset: 0,
length: address.len(),
},
}
})
}
pub fn new_group(display_name: String, mailbox_list: Vec<Self>) -> Self {
Self::Group(GroupAddress {
raw: format!(
"{}:{};",
display_name,
mailbox_list
.iter()
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join(",")
)
.into_bytes(),
display_name: StrBuilder {
offset: 0,
length: display_name.len(),
},
mailbox_list,
})
}
pub fn raw(&self) -> &[u8] {
match self {
Self::Mailbox(m) => m.raw.as_slice(),
Self::Group(g) => g.raw.as_slice(),
}
}
pub fn get_display_name(&self) -> Option<String> {
let ret = match self {
Self::Mailbox(m) => m.display_name.display(&m.raw),
Self::Group(g) => g.display_name.display(&g.raw),
};
if ret.is_empty() {
None
} else {
Some(ret)
}
}
pub fn get_email(&self) -> String {
match self {
Self::Mailbox(m) => m.address_spec.display(&m.raw),
Self::Group(_) => String::new(),
}
}
pub fn address_spec_raw(&self) -> &[u8] {
match self {
Self::Mailbox(m) => m.address_spec.display_bytes(&m.raw),
Self::Group(g) => &g.raw,
}
}
pub fn get_fqdn(&self) -> Option<String> {
match self {
Self::Mailbox(m) => {
let raw_address = m.address_spec.display_bytes(&m.raw);
let fqdn_pos = raw_address.iter().position(|&b| b == b'@')? + 1;
Some(String::from_utf8_lossy(&raw_address[fqdn_pos..]).into())
}
Self::Group(_) => None,
}
}
pub fn get_tags(&self, separator: char) -> Vec<String> {
let email = self.get_email();
let at_pos = email
.as_bytes()
.iter()
.position(|&b| b == b'@')
.unwrap_or(0);
let email: &str = email[..at_pos].into();
email
.split(separator)
.skip(1)
.map(str::to_string)
.collect::<_>()
}
pub fn list_try_from<T: AsRef<[u8]>>(val: T) -> Result<Vec<Self>> {
Ok(parser::address::rfc2822address_list(val.as_ref())?
.1
.to_vec())
}
pub fn contains_address(&self, other: &Self) -> bool {
match self {
Self::Mailbox(_) => self == other,
Self::Group(g) => g
.mailbox_list
.iter()
.any(|addr| addr.contains_address(other)),
}
}
pub fn subaddress(&self, separator: &str) -> Option<(Self, String)> {
match self {
Self::Mailbox(_) => {
let email = self.get_email();
let (local_part, domain) =
match super::parser::address::addr_spec_raw(email.as_bytes())
.map_err(Into::<Error>::into)
.and_then(|(_, (l, d))| {
Ok((String::from_utf8(l.into())?, String::from_utf8(d.into())?))
}) {
Ok(v) => v,
Err(_) => return None,
};
let s = local_part.split(separator).collect::<Vec<_>>();
if s.len() < 2 {
return None;
}
let subaddress = &local_part[s[0].len() + separator.len()..];
let display_name = self.get_display_name();
Some((
Self::new(display_name, format!("{}@{}", s[0], domain)),
subaddress.to_string(),
))
}
Self::Group(_) => None,
}
}
pub fn display_name_bytes(&self) -> &[u8] {
match self {
Self::Mailbox(m) => m.display_name.display_bytes(&m.raw),
Self::Group(g) => g.display_name.display_bytes(&g.raw),
}
}
pub fn display(&self) -> UIAddress<'_> {
UIAddress(self)
}
pub fn display_name(&self) -> UINameAddress<'_> {
UINameAddress(self)
}
pub fn display_slice(slice: &[Self], separator: Option<&str>) -> String {
let separator = separator.unwrap_or(", ");
match slice.first() {
None => String::new(),
Some(f) if slice.len() == 1 => f.display().to_string(),
Some(_) => slice
.iter()
.map(|a| a.display().to_string())
.collect::<Vec<String>>()
.join(separator),
}
}
pub fn display_name_slice(slice: &[Self], separator: Option<&str>) -> String {
let separator = separator.unwrap_or(", ");
match slice.first() {
None => String::new(),
Some(f) if slice.len() == 1 => f.display_name().to_string(),
Some(_) => slice
.iter()
.map(|a| a.display_name().to_string())
.collect::<Vec<String>>()
.join(separator),
}
}
}
impl Eq for Address {}
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Mailbox(_), Self::Group(_)) | (Self::Group(_), Self::Mailbox(_)) => false,
(Self::Mailbox(s), Self::Mailbox(o)) => s == o,
(Self::Group(s), Self::Group(o)) => {
self.display_name_bytes() == other.display_name_bytes()
&& s.mailbox_list.iter().collect::<HashSet<_>>()
== o.mailbox_list.iter().collect::<HashSet<_>>()
}
}
}
}
impl Hash for Address {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Mailbox(s) => {
s.address_spec.display_bytes(&s.raw).hash(state);
}
Self::Group(s) => {
s.display_name.display_bytes(&s.raw).hash(state);
for sub in &s.mailbox_list {
sub.hash(state);
}
}
}
}
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Mailbox(m) if m.display_name.length > 0 => match m.display_name.display(&m.raw) {
d if d.contains('.') || d.contains(',') => {
write!(f, "\"{}\" <{}>", d, m.address_spec.display(&m.raw))
}
d => write!(f, "{} <{}>", d, m.address_spec.display(&m.raw)),
},
Self::Mailbox(m) => write!(f, "{}", m.address_spec.display(&m.raw)),
Self::Group(g) => {
let attachment_strings: Vec<String> =
g.mailbox_list.iter().map(|a| format!("{a}")).collect();
write!(
f,
"{}: {}",
g.display_name.display(&g.raw),
attachment_strings.join(", ")
)
}
}
}
}
impl std::fmt::Debug for Address {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Mailbox(m) => f
.debug_struct(stringify!(Address::Mailbox))
.field("display_name", &m.display_name.display(&m.raw))
.field("address_spec", &m.address_spec.display(&m.raw))
.finish(),
Self::Group(g) => {
let attachment_strings: Vec<String> =
g.mailbox_list.iter().map(|a| format!("{a}")).collect();
f.debug_struct(stringify!(Address::Group))
.field("display_name", &g.display_name.display(&g.raw))
.field("addresses", &attachment_strings.join(", "))
.finish()
}
}
}
}
impl TryFrom<&str> for Address {
type Error = Error;
fn try_from(val: &str) -> Result<Self> {
Ok(parser::address::address(val.as_bytes())?.1)
}
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct UIAddress<'a>(&'a Address);
impl std::fmt::Display for UIAddress<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
Address::Mailbox(m) if m.display_name.length > 0 => write!(
f,
"{} <{}>",
m.display_name.display(&m.raw),
m.address_spec.display(&m.raw)
),
Address::Mailbox(m) => write!(f, "{}", m.address_spec.display(&m.raw)),
Address::Group(g) => {
let attachment_strings: Vec<String> =
g.mailbox_list.iter().map(|a| Self(a).to_string()).collect();
write!(
f,
"{}: {}",
g.display_name.display(&g.raw),
attachment_strings.join(", ")
)
}
}
}
}
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct UINameAddress<'a>(&'a Address);
impl std::fmt::Display for UINameAddress<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
Address::Mailbox(m) if m.display_name.length > 0 => {
write!(f, "{}", m.display_name.display(&m.raw),)
}
Address::Mailbox(m) => write!(f, "{}", m.address_spec.display(&m.raw)),
Address::Group(g) => {
let attachment_strings: Vec<String> =
g.mailbox_list.iter().map(|a| Self(a).to_string()).collect();
write!(
f,
"{}: {}",
g.display_name.display(&g.raw),
attachment_strings.join(", ")
)
}
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct StrBuilder {
pub offset: usize,
pub length: usize,
}
pub trait StrBuild {
fn new(string: &[u8], slice: &[u8]) -> Self;
fn raw(&self) -> &[u8];
fn val(&self) -> &[u8];
}
impl StrBuilder {
pub fn display(&self, s: &[u8]) -> String {
let offset = self.offset;
let length = self.length;
String::from_utf8_lossy(&s[offset..offset + length]).to_string()
}
pub fn display_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
&b[self.offset..(self.offset + self.length)]
}
}
#[derive(Clone, Default, Deserialize, Serialize)]
pub struct MessageID(pub Vec<u8>, pub StrBuilder);
impl MessageID {
pub fn display_brackets(&self) -> impl std::fmt::Display + '_ {
MessageIDBracket(self)
}
pub fn display_slice(slice: &[Self], separator: Option<&str>) -> String {
let separator = separator.unwrap_or(", ");
match slice.first() {
None => String::new(),
Some(f) if slice.len() == 1 => f.display_brackets().to_string(),
Some(_) => slice
.iter()
.map(|a| a.display_brackets().to_string())
.collect::<Vec<String>>()
.join(separator),
}
}
}
impl StrBuild for MessageID {
fn new(string: &[u8], slice: &[u8]) -> Self {
let offset = string.find(slice).unwrap_or(0);
Self(
string.to_owned(),
StrBuilder {
offset,
length: slice.len() + 1,
},
)
}
fn raw(&self) -> &[u8] {
let offset = self.1.offset;
let length = self.1.length;
&self.0[offset..offset + length.saturating_sub(1)]
}
fn val(&self) -> &[u8] {
&self.0
}
}
struct MessageIDBracket<'a>(&'a MessageID);
impl std::fmt::Display for MessageIDBracket<'_> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "<")?;
write!(fmt, "{}", self.0)?;
write!(fmt, ">")
}
}
impl std::fmt::Display for MessageID {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
let val = String::from_utf8_lossy(self.val());
write!(
fmt,
"{}",
val.trim().trim_start_matches('<').trim_end_matches('>')
)
}
}
impl PartialEq for MessageID {
fn eq(&self, other: &Self) -> bool {
self.raw() == other.raw()
}
}
impl Eq for MessageID {}
impl PartialEq<str> for MessageID {
fn eq(&self, other: &str) -> bool {
self.raw()
== other
.trim()
.trim_start_matches('<')
.trim_end_matches('>')
.as_bytes()
}
}
impl PartialEq<&str> for MessageID {
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl std::fmt::Debug for MessageID {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", String::from_utf8_lossy(self.raw()))
}
}
impl Hash for MessageID {
fn hash<H: Hasher>(&self, state: &mut H) {
self.raw().hash(state)
}
}
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct References {
refs: Vec<MessageID>,
}
impl std::fmt::Debug for References {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut dbg_t = fmt.debug_tuple(crate::identify! {References});
for r in &self.refs {
dbg_t.field(r);
}
dbg_t.finish()
}
}
impl References {
pub fn new(refs: Vec<MessageID>) -> Option<Self> {
if refs.is_empty() {
return None;
}
Some(Self { refs })
}
pub fn push(&mut self, new: MessageID) {
self.refs.push(new);
}
pub fn remove(&mut self, msgid: &MessageID) {
self.refs.retain(|r| r != msgid);
}
pub fn refs(&self) -> &[MessageID] {
&self.refs
}
}
impl Extend<MessageID> for References {
fn extend<T: IntoIterator<Item = MessageID>>(&mut self, iter: T) {
for elem in iter {
if !self.refs.contains(&elem) {
self.refs.push(elem);
}
}
}
}
impl<'a> Extend<&'a MessageID> for References {
fn extend<T: IntoIterator<Item = &'a MessageID>>(&mut self, iter: T) {
for elem in iter {
if !self.refs.contains(elem) {
self.refs.push(elem.clone());
}
}
}
}
#[macro_export]
macro_rules! make_address {
($d:expr, $a:expr) => {{
let display_name = { $d };
let address = { $a };
Address::Mailbox(if display_name.is_empty() {
MailboxAddress {
raw: format!("{}", address).into_bytes(),
display_name: StrBuilder {
offset: 0,
length: 0,
},
address_spec: StrBuilder {
offset: 0,
length: address.len(),
},
}
} else {
MailboxAddress {
raw: format!("{} <{}>", display_name, address).into_bytes(),
display_name: StrBuilder {
offset: 0,
length: display_name.len(),
},
address_spec: StrBuilder {
offset: display_name.len() + 2,
length: address.len(),
},
}
})
}};
}