Skip to main content

mail_builder/headers/
address.rs

1use std::borrow::Cow;
2
3use super::{
4    Header,
5    fold::{FOLD_TARGET, FoldWriter, has_control},
6    rfc2047::write_phrase,
7};
8use crate::writer::Writer;
9
10/// RFC5322 e-mail address
11#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
12pub struct EmailAddress<'x> {
13    pub name: Option<Cow<'x, str>>,
14    pub email: Cow<'x, str>,
15}
16
17/// RFC5322 grouped e-mail addresses
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct GroupedAddresses<'x> {
20    pub name: Option<Cow<'x, str>>,
21    pub addresses: Vec<Address<'x>>,
22}
23
24/// RFC5322 address
25#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub enum Address<'x> {
27    Address(EmailAddress<'x>),
28    Group(GroupedAddresses<'x>),
29    List(Vec<Address<'x>>),
30}
31
32impl<'x> Address<'x> {
33    /// Create an RFC5322 e-mail address
34    pub fn new_address(name: Option<impl Into<Cow<'x, str>>>, email: impl Into<Cow<'x, str>>) -> Self {
35        Address::Address(EmailAddress {
36            name: name.map(|v| v.into()),
37            email: email.into(),
38        })
39    }
40
41    /// Create an RFC5322 grouped e-mail address
42    pub fn new_group(name: Option<impl Into<Cow<'x, str>>>, addresses: Vec<Address<'x>>) -> Self {
43        Address::Group(GroupedAddresses {
44            name: name.map(|v| v.into()),
45            addresses,
46        })
47    }
48
49    /// Create an address list
50    pub fn new_list(items: Vec<Address<'x>>) -> Self {
51        Address::List(items)
52    }
53
54    pub fn unwrap_address(&self) -> &EmailAddress<'x> {
55        match self {
56            Address::Address(address) => address,
57            _ => panic!("Address is not an EmailAddress"),
58        }
59    }
60}
61
62impl<'x> From<(&'x str, &'x str)> for Address<'x> {
63    fn from(value: (&'x str, &'x str)) -> Self {
64        Address::Address(EmailAddress {
65            name: Some(value.0.into()),
66            email: value.1.into(),
67        })
68    }
69}
70
71impl From<(String, String)> for Address<'_> {
72    fn from(value: (String, String)) -> Self {
73        Address::Address(EmailAddress {
74            name: Some(value.0.into()),
75            email: value.1.into(),
76        })
77    }
78}
79
80impl<'x> From<&'x str> for Address<'x> {
81    fn from(value: &'x str) -> Self {
82        Address::Address(EmailAddress {
83            name: None,
84            email: value.into(),
85        })
86    }
87}
88
89impl From<String> for Address<'_> {
90    fn from(value: String) -> Self {
91        Address::Address(EmailAddress {
92            name: None,
93            email: value.into(),
94        })
95    }
96}
97
98impl<'x, T> From<Vec<T>> for Address<'x>
99where
100    T: Into<Address<'x>>,
101{
102    fn from(value: Vec<T>) -> Self {
103        Address::new_list(value.into_iter().map(|x| x.into()).collect())
104    }
105}
106
107impl<'x, T, U> From<(U, Vec<T>)> for Address<'x>
108where
109    T: Into<Address<'x>>,
110    U: Into<Cow<'x, str>>,
111{
112    fn from(value: (U, Vec<T>)) -> Self {
113        Address::Group(GroupedAddresses {
114            name: Some(value.0.into()),
115            addresses: value.1.into_iter().map(|x| x.into()).collect(),
116        })
117    }
118}
119
120const TAIL_NONE: &[u8] = b"";
121const TAIL_COMMA: &[u8] = b",";
122
123impl Header for Address<'_> {
124    fn write_header(&self, output: &mut impl Writer, column: usize) {
125        if let Address::Address(address) = self
126            && address.name.is_none()
127            && column + address.email.len() + 2 <= FOLD_TARGET
128        {
129            output.write_byte(b'<');
130            output.write(&address.addr_spec());
131            output.write(b">\r\n");
132            return;
133        }
134
135        let mut folder = FoldWriter::new(output, column);
136        self.write_value(&mut folder, TAIL_NONE);
137        folder.finish();
138    }
139}
140
141impl Address<'_> {
142    #[inline(always)]
143    fn writes_nothing(&self, in_group: bool) -> bool {
144        match self {
145            Address::Address(_) => false,
146            Address::Group(group) => {
147                let named = group.name.is_some();
148                (in_group || !named)
149                    && group
150                        .addresses
151                        .iter()
152                        .all(|address| address.writes_nothing(in_group || named))
153            }
154            Address::List(list) => list.iter().all(|address| address.writes_nothing(in_group)),
155        }
156    }
157
158    fn write_value<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
159        match self {
160            Address::Address(address) => address.write_mailbox(folder, tail),
161            Address::Group(group) => group.write_group(folder, tail),
162            Address::List(list) => write_list(folder, list, tail, false),
163        }
164    }
165}
166
167fn write_list<W: Writer>(folder: &mut FoldWriter<'_, W>, list: &[Address<'_>], tail: &[u8], in_group: bool) {
168    let Some(last) = list.iter().rposition(|address| !address.writes_nothing(in_group)) else {
169        return;
170    };
171
172    for (pos, address) in list.iter().enumerate() {
173        if pos > last {
174            return;
175        }
176        if address.writes_nothing(in_group) {
177            continue;
178        }
179
180        let item_tail = if pos == last { tail } else { TAIL_COMMA };
181
182        match address {
183            Address::Address(address) => address.write_mailbox(folder, item_tail),
184            Address::Group(group) if in_group => write_list(folder, &group.addresses, item_tail, true),
185            Address::Group(group) => group.write_group(folder, item_tail),
186            Address::List(nested) => write_list(folder, nested, item_tail, in_group),
187        }
188
189        if pos == last {
190            return;
191        }
192        folder.space();
193    }
194}
195
196impl EmailAddress<'_> {
197    #[inline]
198    fn addr_spec(&self) -> Cow<'_, [u8]> {
199        let email = self.email.as_bytes();
200        if has_control(email) {
201            Cow::Owned(email.iter().copied().filter(|byte| !byte.is_ascii_control()).collect())
202        } else {
203            Cow::Borrowed(email)
204        }
205    }
206
207    #[inline]
208    pub(crate) fn write_mailbox<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
209        if let Some(name) = &self.name {
210            write_phrase(folder, name, TAIL_NONE);
211            folder.space();
212        }
213
214        folder.begin_atom(self.email.len() + 2 + tail.len());
215        folder.write_byte(b'<');
216        folder.write(&self.addr_spec());
217        match tail {
218            [] => folder.write_byte(b'>'),
219            [b','] => folder.write(b">,"),
220            [b';'] => folder.write(b">;"),
221            _ => {
222                folder.write_byte(b'>');
223                folder.write_tail(tail);
224            }
225        }
226    }
227}
228
229impl Header for EmailAddress<'_> {
230    fn write_header(&self, output: &mut impl Writer, column: usize) {
231        let mut folder = FoldWriter::new(output, column);
232        self.write_mailbox(&mut folder, TAIL_NONE);
233        folder.finish();
234    }
235}
236
237impl GroupedAddresses<'_> {
238    pub(crate) fn write_group<W: Writer>(&self, folder: &mut FoldWriter<'_, W>, tail: &[u8]) {
239        let Some(name) = &self.name else {
240            return write_list(folder, &self.addresses, tail, false);
241        };
242
243        let is_empty = self.addresses.iter().all(|address| address.writes_nothing(true));
244        let (name_tail, list_tail): (&[u8], &[u8]) = if tail.is_empty() {
245            (b":;", b";")
246        } else {
247            (b":;,", b";,")
248        };
249        let name_tail = if is_empty { name_tail } else { b":" };
250
251        write_phrase(folder, name, name_tail);
252
253        if !is_empty {
254            folder.space();
255            write_list(folder, &self.addresses, list_tail, true);
256        }
257    }
258}
259
260impl Header for GroupedAddresses<'_> {
261    fn write_header(&self, output: &mut impl Writer, column: usize) {
262        let mut folder = FoldWriter::new(output, column);
263        self.write_group(&mut folder, TAIL_NONE);
264        folder.finish();
265    }
266}