1use std::fmt::Display;
2use std::str::FromStr;
3use std::sync::OnceLock;
4
5use crate::email::{EmailAddress, EmailAddressParseError};
6
7static ADDRESS_PARSER: OnceLock<mail_parser::MessageParser> = OnceLock::new();
8
9#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
11#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub struct Mailbox {
13 name: Option<String>,
14 email: EmailAddress,
15}
16
17impl Mailbox {
18 #[must_use]
20 pub fn name(&self) -> Option<&str> {
21 self.name.as_deref()
22 }
23
24 #[must_use]
26 pub const fn email(&self) -> &EmailAddress {
27 &self.email
28 }
29}
30
31impl From<EmailAddress> for Mailbox {
32 fn from(email: EmailAddress) -> Self {
33 Self { name: None, email }
34 }
35}
36
37impl From<(String, EmailAddress)> for Mailbox {
38 fn from((name, email): (String, EmailAddress)) -> Self {
39 Self {
40 name: Some(name),
41 email,
42 }
43 }
44}
45
46impl From<(Option<String>, EmailAddress)> for Mailbox {
47 fn from((name, email): (Option<String>, EmailAddress)) -> Self {
48 Self { name, email }
49 }
50}
51
52#[derive(Debug, thiserror::Error)]
53#[non_exhaustive]
54pub enum MailboxParseError {
55 #[error("expected a single mailbox, found {found} address item(s)")]
56 ExpectedSingleMailbox { found: usize },
57 #[error("expected mailbox but found group")]
58 UnexpectedAddressKind,
59 #[error("mailbox list contains group entries")]
60 ContainsGroupEntry,
61 #[error("mailbox parse backend failed")]
62 Backend {
63 #[source]
64 source: AddressBackendError,
65 },
66}
67
68impl FromStr for Mailbox {
69 type Err = MailboxParseError;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 if let Ok(email) = EmailAddress::from_str(s) {
73 return Ok(Self::from(email));
74 }
75
76 let addresses =
77 parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
78 if addresses.len() != 1 {
79 return Err(MailboxParseError::ExpectedSingleMailbox {
80 found: addresses.len(),
81 });
82 }
83
84 match addresses.into_iter().next() {
85 Some(Address::Mailbox(mailbox)) => Ok(mailbox),
86 _ => Err(MailboxParseError::UnexpectedAddressKind),
87 }
88 }
89}
90
91impl TryFrom<&str> for Mailbox {
92 type Error = MailboxParseError;
93
94 fn try_from(value: &str) -> Result<Self, Self::Error> {
104 Self::from_str(value)
105 }
106}
107
108impl Display for Mailbox {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self.name() {
125 Some(name) => {
126 write_quoted(name, f)?;
127 f.write_str(" <")?;
128 self.email.fmt(f)?;
129 f.write_str(">")
130 }
131 None => self.email.fmt(f),
132 }
133 }
134}
135
136#[cfg(feature = "serde")]
137impl serde::Serialize for Mailbox {
138 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
139 where
140 S: serde::Serializer,
141 {
142 serializer.serialize_str(&self.to_string())
143 }
144}
145
146#[cfg(feature = "serde")]
147impl<'de> serde::Deserialize<'de> for Mailbox {
148 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
149 where
150 D: serde::Deserializer<'de>,
151 {
152 let value = String::deserialize(deserializer)?;
153 value.parse().map_err(serde::de::Error::custom)
154 }
155}
156
157#[cfg(feature = "arbitrary")]
158impl<'a> arbitrary::Arbitrary<'a> for Mailbox {
159 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
160 let email = EmailAddress::arbitrary(u)?;
161 if bool::arbitrary(u)? {
162 let name = format!("User {}", u8::arbitrary(u)?);
163 Ok(Self {
164 name: Some(name),
165 email,
166 })
167 } else {
168 Ok(Self { name: None, email })
169 }
170 }
171}
172
173#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
175#[derive(Clone, Debug, PartialEq, Eq, Hash)]
176pub struct Group {
177 name: String,
178 members: Vec<Mailbox>,
179}
180
181impl Group {
182 #[must_use]
184 pub fn name(&self) -> &str {
185 self.name.as_str()
186 }
187
188 #[must_use]
190 pub fn members(&self) -> &[Mailbox] {
191 self.members.as_slice()
192 }
193}
194
195#[derive(Debug, thiserror::Error)]
196#[non_exhaustive]
197pub enum GroupParseError {
198 #[error("expected a single group, found {found} address item(s)")]
199 ExpectedSingleGroup { found: usize },
200 #[error("expected group but found mailbox")]
201 UnexpectedAddressKind,
202 #[error("group parse backend failed")]
203 Backend {
204 #[source]
205 source: AddressBackendError,
206 },
207}
208
209impl FromStr for Group {
210 type Err = GroupParseError;
211
212 fn from_str(s: &str) -> Result<Self, Self::Err> {
213 let addresses =
214 parse_address_items(s).map_err(|source| GroupParseError::Backend { source })?;
215 if addresses.len() != 1 {
216 return Err(GroupParseError::ExpectedSingleGroup {
217 found: addresses.len(),
218 });
219 }
220
221 match addresses.into_iter().next() {
222 Some(Address::Group(group)) => Ok(group),
223 _ => Err(GroupParseError::UnexpectedAddressKind),
224 }
225 }
226}
227
228impl TryFrom<&str> for Group {
229 type Error = GroupParseError;
230
231 fn try_from(value: &str) -> Result<Self, Self::Error> {
241 Self::from_str(value)
242 }
243}
244
245impl Display for Group {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 write_quoted(self.name(), f)?;
252 f.write_str(":")?;
253 for (idx, member) in self.members().iter().enumerate() {
254 if idx > 0 {
255 f.write_str(", ")?;
256 }
257 member.fmt(f)?;
258 }
259 f.write_str(";")
260 }
261}
262
263#[cfg(feature = "serde")]
264impl serde::Serialize for Group {
265 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
266 where
267 S: serde::Serializer,
268 {
269 serializer.serialize_str(&self.to_string())
270 }
271}
272
273#[cfg(feature = "serde")]
274impl<'de> serde::Deserialize<'de> for Group {
275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276 where
277 D: serde::Deserializer<'de>,
278 {
279 let value = String::deserialize(deserializer)?;
280 value.parse().map_err(serde::de::Error::custom)
281 }
282}
283
284#[cfg(feature = "arbitrary")]
285impl<'a> arbitrary::Arbitrary<'a> for Group {
286 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
287 let member_count = usize::from(u.int_in_range::<u8>(0..=3)?);
288 let mut members = Vec::with_capacity(member_count);
289 for _ in 0..member_count {
290 members.push(Mailbox::arbitrary(u)?);
291 }
292 Ok(Self {
293 name: format!("Group {}", u8::arbitrary(u)?),
294 members,
295 })
296 }
297}
298
299#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
308#[derive(Clone, Debug, PartialEq, Eq, Hash)]
309pub enum Address {
310 Mailbox(Mailbox),
311 Group(Group),
312}
313
314impl From<Mailbox> for Address {
315 fn from(value: Mailbox) -> Self {
316 Self::Mailbox(value)
317 }
318}
319
320impl From<Group> for Address {
321 fn from(value: Group) -> Self {
322 Self::Group(value)
323 }
324}
325
326impl Address {
327 pub fn mailboxes(&self) -> impl Iterator<Item = &Mailbox> {
329 match self {
330 Self::Mailbox(mailbox) => std::slice::from_ref(mailbox).iter(),
331 Self::Group(group) => group.members().iter(),
332 }
333 }
334}
335
336#[derive(Debug, thiserror::Error)]
337#[non_exhaustive]
338pub enum AddressParseError {
339 #[error("expected a single address, found {found} address item(s)")]
340 ExpectedSingleAddress { found: usize },
341 #[error("address parse backend failed")]
342 Backend {
343 #[source]
344 source: AddressBackendError,
345 },
346}
347
348impl FromStr for Address {
349 type Err = AddressParseError;
350
351 fn from_str(s: &str) -> Result<Self, Self::Err> {
352 if let Ok(email) = EmailAddress::from_str(s) {
353 return Ok(Self::Mailbox(Mailbox::from(email)));
354 }
355
356 let addresses =
357 parse_address_items(s).map_err(|source| AddressParseError::Backend { source })?;
358 if addresses.len() != 1 {
359 return Err(AddressParseError::ExpectedSingleAddress {
360 found: addresses.len(),
361 });
362 }
363
364 addresses
365 .into_iter()
366 .next()
367 .ok_or(AddressParseError::ExpectedSingleAddress { found: 0 })
368 }
369}
370
371impl TryFrom<&str> for Address {
372 type Error = AddressParseError;
373
374 fn try_from(value: &str) -> Result<Self, Self::Error> {
383 Self::from_str(value)
384 }
385}
386
387impl Display for Address {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 match self {
392 Self::Mailbox(mailbox) => mailbox.fmt(f),
393 Self::Group(group) => group.fmt(f),
394 }
395 }
396}
397
398#[cfg(feature = "serde")]
399impl serde::Serialize for Address {
400 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
401 where
402 S: serde::Serializer,
403 {
404 serializer.serialize_str(&self.to_string())
405 }
406}
407
408#[cfg(feature = "serde")]
409impl<'de> serde::Deserialize<'de> for Address {
410 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
411 where
412 D: serde::Deserializer<'de>,
413 {
414 let value = String::deserialize(deserializer)?;
415 value.parse().map_err(serde::de::Error::custom)
416 }
417}
418
419#[cfg(feature = "arbitrary")]
420impl<'a> arbitrary::Arbitrary<'a> for Address {
421 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
422 if bool::arbitrary(u)? {
423 Ok(Self::Mailbox(Mailbox::arbitrary(u)?))
424 } else {
425 Ok(Self::Group(Group::arbitrary(u)?))
426 }
427 }
428}
429
430macro_rules! impl_address_collection {
431 ($(#[$meta:meta])* $name:ident, $item:ty, $error:ty, $parse_fn:expr) => {
432 $(#[$meta])*
433 #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
434 #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
435 pub struct $name {
436 items: Vec<$item>,
437 }
438
439 impl $name {
440 #[must_use]
441 pub fn len(&self) -> usize {
442 self.items.len()
443 }
444
445 #[must_use]
446 pub fn is_empty(&self) -> bool {
447 self.items.is_empty()
448 }
449
450 pub fn iter(&self) -> std::slice::Iter<'_, $item> {
451 self.items.iter()
452 }
453
454 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, $item> {
455 self.items.iter_mut()
456 }
457
458 #[must_use]
459 pub fn as_slice(&self) -> &[$item] {
460 self.items.as_slice()
461 }
462
463 #[must_use]
464 pub fn into_vec(self) -> Vec<$item> {
465 self.items
466 }
467 }
468
469 impl From<Vec<$item>> for $name {
470 fn from(items: Vec<$item>) -> Self {
471 Self { items }
472 }
473 }
474
475 impl From<$name> for Vec<$item> {
476 fn from(value: $name) -> Self {
477 value.items
478 }
479 }
480
481 impl FromStr for $name {
482 type Err = $error;
483
484 fn from_str(s: &str) -> Result<Self, Self::Err> {
485 let items = ($parse_fn)(s)?;
486 Ok(Self { items })
487 }
488 }
489
490 impl TryFrom<&str> for $name {
491 type Error = $error;
492
493 fn try_from(value: &str) -> Result<Self, Self::Error> {
495 Self::from_str(value)
496 }
497 }
498
499 impl IntoIterator for $name {
500 type Item = $item;
501 type IntoIter = std::vec::IntoIter<$item>;
502
503 fn into_iter(self) -> Self::IntoIter {
504 self.items.into_iter()
505 }
506 }
507
508 impl<'a> IntoIterator for &'a $name {
509 type Item = &'a $item;
510 type IntoIter = std::slice::Iter<'a, $item>;
511
512 fn into_iter(self) -> Self::IntoIter {
513 self.items.iter()
514 }
515 }
516
517 impl<'a> IntoIterator for &'a mut $name {
518 type Item = &'a mut $item;
519 type IntoIter = std::slice::IterMut<'a, $item>;
520
521 fn into_iter(self) -> Self::IntoIter {
522 self.items.iter_mut()
523 }
524 }
525
526 impl AsRef<[$item]> for $name {
527 fn as_ref(&self) -> &[$item] {
528 self.items.as_slice()
529 }
530 }
531
532 impl std::iter::FromIterator<$item> for $name {
533 fn from_iter<T: IntoIterator<Item = $item>>(iter: T) -> Self {
534 Self {
535 items: iter.into_iter().collect(),
536 }
537 }
538 }
539
540 impl Extend<$item> for $name {
541 fn extend<T: IntoIterator<Item = $item>>(&mut self, iter: T) {
542 self.items.extend(iter);
543 }
544 }
545
546 impl Display for $name {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 for (idx, item) in self.items.iter().enumerate() {
549 if idx > 0 {
550 f.write_str(", ")?;
551 }
552 item.fmt(f)?;
553 }
554 Ok(())
555 }
556 }
557
558 #[cfg(feature = "serde")]
559 impl serde::Serialize for $name {
560 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
561 where
562 S: serde::Serializer,
563 {
564 serializer.serialize_str(&self.to_string())
565 }
566 }
567
568 #[cfg(feature = "serde")]
569 impl<'de> serde::Deserialize<'de> for $name {
570 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
571 where
572 D: serde::Deserializer<'de>,
573 {
574 let value = String::deserialize(deserializer)?;
575 value.parse().map_err(serde::de::Error::custom)
576 }
577 }
578
579 #[cfg(feature = "arbitrary")]
580 impl<'a> arbitrary::Arbitrary<'a> for $name {
581 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
582 let len = usize::from(u.int_in_range::<u8>(0..=4)?);
583 let mut items = Vec::with_capacity(len);
584 for _ in 0..len {
585 items.push(<$item>::arbitrary(u)?);
586 }
587 Ok(Self { items })
588 }
589 }
590 };
591}
592
593impl_address_collection!(
594 AddressList,
599 Address,
600 AddressParseError,
601 |s| parse_address_items(s).map_err(|source| AddressParseError::Backend { source })
602);
603
604impl<'a> TryFrom<Vec<&'a str>> for AddressList {
605 type Error = AddressParseError;
606
607 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
608 value
609 .into_iter()
610 .map(Address::from_str)
611 .collect::<Result<Vec<_>, _>>()
612 .map(Self::from)
613 }
614}
615
616impl<'a> TryFrom<&'a [&'a str]> for AddressList {
617 type Error = AddressParseError;
618
619 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
620 value
621 .iter()
622 .copied()
623 .map(Address::from_str)
624 .collect::<Result<Vec<_>, _>>()
625 .map(Self::from)
626 }
627}
628
629impl<'a> TryFrom<Vec<&'a str>> for MailboxList {
630 type Error = MailboxParseError;
631
632 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
633 value
634 .into_iter()
635 .map(Mailbox::from_str)
636 .collect::<Result<Vec<_>, _>>()
637 .map(Self::from)
638 }
639}
640
641impl<'a> TryFrom<&'a [&'a str]> for MailboxList {
642 type Error = MailboxParseError;
643
644 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
645 value
646 .iter()
647 .copied()
648 .map(Mailbox::from_str)
649 .collect::<Result<Vec<_>, _>>()
650 .map(Self::from)
651 }
652}
653
654impl_address_collection!(
655 MailboxList,
659 Mailbox,
660 MailboxParseError,
661 |s| {
662 let addresses = parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
663 let mut items = Vec::with_capacity(addresses.len());
664
665 for address in addresses {
666 match address {
667 Address::Mailbox(mailbox) => items.push(mailbox),
668 Address::Group(_) => return Err(MailboxParseError::ContainsGroupEntry),
669 }
670 }
671
672 Ok(items)
673 }
674);
675
676pub const MAX_ADDRESS_INPUT_BYTES: usize = 64 * 1024;
683
684#[derive(Debug, thiserror::Error)]
685#[non_exhaustive]
686pub enum AddressBackendError {
687 #[error("address input contains raw newline characters")]
688 InputContainsRawNewlines,
689 #[error("address input is {len} bytes, exceeding maximum of {max}")]
690 #[non_exhaustive]
691 InputTooLong { len: usize, max: usize },
692 #[error("failed to parse address header")]
693 HeaderParse,
694 #[error("parsed header did not contain address data")]
695 MissingAddress,
696 #[error("mailbox is missing addr-spec")]
697 MissingAddrSpec,
698 #[error("invalid addr-spec `{input}`")]
699 InvalidAddrSpec {
700 input: String,
701 #[source]
702 source: EmailAddressParseError,
703 },
704 #[error("group member at index {index} is missing addr-spec")]
705 GroupMemberMissingAddrSpec { index: usize },
706 #[error("invalid group member addr-spec `{input}` at index {index}")]
707 InvalidGroupMemberAddrSpec {
708 index: usize,
709 input: String,
710 #[source]
711 source: EmailAddressParseError,
712 },
713 #[error("group is missing a name")]
714 GroupMissingName,
715}
716
717fn write_quoted(value: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 f.write_str("\"")?;
719 for ch in value.chars() {
720 if ch == '\\' || ch == '"' {
721 f.write_str("\\")?;
722 }
723 f.write_str(ch.encode_utf8(&mut [0; 4]))?;
724 }
725 f.write_str("\"")
726}
727
728fn parse_address_items(input: &str) -> Result<Vec<Address>, AddressBackendError> {
753 if input.len() > MAX_ADDRESS_INPUT_BYTES {
754 return Err(AddressBackendError::InputTooLong {
755 len: input.len(),
756 max: MAX_ADDRESS_INPUT_BYTES,
757 });
758 }
759 if input.contains('\r') || input.contains('\n') {
760 return Err(AddressBackendError::InputContainsRawNewlines);
761 }
762
763 let raw = format!("To: {input}\r\n\r\n");
764 let parser =
765 ADDRESS_PARSER.get_or_init(|| mail_parser::MessageParser::new().with_address_headers());
766 let message = parser
767 .parse_headers(raw.as_bytes())
768 .ok_or(AddressBackendError::HeaderParse)?;
769 let parsed = message.to().ok_or(AddressBackendError::MissingAddress)?;
770
771 match parsed {
772 mail_parser::Address::List(list) => list
773 .iter()
774 .map(convert_mailbox)
775 .map(|result| result.map(Address::Mailbox))
776 .collect(),
777 mail_parser::Address::Group(groups) => {
785 let mut items = Vec::with_capacity(groups.len());
786 for group in groups {
787 if group.name.is_some() {
788 items.push(Address::Group(convert_group(group)?));
789 } else {
790 for addr in group.addresses.iter() {
791 items.push(Address::Mailbox(convert_mailbox(addr)?));
792 }
793 }
794 }
795 Ok(items)
796 }
797 }
798}
799
800fn convert_mailbox(value: &mail_parser::Addr<'_>) -> Result<Mailbox, AddressBackendError> {
801 let raw_email = value
802 .address()
803 .ok_or(AddressBackendError::MissingAddrSpec)?;
804 let email = EmailAddress::from_str(raw_email).map_err(|source| {
805 AddressBackendError::InvalidAddrSpec {
806 input: raw_email.to_owned(),
807 source,
808 }
809 })?;
810
811 Ok(match value.name() {
812 Some(name) => Mailbox::from((name.to_owned(), email)),
813 None => Mailbox::from(email),
814 })
815}
816
817fn convert_group(value: &mail_parser::Group<'_>) -> Result<Group, AddressBackendError> {
818 let name = value
819 .name
820 .as_deref()
821 .ok_or(AddressBackendError::GroupMissingName)?
822 .to_owned();
823 let mut members = Vec::with_capacity(value.addresses.len());
824 for (index, member) in value.addresses.iter().enumerate() {
825 let mailbox = convert_mailbox(member).map_err(|error| match error {
826 AddressBackendError::MissingAddrSpec => {
827 AddressBackendError::GroupMemberMissingAddrSpec { index }
828 }
829 AddressBackendError::InvalidAddrSpec { input, source } => {
830 AddressBackendError::InvalidGroupMemberAddrSpec {
831 index,
832 input,
833 source,
834 }
835 }
836 other => other,
837 })?;
838 members.push(mailbox);
839 }
840
841 Ok(Group { name, members })
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847
848 #[test]
849 fn mailbox_from_str_accepts_rfc_examples() {
850 let parsed = "Mary Smith <mary@x.test>".parse::<Mailbox>();
851 assert!(parsed.is_ok(), "expected valid mailbox");
852
853 let parsed = "jdoe@one.test".parse::<Mailbox>();
854 assert!(parsed.is_ok(), "expected valid mailbox");
855 }
856
857 #[test]
858 fn mailbox_from_str_rejects_group() {
859 let parsed = "Undisclosed recipients:;".parse::<Mailbox>();
860 assert!(matches!(
861 parsed,
862 Err(MailboxParseError::UnexpectedAddressKind)
863 ));
864 }
865
866 #[test]
867 fn group_from_str_accepts_rfc_examples() {
868 let parsed =
869 "A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;".parse::<Group>();
870 assert!(parsed.is_ok(), "expected valid group");
871
872 let parsed = "Undisclosed recipients:;".parse::<Group>();
873 assert!(parsed.is_ok(), "expected valid group");
874 }
875
876 #[test]
877 fn address_list_roundtrip() {
878 let list = "Mary Smith <mary@x.test>, jdoe@one.test"
879 .parse::<AddressList>()
880 .expect("address list should parse");
881 let rendered = list.to_string();
882 let reparsed = rendered
883 .parse::<AddressList>()
884 .expect("rendered address list should parse");
885 assert_eq!(reparsed.as_slice(), list.as_slice());
886 }
887
888 #[test]
889 fn mailbox_from_str_rejects_input_with_raw_newline() {
890 let parsed = "Mary Smith <mary@x.test>\nBcc: victim@example.com".parse::<Mailbox>();
891 assert!(matches!(
892 parsed,
893 Err(MailboxParseError::Backend {
894 source: AddressBackendError::InputContainsRawNewlines,
895 })
896 ));
897 }
898}