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#[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#[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#[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#[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#[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}