1use std::fmt::Display;
8use std::str::FromStr;
9use std::sync::OnceLock;
10
11use crate::email::{EmailAddress, EmailAddressParseError};
12
13static ADDRESS_PARSER: OnceLock<mail_parser::MessageParser> = OnceLock::new();
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17pub struct Mailbox {
18 name: Option<String>,
19 email: EmailAddress,
20}
21
22impl Mailbox {
23 #[must_use]
25 pub fn name(&self) -> Option<&str> {
26 self.name.as_deref()
27 }
28
29 #[must_use]
31 pub const fn email(&self) -> &EmailAddress {
32 &self.email
33 }
34}
35
36impl From<EmailAddress> for Mailbox {
37 fn from(email: EmailAddress) -> Self {
38 Self { name: None, email }
39 }
40}
41
42impl From<(String, EmailAddress)> for Mailbox {
43 fn from((name, email): (String, EmailAddress)) -> Self {
44 Self {
45 name: Some(name),
46 email,
47 }
48 }
49}
50
51impl From<(Option<String>, EmailAddress)> for Mailbox {
52 fn from((name, email): (Option<String>, EmailAddress)) -> Self {
53 Self { name, email }
54 }
55}
56
57#[derive(Debug, thiserror::Error)]
59#[non_exhaustive]
60pub enum MailboxParseError {
61 #[error("expected a single mailbox, found {found} address item(s)")]
63 ExpectedSingleMailbox {
64 found: usize,
66 },
67 #[error("expected mailbox but found group")]
69 UnexpectedAddressKind,
70 #[error("mailbox list contains group entries")]
72 ContainsGroupEntry,
73 #[error("mailbox parse backend failed")]
75 Backend {
76 #[source]
78 source: AddressBackendError,
79 },
80}
81
82impl FromStr for Mailbox {
83 type Err = MailboxParseError;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 if let Ok(email) = EmailAddress::from_str(s) {
87 return Ok(Self::from(email));
88 }
89
90 let addresses =
91 parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
92 if addresses.len() != 1 {
93 return Err(MailboxParseError::ExpectedSingleMailbox {
94 found: addresses.len(),
95 });
96 }
97
98 match addresses.into_iter().next() {
99 Some(Address::Mailbox(mailbox)) => Ok(mailbox),
100 _ => Err(MailboxParseError::UnexpectedAddressKind),
101 }
102 }
103}
104
105impl TryFrom<&str> for Mailbox {
106 type Error = MailboxParseError;
107
108 fn try_from(value: &str) -> Result<Self, Self::Error> {
126 Self::from_str(value)
127 }
128}
129
130impl Display for Mailbox {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 match self.name() {
147 Some(name) => {
148 write_quoted(name, f)?;
149 f.write_str(" <")?;
150 self.email.fmt(f)?;
151 f.write_str(">")
152 }
153 None => self.email.fmt(f),
154 }
155 }
156}
157
158#[cfg(feature = "serde")]
159#[derive(serde::Deserialize, PartialEq, Eq)]
160#[serde(rename_all = "snake_case")]
161enum AddressKind {
162 Mailbox,
163 Group,
164}
165
166#[cfg(feature = "schemars")]
167fn mailbox_typed_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
168 let email = generator.subschema_for::<EmailAddress>();
169 schemars::json_schema!({
170 "type": "object",
171 "properties": {
172 "type": {"const": "mailbox"},
173 "name": {"type": ["string", "null"]},
174 "email": email
175 },
176 "required": ["type", "email"]
177 })
178}
179
180#[cfg(feature = "serde")]
181impl serde::Serialize for Mailbox {
182 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183 where
184 S: serde::Serializer,
185 {
186 use serde::ser::SerializeStruct as _;
187
188 let len = 2 + usize::from(self.name.is_some());
189 let mut value = serializer.serialize_struct("Mailbox", len)?;
190 value.serialize_field("type", "mailbox")?;
191 if let Some(name) = &self.name {
192 value.serialize_field("name", name)?;
193 }
194 value.serialize_field("email", &self.email)?;
195 value.end()
196 }
197}
198
199#[cfg(feature = "serde")]
200impl<'de> serde::Deserialize<'de> for Mailbox {
201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
202 where
203 D: serde::Deserializer<'de>,
204 {
205 #[derive(serde::Deserialize)]
206 struct RawMailbox {
207 #[serde(rename = "type")]
208 type_: AddressKind,
209 #[serde(default)]
210 name: Option<String>,
211 email: EmailAddress,
212 }
213
214 fn from_raw<E>(raw: RawMailbox) -> Result<Mailbox, E>
215 where
216 E: serde::de::Error,
217 {
218 if raw.type_ != AddressKind::Mailbox {
219 return Err(E::custom("expected mailbox address type"));
220 }
221 Ok(Mailbox {
222 name: raw.name,
223 email: raw.email,
224 })
225 }
226
227 #[cfg(feature = "rfc5322-string-compat")]
228 {
229 struct MailboxVisitor;
234
235 impl<'de> serde::de::Visitor<'de> for MailboxVisitor {
236 type Value = Mailbox;
237
238 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 f.write_str("a typed mailbox object or an RFC 5322 mailbox string")
240 }
241
242 fn visit_str<E>(self, value: &str) -> Result<Mailbox, E>
243 where
244 E: serde::de::Error,
245 {
246 value.parse().map_err(E::custom)
247 }
248
249 fn visit_string<E>(self, value: String) -> Result<Mailbox, E>
250 where
251 E: serde::de::Error,
252 {
253 value.parse().map_err(E::custom)
254 }
255
256 fn visit_map<A>(self, map: A) -> Result<Mailbox, A::Error>
257 where
258 A: serde::de::MapAccess<'de>,
259 {
260 let raw = <RawMailbox as serde::Deserialize<'de>>::deserialize(
261 serde::de::value::MapAccessDeserializer::new(map),
262 )?;
263 from_raw(raw)
264 }
265 }
266
267 deserializer.deserialize_any(MailboxVisitor)
268 }
269
270 #[cfg(not(feature = "rfc5322-string-compat"))]
271 from_raw(RawMailbox::deserialize(deserializer)?)
272 }
273}
274
275#[cfg(feature = "schemars")]
276impl schemars::JsonSchema for Mailbox {
277 fn schema_name() -> std::borrow::Cow<'static, str> {
278 "Mailbox".into()
279 }
280
281 fn schema_id() -> std::borrow::Cow<'static, str> {
282 concat!(module_path!(), "::Mailbox").into()
283 }
284
285 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
286 let typed = mailbox_typed_schema(generator);
287
288 #[cfg(feature = "rfc5322-string-compat")]
289 {
290 schemars::json_schema!({
291 "oneOf": [
292 typed,
293 {
294 "type": "string",
295 "description": "RFC 5322 mailbox string"
296 }
297 ]
298 })
299 }
300
301 #[cfg(not(feature = "rfc5322-string-compat"))]
302 typed
303 }
304}
305
306#[cfg(feature = "arbitrary")]
307impl<'a> arbitrary::Arbitrary<'a> for Mailbox {
308 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
309 let email = EmailAddress::arbitrary(u)?;
310 if bool::arbitrary(u)? {
311 let name = format!("User {}", u8::arbitrary(u)?);
312 Ok(Self {
313 name: Some(name),
314 email,
315 })
316 } else {
317 Ok(Self { name: None, email })
318 }
319 }
320}
321
322#[derive(Clone, Debug, PartialEq, Eq, Hash)]
324pub struct Group {
325 name: String,
326 members: Vec<Mailbox>,
327}
328
329impl Group {
330 #[must_use]
332 pub fn name(&self) -> &str {
333 self.name.as_str()
334 }
335
336 #[must_use]
338 pub fn members(&self) -> &[Mailbox] {
339 self.members.as_slice()
340 }
341}
342
343#[derive(Debug, thiserror::Error)]
345#[non_exhaustive]
346pub enum GroupParseError {
347 #[error("expected a single group, found {found} address item(s)")]
349 ExpectedSingleGroup {
350 found: usize,
352 },
353 #[error("expected group but found mailbox")]
355 UnexpectedAddressKind,
356 #[error("group parse backend failed")]
358 Backend {
359 #[source]
361 source: AddressBackendError,
362 },
363}
364
365impl FromStr for Group {
366 type Err = GroupParseError;
367
368 fn from_str(s: &str) -> Result<Self, Self::Err> {
369 let addresses =
370 parse_address_items(s).map_err(|source| GroupParseError::Backend { source })?;
371 if addresses.len() != 1 {
372 return Err(GroupParseError::ExpectedSingleGroup {
373 found: addresses.len(),
374 });
375 }
376
377 match addresses.into_iter().next() {
378 Some(Address::Group(group)) => Ok(group),
379 _ => Err(GroupParseError::UnexpectedAddressKind),
380 }
381 }
382}
383
384impl TryFrom<&str> for Group {
385 type Error = GroupParseError;
386
387 fn try_from(value: &str) -> Result<Self, Self::Error> {
404 Self::from_str(value)
405 }
406}
407
408impl Display for Group {
413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414 write_quoted(self.name(), f)?;
415 f.write_str(":")?;
416 for (idx, member) in self.members().iter().enumerate() {
417 if idx > 0 {
418 f.write_str(", ")?;
419 }
420 member.fmt(f)?;
421 }
422 f.write_str(";")
423 }
424}
425
426#[cfg(feature = "schemars")]
427fn group_typed_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
428 let members = <Vec<Mailbox> as schemars::JsonSchema>::json_schema(generator);
429 schemars::json_schema!({
430 "type": "object",
431 "properties": {
432 "type": {"const": "group"},
433 "name": {"type": "string"},
434 "members": members
435 },
436 "required": ["type", "name"]
437 })
438}
439
440#[cfg(feature = "serde")]
441impl serde::Serialize for Group {
442 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
443 where
444 S: serde::Serializer,
445 {
446 use serde::ser::SerializeStruct as _;
447
448 let len = 2 + usize::from(!self.members.is_empty());
449 let mut value = serializer.serialize_struct("Group", len)?;
450 value.serialize_field("type", "group")?;
451 value.serialize_field("name", &self.name)?;
452 if !self.members.is_empty() {
453 value.serialize_field("members", &self.members)?;
454 }
455 value.end()
456 }
457}
458
459#[cfg(feature = "serde")]
460impl<'de> serde::Deserialize<'de> for Group {
461 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
462 where
463 D: serde::Deserializer<'de>,
464 {
465 #[derive(serde::Deserialize)]
466 struct RawGroup {
467 #[serde(rename = "type")]
468 type_: AddressKind,
469 name: String,
470 #[serde(default)]
471 members: Vec<Mailbox>,
472 }
473
474 fn from_raw<E>(raw: RawGroup) -> Result<Group, E>
475 where
476 E: serde::de::Error,
477 {
478 if raw.type_ != AddressKind::Group {
479 return Err(E::custom("expected group address type"));
480 }
481 Ok(Group {
482 name: raw.name,
483 members: raw.members,
484 })
485 }
486
487 #[cfg(feature = "rfc5322-string-compat")]
488 {
489 struct GroupVisitor;
490
491 impl<'de> serde::de::Visitor<'de> for GroupVisitor {
492 type Value = Group;
493
494 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495 f.write_str("a typed group object or an RFC 5322 group string")
496 }
497
498 fn visit_str<E>(self, value: &str) -> Result<Group, E>
499 where
500 E: serde::de::Error,
501 {
502 value.parse().map_err(E::custom)
503 }
504
505 fn visit_string<E>(self, value: String) -> Result<Group, E>
506 where
507 E: serde::de::Error,
508 {
509 value.parse().map_err(E::custom)
510 }
511
512 fn visit_map<A>(self, map: A) -> Result<Group, A::Error>
513 where
514 A: serde::de::MapAccess<'de>,
515 {
516 let raw = <RawGroup as serde::Deserialize<'de>>::deserialize(
517 serde::de::value::MapAccessDeserializer::new(map),
518 )?;
519 from_raw(raw)
520 }
521 }
522
523 deserializer.deserialize_any(GroupVisitor)
524 }
525
526 #[cfg(not(feature = "rfc5322-string-compat"))]
527 from_raw(RawGroup::deserialize(deserializer)?)
528 }
529}
530
531#[cfg(feature = "schemars")]
532impl schemars::JsonSchema for Group {
533 fn schema_name() -> std::borrow::Cow<'static, str> {
534 "Group".into()
535 }
536
537 fn schema_id() -> std::borrow::Cow<'static, str> {
538 concat!(module_path!(), "::Group").into()
539 }
540
541 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
542 let typed = group_typed_schema(generator);
543
544 #[cfg(feature = "rfc5322-string-compat")]
545 {
546 schemars::json_schema!({
547 "oneOf": [
548 typed,
549 {
550 "type": "string",
551 "description": "RFC 5322 group string"
552 }
553 ]
554 })
555 }
556
557 #[cfg(not(feature = "rfc5322-string-compat"))]
558 typed
559 }
560}
561
562#[cfg(feature = "arbitrary")]
563impl<'a> arbitrary::Arbitrary<'a> for Group {
564 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
565 let member_count = usize::from(u.int_in_range::<u8>(0..=3)?);
566 let mut members = Vec::with_capacity(member_count);
567 for _ in 0..member_count {
568 members.push(Mailbox::arbitrary(u)?);
569 }
570 Ok(Self {
571 name: format!("Group {}", u8::arbitrary(u)?),
572 members,
573 })
574 }
575}
576
577#[derive(Clone, Debug, PartialEq, Eq, Hash)]
586pub enum Address {
587 Mailbox(Mailbox),
589 Group(Group),
591}
592
593impl From<Mailbox> for Address {
594 fn from(value: Mailbox) -> Self {
595 Self::Mailbox(value)
596 }
597}
598
599impl From<Group> for Address {
600 fn from(value: Group) -> Self {
601 Self::Group(value)
602 }
603}
604
605impl Address {
606 pub fn mailboxes(&self) -> impl Iterator<Item = &Mailbox> {
608 match self {
609 Self::Mailbox(mailbox) => std::slice::from_ref(mailbox).iter(),
610 Self::Group(group) => group.members().iter(),
611 }
612 }
613}
614
615#[derive(Debug, thiserror::Error)]
617#[non_exhaustive]
618pub enum AddressParseError {
619 #[error("expected a single address, found {found} address item(s)")]
621 ExpectedSingleAddress {
622 found: usize,
624 },
625 #[error("address parse backend failed")]
627 Backend {
628 #[source]
630 source: AddressBackendError,
631 },
632}
633
634impl FromStr for Address {
635 type Err = AddressParseError;
636
637 fn from_str(s: &str) -> Result<Self, Self::Err> {
638 if let Ok(email) = EmailAddress::from_str(s) {
639 return Ok(Self::Mailbox(Mailbox::from(email)));
640 }
641
642 let addresses =
643 parse_address_items(s).map_err(|source| AddressParseError::Backend { source })?;
644 if addresses.len() != 1 {
645 return Err(AddressParseError::ExpectedSingleAddress {
646 found: addresses.len(),
647 });
648 }
649
650 addresses
651 .into_iter()
652 .next()
653 .ok_or(AddressParseError::ExpectedSingleAddress { found: 0 })
654 }
655}
656
657impl TryFrom<&str> for Address {
658 type Error = AddressParseError;
659
660 fn try_from(value: &str) -> Result<Self, Self::Error> {
677 Self::from_str(value)
678 }
679}
680
681impl Display for Address {
684 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685 match self {
686 Self::Mailbox(mailbox) => mailbox.fmt(f),
687 Self::Group(group) => group.fmt(f),
688 }
689 }
690}
691
692#[cfg(feature = "serde")]
693impl serde::Serialize for Address {
694 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
695 where
696 S: serde::Serializer,
697 {
698 match self {
699 Self::Mailbox(mailbox) => serde::Serialize::serialize(mailbox, serializer),
700 Self::Group(group) => serde::Serialize::serialize(group, serializer),
701 }
702 }
703}
704
705#[cfg(feature = "serde")]
706impl<'de> serde::Deserialize<'de> for Address {
707 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
708 where
709 D: serde::Deserializer<'de>,
710 {
711 #[derive(serde::Deserialize)]
712 #[serde(tag = "type", rename_all = "snake_case")]
713 enum RawAddress {
714 Mailbox {
715 #[serde(default)]
716 name: Option<String>,
717 email: EmailAddress,
718 },
719 Group {
720 name: String,
721 #[serde(default)]
722 members: Vec<Mailbox>,
723 },
724 }
725
726 fn from_raw(raw: RawAddress) -> Address {
727 match raw {
728 RawAddress::Mailbox { name, email } => Address::Mailbox(Mailbox { name, email }),
729 RawAddress::Group { name, members } => Address::Group(Group { name, members }),
730 }
731 }
732
733 #[cfg(feature = "rfc5322-string-compat")]
734 {
735 struct AddressVisitor;
736
737 impl<'de> serde::de::Visitor<'de> for AddressVisitor {
738 type Value = Address;
739
740 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
741 f.write_str("a typed address object or an RFC 5322 address string")
742 }
743
744 fn visit_str<E>(self, value: &str) -> Result<Address, E>
745 where
746 E: serde::de::Error,
747 {
748 value.parse().map_err(E::custom)
749 }
750
751 fn visit_string<E>(self, value: String) -> Result<Address, E>
752 where
753 E: serde::de::Error,
754 {
755 value.parse().map_err(E::custom)
756 }
757
758 fn visit_map<A>(self, map: A) -> Result<Address, A::Error>
759 where
760 A: serde::de::MapAccess<'de>,
761 {
762 let raw = <RawAddress as serde::Deserialize<'de>>::deserialize(
763 serde::de::value::MapAccessDeserializer::new(map),
764 )?;
765 Ok(from_raw(raw))
766 }
767 }
768
769 deserializer.deserialize_any(AddressVisitor)
770 }
771
772 #[cfg(not(feature = "rfc5322-string-compat"))]
773 Ok(from_raw(RawAddress::deserialize(deserializer)?))
774 }
775}
776
777#[cfg(feature = "schemars")]
778impl schemars::JsonSchema for Address {
779 fn schema_name() -> std::borrow::Cow<'static, str> {
780 "Address".into()
781 }
782
783 fn schema_id() -> std::borrow::Cow<'static, str> {
784 concat!(module_path!(), "::Address").into()
785 }
786
787 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
788 let mailbox = mailbox_typed_schema(generator);
789 let group = group_typed_schema(generator);
790
791 #[cfg(feature = "rfc5322-string-compat")]
792 {
793 schemars::json_schema!({
794 "oneOf": [
795 mailbox,
796 group,
797 {
798 "type": "string",
799 "description": "RFC 5322 address string"
800 }
801 ]
802 })
803 }
804
805 #[cfg(not(feature = "rfc5322-string-compat"))]
806 schemars::json_schema!({"oneOf": [mailbox, group]})
807 }
808}
809
810#[cfg(feature = "arbitrary")]
811impl<'a> arbitrary::Arbitrary<'a> for Address {
812 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
813 if bool::arbitrary(u)? {
814 Ok(Self::Mailbox(Mailbox::arbitrary(u)?))
815 } else {
816 Ok(Self::Group(Group::arbitrary(u)?))
817 }
818 }
819}
820
821macro_rules! impl_address_collection {
822 ($(#[$meta:meta])* $name:ident, $item:ty, $error:ty, $parse_fn:expr) => {
823 $(#[$meta])*
824 #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
825 pub struct $name {
826 items: Vec<$item>,
827 }
828
829 #[cfg(feature = "schemars")]
830 impl schemars::JsonSchema for $name {
831 fn inline_schema() -> bool {
832 true
833 }
834
835 fn schema_name() -> std::borrow::Cow<'static, str> {
836 stringify!($name).into()
837 }
838
839 fn schema_id() -> std::borrow::Cow<'static, str> {
840 concat!(module_path!(), "::", stringify!($name)).into()
841 }
842
843 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
844 let typed = <Vec<$item> as schemars::JsonSchema>::json_schema(generator);
845
846 #[cfg(feature = "rfc5322-string-compat")]
847 {
848 schemars::json_schema!({
849 "oneOf": [
850 typed,
851 {
852 "type": "string",
853 "description": "RFC 5322 comma-separated address list string"
854 }
855 ]
856 })
857 }
858
859 #[cfg(not(feature = "rfc5322-string-compat"))]
860 typed
861 }
862 }
863
864 impl $name {
865 #[must_use]
867 pub fn len(&self) -> usize {
868 self.items.len()
869 }
870
871 #[must_use]
873 pub fn is_empty(&self) -> bool {
874 self.items.is_empty()
875 }
876
877 pub fn iter(&self) -> std::slice::Iter<'_, $item> {
879 self.items.iter()
880 }
881
882 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, $item> {
884 self.items.iter_mut()
885 }
886
887 #[must_use]
889 pub fn as_slice(&self) -> &[$item] {
890 self.items.as_slice()
891 }
892
893 #[must_use]
895 pub fn into_vec(self) -> Vec<$item> {
896 self.items
897 }
898 }
899
900 impl From<Vec<$item>> for $name {
901 fn from(items: Vec<$item>) -> Self {
902 Self { items }
903 }
904 }
905
906 impl From<$name> for Vec<$item> {
907 fn from(value: $name) -> Self {
908 value.items
909 }
910 }
911
912 impl FromStr for $name {
913 type Err = $error;
914
915 fn from_str(s: &str) -> Result<Self, Self::Err> {
916 let items = ($parse_fn)(s)?;
917 Ok(Self { items })
918 }
919 }
920
921 impl TryFrom<&str> for $name {
922 type Error = $error;
923
924 fn try_from(value: &str) -> Result<Self, Self::Error> {
926 Self::from_str(value)
927 }
928 }
929
930 impl IntoIterator for $name {
931 type Item = $item;
932 type IntoIter = std::vec::IntoIter<$item>;
933
934 fn into_iter(self) -> Self::IntoIter {
935 self.items.into_iter()
936 }
937 }
938
939 impl<'a> IntoIterator for &'a $name {
940 type Item = &'a $item;
941 type IntoIter = std::slice::Iter<'a, $item>;
942
943 fn into_iter(self) -> Self::IntoIter {
944 self.items.iter()
945 }
946 }
947
948 impl<'a> IntoIterator for &'a mut $name {
949 type Item = &'a mut $item;
950 type IntoIter = std::slice::IterMut<'a, $item>;
951
952 fn into_iter(self) -> Self::IntoIter {
953 self.items.iter_mut()
954 }
955 }
956
957 impl AsRef<[$item]> for $name {
958 fn as_ref(&self) -> &[$item] {
959 self.items.as_slice()
960 }
961 }
962
963 impl std::iter::FromIterator<$item> for $name {
964 fn from_iter<T: IntoIterator<Item = $item>>(iter: T) -> Self {
965 Self {
966 items: iter.into_iter().collect(),
967 }
968 }
969 }
970
971 impl Extend<$item> for $name {
972 fn extend<T: IntoIterator<Item = $item>>(&mut self, iter: T) {
973 self.items.extend(iter);
974 }
975 }
976
977 impl Display for $name {
978 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
979 for (idx, item) in self.items.iter().enumerate() {
980 if idx > 0 {
981 f.write_str(", ")?;
982 }
983 item.fmt(f)?;
984 }
985 Ok(())
986 }
987 }
988
989 #[cfg(feature = "serde")]
990 impl serde::Serialize for $name {
991 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
992 where
993 S: serde::Serializer,
994 {
995 serde::Serialize::serialize(&self.items, serializer)
996 }
997 }
998
999 #[cfg(feature = "serde")]
1000 impl<'de> serde::Deserialize<'de> for $name {
1001 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1002 where
1003 D: serde::Deserializer<'de>,
1004 {
1005 #[cfg(feature = "rfc5322-string-compat")]
1006 {
1007 struct CollectionVisitor;
1008
1009 impl<'de> serde::de::Visitor<'de> for CollectionVisitor {
1010 type Value = $name;
1011
1012 fn expecting(
1013 &self,
1014 f: &mut std::fmt::Formatter<'_>,
1015 ) -> std::fmt::Result {
1016 f.write_str(concat!(
1017 "a ",
1018 stringify!($name),
1019 " array or an RFC 5322 list string",
1020 ))
1021 }
1022
1023 fn visit_str<E>(self, value: &str) -> Result<$name, E>
1024 where
1025 E: serde::de::Error,
1026 {
1027 value.parse().map_err(E::custom)
1028 }
1029
1030 fn visit_string<E>(self, value: String) -> Result<$name, E>
1031 where
1032 E: serde::de::Error,
1033 {
1034 value.parse().map_err(E::custom)
1035 }
1036
1037 fn visit_seq<A>(self, mut seq: A) -> Result<$name, A::Error>
1038 where
1039 A: serde::de::SeqAccess<'de>,
1040 {
1041 let mut items = Vec::with_capacity(seq.size_hint().unwrap_or(0));
1042 while let Some(item) = seq.next_element::<$item>()? {
1043 items.push(item);
1044 }
1045 Ok($name { items })
1046 }
1047 }
1048
1049 deserializer.deserialize_any(CollectionVisitor)
1050 }
1051
1052 #[cfg(not(feature = "rfc5322-string-compat"))]
1053 {
1054 let items = <Vec<$item> as serde::Deserialize>::deserialize(deserializer)?;
1055 Ok(Self { items })
1056 }
1057 }
1058 }
1059
1060 #[cfg(feature = "arbitrary")]
1061 impl<'a> arbitrary::Arbitrary<'a> for $name {
1062 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1063 let len = usize::from(u.int_in_range::<u8>(0..=4)?);
1064 let mut items = Vec::with_capacity(len);
1065 for _ in 0..len {
1066 items.push(<$item>::arbitrary(u)?);
1067 }
1068 Ok(Self { items })
1069 }
1070 }
1071 };
1072}
1073
1074impl_address_collection!(
1075 AddressList,
1080 Address,
1081 AddressParseError,
1082 |s| parse_address_items(s).map_err(|source| AddressParseError::Backend { source })
1083);
1084
1085impl<'a> TryFrom<Vec<&'a str>> for AddressList {
1086 type Error = AddressParseError;
1087
1088 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
1089 value
1090 .into_iter()
1091 .map(Address::from_str)
1092 .collect::<Result<Vec<_>, _>>()
1093 .map(Self::from)
1094 }
1095}
1096
1097impl<'a> TryFrom<&'a [&'a str]> for AddressList {
1098 type Error = AddressParseError;
1099
1100 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
1101 value
1102 .iter()
1103 .copied()
1104 .map(Address::from_str)
1105 .collect::<Result<Vec<_>, _>>()
1106 .map(Self::from)
1107 }
1108}
1109
1110impl<'a> TryFrom<Vec<&'a str>> for MailboxList {
1111 type Error = MailboxParseError;
1112
1113 fn try_from(value: Vec<&'a str>) -> Result<Self, Self::Error> {
1114 value
1115 .into_iter()
1116 .map(Mailbox::from_str)
1117 .collect::<Result<Vec<_>, _>>()
1118 .map(Self::from)
1119 }
1120}
1121
1122impl<'a> TryFrom<&'a [&'a str]> for MailboxList {
1123 type Error = MailboxParseError;
1124
1125 fn try_from(value: &'a [&'a str]) -> Result<Self, Self::Error> {
1126 value
1127 .iter()
1128 .copied()
1129 .map(Mailbox::from_str)
1130 .collect::<Result<Vec<_>, _>>()
1131 .map(Self::from)
1132 }
1133}
1134
1135impl_address_collection!(
1136 MailboxList,
1140 Mailbox,
1141 MailboxParseError,
1142 |s| {
1143 let addresses = parse_address_items(s).map_err(|source| MailboxParseError::Backend { source })?;
1144 let mut items = Vec::with_capacity(addresses.len());
1145
1146 for address in addresses {
1147 match address {
1148 Address::Mailbox(mailbox) => items.push(mailbox),
1149 Address::Group(_) => return Err(MailboxParseError::ContainsGroupEntry),
1150 }
1151 }
1152
1153 Ok(items)
1154 }
1155);
1156
1157pub const MAX_ADDRESS_INPUT_BYTES: usize = 64 * 1024;
1164
1165#[derive(Debug, thiserror::Error)]
1167#[non_exhaustive]
1168pub enum AddressBackendError {
1169 #[error("address input contains raw newline characters")]
1171 InputContainsRawNewlines,
1172 #[error("address input is {len} bytes, exceeding maximum of {max}")]
1174 #[non_exhaustive]
1175 InputTooLong {
1176 len: usize,
1178 max: usize,
1180 },
1181 #[error("failed to parse address header")]
1183 HeaderParse,
1184 #[error("parsed header did not contain address data")]
1186 MissingAddress,
1187 #[error("mailbox is missing addr-spec")]
1189 MissingAddrSpec,
1190 #[error("invalid addr-spec `{input}`")]
1192 InvalidAddrSpec {
1193 input: String,
1195 #[source]
1197 source: EmailAddressParseError,
1198 },
1199 #[error("group member at index {index} is missing addr-spec")]
1201 GroupMemberMissingAddrSpec {
1202 index: usize,
1204 },
1205 #[error("invalid group member addr-spec `{input}` at index {index}")]
1207 InvalidGroupMemberAddrSpec {
1208 index: usize,
1210 input: String,
1212 #[source]
1214 source: EmailAddressParseError,
1215 },
1216 #[error("group is missing a name")]
1218 GroupMissingName,
1219}
1220
1221fn write_quoted(value: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1222 f.write_str("\"")?;
1223 for ch in value.chars() {
1224 if ch == '\\' || ch == '"' {
1225 f.write_str("\\")?;
1226 }
1227 f.write_str(ch.encode_utf8(&mut [0; 4]))?;
1228 }
1229 f.write_str("\"")
1230}
1231
1232fn parse_address_items(input: &str) -> Result<Vec<Address>, AddressBackendError> {
1257 if input.len() > MAX_ADDRESS_INPUT_BYTES {
1258 return Err(AddressBackendError::InputTooLong {
1259 len: input.len(),
1260 max: MAX_ADDRESS_INPUT_BYTES,
1261 });
1262 }
1263 if input.contains('\r') || input.contains('\n') {
1264 return Err(AddressBackendError::InputContainsRawNewlines);
1265 }
1266
1267 let raw = format!("To: {input}\r\n\r\n");
1268 let parser =
1269 ADDRESS_PARSER.get_or_init(|| mail_parser::MessageParser::new().with_address_headers());
1270 let message = parser
1271 .parse_headers(raw.as_bytes())
1272 .ok_or(AddressBackendError::HeaderParse)?;
1273 let parsed = message.to().ok_or(AddressBackendError::MissingAddress)?;
1274
1275 match parsed {
1276 mail_parser::Address::List(list) => list
1277 .iter()
1278 .map(convert_mailbox)
1279 .map(|result| result.map(Address::Mailbox))
1280 .collect(),
1281 mail_parser::Address::Group(groups) => {
1289 let mut items = Vec::with_capacity(groups.len());
1290 for group in groups {
1291 if group.name.is_some() {
1292 items.push(Address::Group(convert_group(group)?));
1293 } else {
1294 for addr in &group.addresses {
1295 items.push(Address::Mailbox(convert_mailbox(addr)?));
1296 }
1297 }
1298 }
1299 Ok(items)
1300 }
1301 }
1302}
1303
1304fn convert_mailbox(value: &mail_parser::Addr<'_>) -> Result<Mailbox, AddressBackendError> {
1305 let raw_email = value
1306 .address()
1307 .ok_or(AddressBackendError::MissingAddrSpec)?;
1308 let email = EmailAddress::from_str(raw_email).map_err(|source| {
1309 AddressBackendError::InvalidAddrSpec {
1310 input: raw_email.to_owned(),
1311 source,
1312 }
1313 })?;
1314
1315 Ok(match value.name() {
1316 Some(name) => Mailbox::from((name.to_owned(), email)),
1317 None => Mailbox::from(email),
1318 })
1319}
1320
1321fn convert_group(value: &mail_parser::Group<'_>) -> Result<Group, AddressBackendError> {
1322 let name = value
1323 .name
1324 .as_deref()
1325 .ok_or(AddressBackendError::GroupMissingName)?
1326 .to_owned();
1327 let mut members = Vec::with_capacity(value.addresses.len());
1328 for (index, member) in value.addresses.iter().enumerate() {
1329 let mailbox = convert_mailbox(member).map_err(|error| match error {
1330 AddressBackendError::MissingAddrSpec => {
1331 AddressBackendError::GroupMemberMissingAddrSpec { index }
1332 }
1333 AddressBackendError::InvalidAddrSpec { input, source } => {
1334 AddressBackendError::InvalidGroupMemberAddrSpec {
1335 index,
1336 input,
1337 source,
1338 }
1339 }
1340 other => other,
1341 })?;
1342 members.push(mailbox);
1343 }
1344
1345 Ok(Group { name, members })
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350 use super::*;
1351
1352 #[test]
1353 fn mailbox_from_str_accepts_rfc_examples() {
1354 let parsed = "Mary Smith <mary@x.test>".parse::<Mailbox>();
1355 assert!(parsed.is_ok(), "expected valid mailbox");
1356
1357 let parsed = "jdoe@one.test".parse::<Mailbox>();
1358 assert!(parsed.is_ok(), "expected valid mailbox");
1359 }
1360
1361 #[test]
1362 fn mailbox_from_str_rejects_group() {
1363 let parsed = "Undisclosed recipients:;".parse::<Mailbox>();
1364 assert!(matches!(
1365 parsed,
1366 Err(MailboxParseError::UnexpectedAddressKind)
1367 ));
1368 }
1369
1370 #[test]
1371 fn group_from_str_accepts_rfc_examples() {
1372 let parsed =
1373 "A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;".parse::<Group>();
1374 assert!(parsed.is_ok(), "expected valid group");
1375
1376 let parsed = "Undisclosed recipients:;".parse::<Group>();
1377 assert!(parsed.is_ok(), "expected valid group");
1378 }
1379
1380 #[test]
1381 fn address_list_roundtrip() {
1382 let list = "Mary Smith <mary@x.test>, jdoe@one.test"
1383 .parse::<AddressList>()
1384 .expect("address list should parse");
1385 let rendered = list.to_string();
1386 let reparsed = rendered
1387 .parse::<AddressList>()
1388 .expect("rendered address list should parse");
1389 assert_eq!(reparsed.as_slice(), list.as_slice());
1390 }
1391
1392 #[test]
1393 fn mailbox_from_str_rejects_input_with_raw_newline() {
1394 let parsed = "Mary Smith <mary@x.test>\nBcc: victim@example.com".parse::<Mailbox>();
1395 assert!(matches!(
1396 parsed,
1397 Err(MailboxParseError::Backend {
1398 source: AddressBackendError::InputContainsRawNewlines,
1399 })
1400 ));
1401 }
1402}