Skip to main content

email_message/
address.rs

1use std::fmt::Display;
2use std::str::FromStr;
3
4use crate::email::{Email, EmailParseError};
5
6fn write_quoted(value: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7    f.write_str("\"")?;
8    for ch in value.chars() {
9        if ch == '\\' || ch == '"' {
10            f.write_str("\\")?;
11        }
12        f.write_str(ch.encode_utf8(&mut [0; 4]))?;
13    }
14    f.write_str("\"")
15}
16
17/// A mailbox address with optional display name.
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
19pub struct Mailbox {
20    name: Option<String>,
21    email: Email,
22}
23
24#[derive(Debug, thiserror::Error)]
25pub enum MailboxParseError {
26    #[error("expected a single mailbox, found {found} address item(s)")]
27    ExpectedSingleMailbox { found: usize },
28    #[error("expected mailbox but found group")]
29    UnexpectedAddressKind,
30    #[error("mailbox list contains group entries")]
31    ContainsGroupEntry,
32    #[error("mailbox parse backend failed")]
33    Backend {
34        #[source]
35        source: AddressBackendError,
36    },
37}
38
39impl Mailbox {
40    pub fn name(&self) -> Option<&str> {
41        self.name.as_deref()
42    }
43
44    pub fn email(&self) -> &Email {
45        &self.email
46    }
47}
48
49impl From<Email> for Mailbox {
50    fn from(email: Email) -> Self {
51        Self { name: None, email }
52    }
53}
54
55impl From<(String, Email)> for Mailbox {
56    fn from((name, email): (String, Email)) -> Self {
57        Self {
58            name: Some(name),
59            email,
60        }
61    }
62}
63
64impl From<(Option<String>, Email)> for Mailbox {
65    fn from((name, email): (Option<String>, Email)) -> Self {
66        Self { name, email }
67    }
68}
69
70impl FromStr for Mailbox {
71    type Err = MailboxParseError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        if let Ok(email) = Email::from_str(s) {
75            return Ok(Mailbox::from(email));
76        }
77
78        let addresses =
79            parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
80        if addresses.len() != 1 {
81            return Err(MailboxParseError::ExpectedSingleMailbox {
82                found: addresses.len(),
83            });
84        }
85
86        match addresses.into_iter().next() {
87            Some(Address::Mailbox(mailbox)) => Ok(mailbox),
88            _ => Err(MailboxParseError::UnexpectedAddressKind),
89        }
90    }
91}
92
93impl Display for Mailbox {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self.name() {
96            Some(name) => {
97                write_quoted(name, f)?;
98                f.write_str(" <")?;
99                self.email.fmt(f)?;
100                f.write_str(">")
101            }
102            None => self.email.fmt(f),
103        }
104    }
105}
106
107/// A named address group containing mailbox members.
108#[derive(Clone, Debug, PartialEq, Eq, Hash)]
109pub struct Group {
110    name: String,
111    members: Vec<Mailbox>,
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum GroupParseError {
116    #[error("expected a single group, found {found} address item(s)")]
117    ExpectedSingleGroup { found: usize },
118    #[error("expected group but found mailbox")]
119    UnexpectedAddressKind,
120    #[error("group parse backend failed")]
121    Backend {
122        #[source]
123        source: AddressBackendError,
124    },
125}
126
127impl FromStr for Group {
128    type Err = GroupParseError;
129
130    fn from_str(s: &str) -> Result<Self, Self::Err> {
131        let addresses =
132            parse_address_items(s).map_err(|source| GroupParseError::Backend { source })?;
133        if addresses.len() != 1 {
134            return Err(GroupParseError::ExpectedSingleGroup {
135                found: addresses.len(),
136            });
137        }
138
139        match addresses.into_iter().next() {
140            Some(Address::Group(group)) => Ok(group),
141            _ => Err(GroupParseError::UnexpectedAddressKind),
142        }
143    }
144}
145
146impl Group {
147    pub fn name(&self) -> &str {
148        self.name.as_str()
149    }
150
151    pub fn members(&self) -> &[Mailbox] {
152        self.members.as_slice()
153    }
154}
155
156impl Display for Group {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write_quoted(self.name(), f)?;
159        f.write_str(":")?;
160        for (idx, member) in self.members().iter().enumerate() {
161            if idx > 0 {
162                f.write_str(",")?;
163            }
164            member.fmt(f)?;
165        }
166        f.write_str(";")
167    }
168}
169
170/// A single address item: either a mailbox or a group.
171#[derive(Clone, Debug, PartialEq, Eq, Hash)]
172pub enum Address {
173    Mailbox(Mailbox),
174    Group(Group),
175}
176
177#[derive(Debug, thiserror::Error)]
178pub enum AddressParseError {
179    #[error("expected a single address, found {found} address item(s)")]
180    ExpectedSingleAddress { found: usize },
181    #[error("address parse backend failed")]
182    Backend {
183        #[source]
184        source: AddressBackendError,
185    },
186}
187
188impl FromStr for Address {
189    type Err = AddressParseError;
190
191    fn from_str(s: &str) -> Result<Self, Self::Err> {
192        if let Ok(mailbox) = Mailbox::from_str(s) {
193            return Ok(Address::Mailbox(mailbox));
194        }
195
196        let addresses =
197            parse_address_items(s).map_err(|source| AddressParseError::Backend { source })?;
198        if addresses.len() != 1 {
199            return Err(AddressParseError::ExpectedSingleAddress {
200                found: addresses.len(),
201            });
202        }
203
204        addresses
205            .into_iter()
206            .next()
207            .ok_or(AddressParseError::ExpectedSingleAddress { found: 0 })
208    }
209}
210
211impl Display for Address {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        match self {
214            Self::Mailbox(mailbox) => mailbox.fmt(f),
215            Self::Group(group) => group.fmt(f),
216        }
217    }
218}
219
220/// A parsed list of address items.
221///
222/// This is used instead of `Vec<Address>` for `FromStr`, because Rust's orphan
223/// rules do not allow implementing foreign traits for foreign types.
224#[derive(Clone, Debug, PartialEq, Eq, Hash)]
225pub struct AddressList {
226    items: Vec<Address>,
227}
228
229impl AddressList {
230    pub fn len(&self) -> usize {
231        self.items.len()
232    }
233
234    pub fn is_empty(&self) -> bool {
235        self.items.is_empty()
236    }
237
238    pub fn iter(&self) -> std::slice::Iter<'_, Address> {
239        self.items.iter()
240    }
241
242    pub fn as_slice(&self) -> &[Address] {
243        self.items.as_slice()
244    }
245
246    pub fn into_vec(self) -> Vec<Address> {
247        self.items
248    }
249}
250
251impl From<Vec<Address>> for AddressList {
252    fn from(items: Vec<Address>) -> Self {
253        Self { items }
254    }
255}
256
257impl From<AddressList> for Vec<Address> {
258    fn from(value: AddressList) -> Self {
259        value.items
260    }
261}
262
263impl FromStr for AddressList {
264    type Err = AddressParseError;
265
266    fn from_str(s: &str) -> Result<Self, Self::Err> {
267        let items =
268            parse_address_items(s).map_err(|source| AddressParseError::Backend { source })?;
269        Ok(Self { items })
270    }
271}
272
273impl IntoIterator for AddressList {
274    type Item = Address;
275    type IntoIter = std::vec::IntoIter<Address>;
276
277    fn into_iter(self) -> Self::IntoIter {
278        self.items.into_iter()
279    }
280}
281
282impl<'a> IntoIterator for &'a AddressList {
283    type Item = &'a Address;
284    type IntoIter = std::slice::Iter<'a, Address>;
285
286    fn into_iter(self) -> Self::IntoIter {
287        self.items.iter()
288    }
289}
290
291impl<'a> IntoIterator for &'a mut AddressList {
292    type Item = &'a mut Address;
293    type IntoIter = std::slice::IterMut<'a, Address>;
294
295    fn into_iter(self) -> Self::IntoIter {
296        self.items.iter_mut()
297    }
298}
299
300impl std::ops::Deref for AddressList {
301    type Target = [Address];
302
303    fn deref(&self) -> &Self::Target {
304        self.items.as_slice()
305    }
306}
307
308impl AsRef<[Address]> for AddressList {
309    fn as_ref(&self) -> &[Address] {
310        self.items.as_slice()
311    }
312}
313
314impl Display for AddressList {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        for (idx, address) in self.items.iter().enumerate() {
317            if idx > 0 {
318                f.write_str(", ")?;
319            }
320            address.fmt(f)?;
321        }
322        Ok(())
323    }
324}
325
326/// A parsed list of mailbox items.
327///
328/// Group entries are rejected when parsing into `MailboxList`.
329#[derive(Clone, Debug, PartialEq, Eq, Hash)]
330pub struct MailboxList {
331    items: Vec<Mailbox>,
332}
333
334impl MailboxList {
335    pub fn len(&self) -> usize {
336        self.items.len()
337    }
338
339    pub fn is_empty(&self) -> bool {
340        self.items.is_empty()
341    }
342
343    pub fn iter(&self) -> std::slice::Iter<'_, Mailbox> {
344        self.items.iter()
345    }
346
347    pub fn as_slice(&self) -> &[Mailbox] {
348        self.items.as_slice()
349    }
350
351    pub fn into_vec(self) -> Vec<Mailbox> {
352        self.items
353    }
354}
355
356impl From<Vec<Mailbox>> for MailboxList {
357    fn from(items: Vec<Mailbox>) -> Self {
358        Self { items }
359    }
360}
361
362impl From<MailboxList> for Vec<Mailbox> {
363    fn from(value: MailboxList) -> Self {
364        value.items
365    }
366}
367
368impl FromStr for MailboxList {
369    type Err = MailboxParseError;
370
371    fn from_str(s: &str) -> Result<Self, Self::Err> {
372        let addresses =
373            parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
374        let mut items = Vec::with_capacity(addresses.len());
375
376        for address in addresses {
377            match address {
378                Address::Mailbox(mailbox) => items.push(mailbox),
379                Address::Group(_) => return Err(MailboxParseError::ContainsGroupEntry),
380            }
381        }
382
383        Ok(Self { items })
384    }
385}
386
387impl IntoIterator for MailboxList {
388    type Item = Mailbox;
389    type IntoIter = std::vec::IntoIter<Mailbox>;
390
391    fn into_iter(self) -> Self::IntoIter {
392        self.items.into_iter()
393    }
394}
395
396impl<'a> IntoIterator for &'a MailboxList {
397    type Item = &'a Mailbox;
398    type IntoIter = std::slice::Iter<'a, Mailbox>;
399
400    fn into_iter(self) -> Self::IntoIter {
401        self.items.iter()
402    }
403}
404
405impl<'a> IntoIterator for &'a mut MailboxList {
406    type Item = &'a mut Mailbox;
407    type IntoIter = std::slice::IterMut<'a, Mailbox>;
408
409    fn into_iter(self) -> Self::IntoIter {
410        self.items.iter_mut()
411    }
412}
413
414impl std::ops::Deref for MailboxList {
415    type Target = [Mailbox];
416
417    fn deref(&self) -> &Self::Target {
418        self.items.as_slice()
419    }
420}
421
422impl AsRef<[Mailbox]> for MailboxList {
423    fn as_ref(&self) -> &[Mailbox] {
424        self.items.as_slice()
425    }
426}
427
428impl Display for MailboxList {
429    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430        for (idx, mailbox) in self.items.iter().enumerate() {
431            if idx > 0 {
432                f.write_str(", ")?;
433            }
434            mailbox.fmt(f)?;
435        }
436        Ok(())
437    }
438}
439
440#[derive(Debug, thiserror::Error)]
441pub enum AddressBackendError {
442    #[error("failed to parse address header")]
443    HeaderParse,
444    #[error("parsed header did not contain address data")]
445    MissingAddress,
446    #[error("mailbox is missing addr-spec")]
447    MissingAddrSpec,
448    #[error("invalid addr-spec `{input}`")]
449    InvalidAddrSpec {
450        input: String,
451        #[source]
452        source: EmailParseError,
453    },
454    #[error("group is missing a name")]
455    GroupMissingName,
456}
457
458#[derive(Debug, thiserror::Error)]
459pub enum ParseError {
460    #[error(transparent)]
461    Email(#[from] EmailParseError),
462    #[error(transparent)]
463    Mailbox(#[from] MailboxParseError),
464    #[error(transparent)]
465    Group(#[from] GroupParseError),
466    #[error(transparent)]
467    Address(#[from] AddressParseError),
468}
469
470fn parse_address_items(input: &str) -> Result<Vec<Address>, AddressBackendError> {
471    let raw = format!("To: {input}\r\n\r\n");
472    let parser = mail_parser::MessageParser::new().with_address_headers();
473    let message = parser
474        .parse_headers(raw.as_bytes())
475        .ok_or(AddressBackendError::HeaderParse)?;
476    let parsed = message.to().ok_or(AddressBackendError::MissingAddress)?;
477
478    match parsed {
479        mail_parser::Address::List(list) => list
480            .iter()
481            .map(convert_mailbox)
482            .map(|result| result.map(Address::Mailbox))
483            .collect(),
484        mail_parser::Address::Group(groups) => groups
485            .iter()
486            .map(convert_group)
487            .map(|result| result.map(Address::Group))
488            .collect(),
489    }
490}
491
492fn convert_mailbox(value: &mail_parser::Addr<'_>) -> Result<Mailbox, AddressBackendError> {
493    let raw_email = value
494        .address()
495        .ok_or(AddressBackendError::MissingAddrSpec)?;
496    let email =
497        Email::from_str(raw_email).map_err(|source| AddressBackendError::InvalidAddrSpec {
498            input: raw_email.to_owned(),
499            source,
500        })?;
501
502    Ok(match value.name() {
503        Some(name) => Mailbox::from((name.to_owned(), email)),
504        None => Mailbox::from(email),
505    })
506}
507
508fn convert_group(value: &mail_parser::Group<'_>) -> Result<Group, AddressBackendError> {
509    let name = value
510        .name
511        .as_deref()
512        .ok_or(AddressBackendError::GroupMissingName)?
513        .to_owned();
514    let members = value
515        .addresses
516        .iter()
517        .map(convert_mailbox)
518        .collect::<Result<Vec<_>, _>>()?;
519
520    Ok(Group { name, members })
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526
527    #[test]
528    fn mailbox_from_str_accepts_rfc_examples() {
529        let parsed = "Mary Smith <mary@x.test>".parse::<Mailbox>();
530        assert!(parsed.is_ok(), "expected valid mailbox");
531
532        let parsed = "jdoe@one.test".parse::<Mailbox>();
533        assert!(parsed.is_ok(), "expected valid mailbox");
534    }
535
536    #[test]
537    fn mailbox_from_str_rejects_group() {
538        let parsed = "Undisclosed recipients:;".parse::<Mailbox>();
539        assert!(matches!(
540            parsed,
541            Err(MailboxParseError::UnexpectedAddressKind)
542        ));
543    }
544
545    #[test]
546    fn group_from_str_accepts_rfc_examples() {
547        let parsed =
548            "A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;".parse::<Group>();
549        assert!(parsed.is_ok(), "expected valid group");
550
551        let parsed = "Undisclosed recipients:;".parse::<Group>();
552        assert!(parsed.is_ok(), "expected valid group");
553    }
554
555    #[test]
556    fn address_list_roundtrip() {
557        let list = "Mary Smith <mary@x.test>, jdoe@one.test"
558            .parse::<AddressList>()
559            .expect("address list should parse");
560        let rendered = list.to_string();
561        let reparsed = rendered
562            .parse::<AddressList>()
563            .expect("rendered address list should parse");
564        assert_eq!(reparsed.as_slice(), list.as_slice());
565    }
566}