Skip to main content

mail_parser/view/
address.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use super::Resolver;
8use crate::store::AddressEntry;
9use std::fmt;
10
11/// An address list: mailboxes and groups in document order (issue #98).
12/// Lists compare by content.
13///
14/// ```
15/// use mail_parser::{Address, MessageParser};
16///
17/// let raw = b"To: Ann <ann@example.com>, team: bob@example.com, cy@example.com;\r\n\r\n";
18/// let message = MessageParser::new().parse(raw).expect("message");
19/// let to = message.to().expect("addresses");
20/// let addresses: Vec<_> = to.mailboxes().filter_map(|mailbox| mailbox.address()).collect();
21/// assert_eq!(addresses, ["ann@example.com", "bob@example.com", "cy@example.com"]);
22/// assert!(matches!(to.iter().nth(1), Some(Address::Group(group)) if group.name() == Some("team")));
23/// assert!(to.contains("BOB@example.com"));
24/// ```
25#[derive(Clone, Copy)]
26pub struct AddressList<'m> {
27    resolver: Resolver<'m>,
28    items: &'m [AddressEntry],
29}
30
31/// One item of an address list.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Address<'m> {
34    /// A mailbox outside any group.
35    Mailbox(Mailbox<'m>),
36    /// A group and its mailboxes.
37    Group(Group<'m>),
38}
39
40/// A mailbox: a display name and an address (or a URL in RFC 2369 list
41/// headers).
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct Mailbox<'m> {
44    name: Option<&'m str>,
45    address: Option<&'m str>,
46}
47
48/// A named group of mailboxes.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct Group<'m> {
51    name: Option<&'m str>,
52    members: AddressRun<'m>,
53}
54
55/// A run of consecutive mailboxes: the members of a [`Group`], or a run
56/// yielded by [`AddressList::groups`]. It is an iterator of [`Mailbox`], and
57/// runs compare by content.
58#[derive(Clone, Copy)]
59pub struct AddressRun<'m> {
60    resolver: Resolver<'m>,
61    items: &'m [AddressEntry],
62}
63
64impl fmt::Debug for AddressList<'_> {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.debug_list().entries(self.iter()).finish()
67    }
68}
69
70impl fmt::Debug for AddressRun<'_> {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.debug_list().entries(*self).finish()
73    }
74}
75
76impl PartialEq for AddressList<'_> {
77    fn eq(&self, other: &Self) -> bool {
78        self.items.len() == other.items.len() && self.iter().eq(other.iter())
79    }
80}
81
82impl Eq for AddressList<'_> {}
83
84impl PartialEq for AddressRun<'_> {
85    fn eq(&self, other: &Self) -> bool {
86        self.items.len() == other.items.len() && Iterator::eq(*self, *other)
87    }
88}
89
90impl Eq for AddressRun<'_> {}
91
92impl<'m> Resolver<'m> {
93    fn mailbox(self, entry: &AddressEntry) -> Mailbox<'m> {
94        Mailbox {
95            name: self.opt(entry.name),
96            address: self.opt(entry.address),
97        }
98    }
99}
100
101impl<'m> AddressList<'m> {
102    pub(crate) fn new(resolver: Resolver<'m>, items: &'m [AddressEntry]) -> Self {
103        AddressList { resolver, items }
104    }
105
106    /// Whether the list has neither mailboxes nor groups.
107    pub fn is_empty(&self) -> bool {
108        self.items.is_empty()
109    }
110
111    /// Mailboxes and groups in document order.
112    pub fn iter(&self) -> impl Iterator<Item = Address<'m>> + use<'m> {
113        let resolver = self.resolver;
114        let mut rest = self.items;
115        std::iter::from_fn(move || {
116            let (first, tail) = rest.split_first()?;
117            if first.is_group() {
118                let (members, tail) = tail.split_at(first.members().min(tail.len()));
119                rest = tail;
120                Some(Address::Group(Group {
121                    name: resolver.opt(first.name),
122                    members: AddressRun {
123                        resolver,
124                        items: members,
125                    },
126                }))
127            } else {
128                rest = tail;
129                Some(Address::Mailbox(resolver.mailbox(first)))
130            }
131        })
132    }
133
134    /// Every mailbox, groups flattened.
135    pub fn mailboxes(&self) -> impl DoubleEndedIterator<Item = Mailbox<'m>> + use<'m> {
136        let resolver = self.resolver;
137        self.items
138            .iter()
139            .filter(|entry| !entry.is_group())
140            .map(move |entry| resolver.mailbox(entry))
141    }
142
143    /// The first mailbox, groups flattened.
144    pub fn first(&self) -> Option<Mailbox<'m>> {
145        self.mailboxes().next()
146    }
147
148    /// The last mailbox, groups flattened.
149    pub fn last(&self) -> Option<Mailbox<'m>> {
150        self.mailboxes().next_back()
151    }
152
153    /// The JMAP `asGroupedAddresses` shape (RFC 8621): each group with its
154    /// name, and each run of mailboxes outside groups as an unnamed group.
155    pub fn groups(&self) -> impl Iterator<Item = (Option<&'m str>, AddressRun<'m>)> + use<'m> {
156        let resolver = self.resolver;
157        let mut rest = self.items;
158        std::iter::from_fn(move || {
159            let (first, tail) = rest.split_first()?;
160            let (name, run, tail) = if first.is_group() {
161                let (members, tail) = tail.split_at(first.members().min(tail.len()));
162                (resolver.opt(first.name), members, tail)
163            } else {
164                let len = rest.iter().take_while(|entry| !entry.is_group()).count();
165                let (run, tail) = rest.split_at(len);
166                (None, run, tail)
167            };
168            rest = tail;
169            Some((
170                name,
171                AddressRun {
172                    resolver,
173                    items: run,
174                },
175            ))
176        })
177    }
178
179    /// Whether any mailbox has this address (ASCII case-insensitive).
180    pub fn contains(&self, address: &str) -> bool {
181        self.mailboxes().any(|mailbox| {
182            mailbox
183                .address
184                .is_some_and(|a| a.eq_ignore_ascii_case(address))
185        })
186    }
187
188    /// Whether the list contains at least one group.
189    pub fn has_groups(&self) -> bool {
190        self.items.iter().any(AddressEntry::is_group)
191    }
192}
193
194impl<'m> Mailbox<'m> {
195    /// The display name, decoded and unquoted.
196    pub fn name(&self) -> Option<&'m str> {
197        self.name
198    }
199
200    /// The address (`local@domain`), or the URL of an RFC 2369 list header.
201    pub fn address(&self) -> Option<&'m str> {
202        self.address
203    }
204}
205
206impl<'m> Group<'m> {
207    /// The group name, decoded and unquoted.
208    pub fn name(&self) -> Option<&'m str> {
209        self.name
210    }
211
212    /// The members of the group.
213    pub fn mailboxes(&self) -> AddressRun<'m> {
214        self.members
215    }
216
217    /// Number of members.
218    pub fn len(&self) -> usize {
219        self.members.len()
220    }
221
222    /// Whether the group has no member.
223    pub fn is_empty(&self) -> bool {
224        self.members.len() == 0
225    }
226}
227
228impl<'m> Iterator for AddressRun<'m> {
229    type Item = Mailbox<'m>;
230
231    fn next(&mut self) -> Option<Mailbox<'m>> {
232        let (first, rest) = self.items.split_first()?;
233        self.items = rest;
234        Some(self.resolver.mailbox(first))
235    }
236
237    fn size_hint(&self) -> (usize, Option<usize>) {
238        (self.items.len(), Some(self.items.len()))
239    }
240}
241
242impl DoubleEndedIterator for AddressRun<'_> {
243    fn next_back(&mut self) -> Option<Self::Item> {
244        let (last, rest) = self.items.split_last()?;
245        self.items = rest;
246        Some(self.resolver.mailbox(last))
247    }
248}
249
250impl ExactSizeIterator for AddressRun<'_> {}