eml_codec/imf/
address.rs

1use nom::{
2    branch::alt,
3    bytes::complete::tag,
4    combinator::{into, map, opt},
5    multi::separated_list1,
6    sequence::tuple,
7    IResult,
8};
9
10//use crate::error::IMFError;
11use crate::imf::mailbox::{mailbox, MailboxRef};
12use crate::text::misc_token::{phrase, Phrase};
13use crate::text::whitespace::cfws;
14
15#[derive(Debug, PartialEq)]
16pub struct GroupRef<'a> {
17    pub name: Phrase<'a>,
18    pub participants: Vec<MailboxRef<'a>>,
19}
20
21#[derive(Debug, PartialEq)]
22pub enum AddressRef<'a> {
23    Single(MailboxRef<'a>),
24    Many(GroupRef<'a>),
25}
26impl<'a> From<MailboxRef<'a>> for AddressRef<'a> {
27    fn from(mx: MailboxRef<'a>) -> Self {
28        AddressRef::Single(mx)
29    }
30}
31impl<'a> From<GroupRef<'a>> for AddressRef<'a> {
32    fn from(grp: GroupRef<'a>) -> Self {
33        AddressRef::Many(grp)
34    }
35}
36pub type AddressList<'a> = Vec<AddressRef<'a>>;
37
38/// Address (section 3.4 of RFC5322)
39///
40/// ```abnf
41///    address         =   mailbox / group
42/// ```
43pub fn address(input: &[u8]) -> IResult<&[u8], AddressRef> {
44    alt((into(mailbox), into(group)))(input)
45}
46
47/// Group
48///
49/// ```abnf
50///    group           =   display-name ":" [group-list] ";" [CFWS]
51///    display-name    =   phrase
52/// ```
53pub fn group(input: &[u8]) -> IResult<&[u8], GroupRef> {
54    let (input, (grp_name, _, grp_list, _, _)) =
55        tuple((phrase, tag(":"), opt(group_list), tag(";"), opt(cfws)))(input)?;
56
57    Ok((
58        input,
59        GroupRef {
60            name: grp_name,
61            participants: grp_list.unwrap_or(vec![]),
62        },
63    ))
64}
65
66/// Group list
67///
68/// ```abnf
69///    group-list      =   mailbox-list / CFWS / obs-group-list
70/// ```
71pub fn group_list(input: &[u8]) -> IResult<&[u8], Vec<MailboxRef>> {
72    alt((mailbox_list, mailbox_cfws))(input)
73}
74
75fn mailbox_cfws(input: &[u8]) -> IResult<&[u8], Vec<MailboxRef>> {
76    let (input, _) = cfws(input)?;
77    Ok((input, vec![]))
78}
79
80/// Mailbox list
81///
82/// ```abnf
83///    mailbox-list    =   (mailbox *("," mailbox)) / obs-mbox-list
84/// ```
85pub fn mailbox_list(input: &[u8]) -> IResult<&[u8], Vec<MailboxRef>> {
86    separated_list1(tag(","), mailbox)(input)
87}
88
89/// Address list
90///
91/// ```abnf
92///   address-list    =   (address *("," address)) / obs-addr-list
93/// ```
94pub fn address_list(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> {
95    separated_list1(tag(","), address)(input)
96}
97
98pub fn address_list_cfws(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> {
99    let (input, _) = cfws(input)?;
100    Ok((input, vec![]))
101}
102
103pub fn nullable_address_list(input: &[u8]) -> IResult<&[u8], Vec<AddressRef>> {
104    map(opt(alt((address_list, address_list_cfws))), |v| {
105        v.unwrap_or(vec![])
106    })(input)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::imf::mailbox::{AddrSpec, Domain, LocalPart, LocalPartToken};
113    use crate::text::misc_token::{Phrase, Word};
114
115    #[test]
116    fn test_mailbox_list() {
117        match mailbox_list(
118            r#"Pete(A nice \) chap) <pete(his account)@silly.test(his host)>"#.as_bytes(),
119        ) {
120            Ok((rest, _)) => assert_eq!(&b""[..], rest),
121            _ => panic!(),
122        };
123
124        match mailbox_list(
125            r#"Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>, <boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>"#.as_bytes(),
126        ) {
127            Ok((rest, _)) => assert_eq!(&b""[..], rest),
128            _ => panic!(),
129        };
130    }
131
132    #[test]
133    fn test_address_list() {
134        assert_eq!(
135            address_list(
136                r#"A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;, Mary Smith <mary@x.test>"#.as_bytes()
137            ),
138            Ok((
139                &b""[..],
140                vec![
141                    AddressRef::Many(GroupRef {
142                        name: Phrase(vec![Word::Atom(&b"A"[..]), Word::Atom(&b"Group"[..])]),
143                        participants: vec![
144                            MailboxRef {
145                                name: Some(Phrase(vec![Word::Atom(&b"Ed"[..]), Word::Atom(&b"Jones"[..])])),
146                                addrspec: AddrSpec {
147                                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"c"[..]))]),
148                                    domain: Domain::Atoms(vec![&b"a"[..], &b"test"[..]]),
149                                },
150                            },
151                            MailboxRef {
152                                name: None,
153                                addrspec: AddrSpec {
154                                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"joe"[..]))]),
155                                    domain: Domain::Atoms(vec![&b"where"[..], &b"test"[..]])
156                                },
157                            },
158                            MailboxRef {
159                                name: Some(Phrase(vec![Word::Atom(&b"John"[..])])),
160                                addrspec: AddrSpec {
161                                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"jdoe"[..]))]),
162                                    domain: Domain::Atoms(vec![&b"one"[..], &b"test"[..]])
163                                },
164                            },
165                        ],
166                    }),
167                    AddressRef::Single(MailboxRef {
168                        name: Some(Phrase(vec![Word::Atom(&b"Mary"[..]), Word::Atom(&b"Smith"[..])])),
169                        addrspec: AddrSpec {
170                            local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(&b"mary"[..]))]),
171                            domain: Domain::Atoms(vec![&b"x"[..], &b"test"[..]])
172                        },
173                    }),
174                ]
175            ))
176        );
177    }
178
179    use crate::text::encoding::{EncodedWord, QuotedChunk, QuotedWord};
180    use crate::text::quoted::QuotedString;
181
182    #[test]
183    fn test_strange_groups() {
184        assert_eq!(
185            address_list(
186                br#""Colleagues": "James Smythe" <james@vandelay.com>;, Friends:
187  jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>;"#
188            ),
189            Ok((
190                &b""[..],
191                vec![
192                    AddressRef::Many(GroupRef {
193                        name: Phrase(vec![Word::Quoted(QuotedString(vec![&b"Colleagues"[..]]))]),
194                        participants: vec![MailboxRef {
195                            name: Some(Phrase(vec![Word::Quoted(QuotedString(vec![
196                                &b"James"[..],
197                                &b" "[..],
198                                &b"Smythe"[..]
199                            ]))])),
200                            addrspec: AddrSpec {
201                                local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(
202                                    &b"james"[..]
203                                ))]),
204                                domain: Domain::Atoms(vec![&b"vandelay"[..], &b"com"[..]]),
205                            }
206                        },],
207                    }),
208                    AddressRef::Many(GroupRef {
209                        name: Phrase(vec![Word::Atom(&b"Friends"[..])]),
210                        participants: vec![
211                            MailboxRef {
212                                name: None,
213                                addrspec: AddrSpec {
214                                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(
215                                        &b"jane"[..]
216                                    ))]),
217                                    domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
218                                }
219                            },
220                            MailboxRef {
221                                name: Some(Phrase(vec![Word::Encoded(EncodedWord::Quoted(
222                                    QuotedWord {
223                                        enc: encoding_rs::UTF_8,
224                                        chunks: vec![
225                                            QuotedChunk::Safe(&b"John"[..]),
226                                            QuotedChunk::Space,
227                                            QuotedChunk::Safe(&b"Sm"[..]),
228                                            QuotedChunk::Encoded(vec![0xc3, 0xae]),
229                                            QuotedChunk::Safe(&b"th"[..]),
230                                        ]
231                                    }
232                                ))])),
233                                addrspec: AddrSpec {
234                                    local_part: LocalPart(vec![LocalPartToken::Word(Word::Atom(
235                                        &b"john"[..]
236                                    ))]),
237                                    domain: Domain::Atoms(vec![&b"example"[..], &b"com"[..]]),
238                                }
239                            },
240                        ]
241                    }),
242                ]
243            ))
244        );
245    }
246}