mail_builder/headers/
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 std::borrow::Cow;
8
9use crate::encoders::encode::rfc2047_encode;
10
11use super::Header;
12
13/// RFC5322 e-mail address
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
15pub struct EmailAddress<'x> {
16    pub name: Option<Cow<'x, str>>,
17    pub email: Cow<'x, str>,
18}
19
20/// RFC5322 grouped e-mail addresses
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
22pub struct GroupedAddresses<'x> {
23    pub name: Option<Cow<'x, str>>,
24    pub addresses: Vec<Address<'x>>,
25}
26
27/// RFC5322 address
28#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
29pub enum Address<'x> {
30    Address(EmailAddress<'x>),
31    Group(GroupedAddresses<'x>),
32    List(Vec<Address<'x>>),
33}
34
35impl<'x> Address<'x> {
36    /// Create an RFC5322 e-mail address
37    pub fn new_address(
38        name: Option<impl Into<Cow<'x, str>>>,
39        email: impl Into<Cow<'x, str>>,
40    ) -> Self {
41        Address::Address(EmailAddress {
42            name: name.map(|v| v.into()),
43            email: email.into(),
44        })
45    }
46
47    /// Create an RFC5322 grouped e-mail address
48    pub fn new_group(name: Option<impl Into<Cow<'x, str>>>, addresses: Vec<Address<'x>>) -> Self {
49        Address::Group(GroupedAddresses {
50            name: name.map(|v| v.into()),
51            addresses,
52        })
53    }
54
55    /// Create an address list
56    pub fn new_list(items: Vec<Address<'x>>) -> Self {
57        Address::List(items)
58    }
59
60    pub fn unwrap_address(&self) -> &EmailAddress<'x> {
61        match self {
62            Address::Address(address) => address,
63            _ => panic!("Address is not an EmailAddress"),
64        }
65    }
66}
67
68impl<'x> From<(&'x str, &'x str)> for Address<'x> {
69    fn from(value: (&'x str, &'x str)) -> Self {
70        Address::Address(EmailAddress {
71            name: Some(value.0.into()),
72            email: value.1.into(),
73        })
74    }
75}
76
77impl From<(String, String)> for Address<'_> {
78    fn from(value: (String, String)) -> Self {
79        Address::Address(EmailAddress {
80            name: Some(value.0.into()),
81            email: value.1.into(),
82        })
83    }
84}
85
86impl<'x> From<&'x str> for Address<'x> {
87    fn from(value: &'x str) -> Self {
88        Address::Address(EmailAddress {
89            name: None,
90            email: value.into(),
91        })
92    }
93}
94
95impl From<String> for Address<'_> {
96    fn from(value: String) -> Self {
97        Address::Address(EmailAddress {
98            name: None,
99            email: value.into(),
100        })
101    }
102}
103
104impl<'x, T> From<Vec<T>> for Address<'x>
105where
106    T: Into<Address<'x>>,
107{
108    fn from(value: Vec<T>) -> Self {
109        Address::new_list(value.into_iter().map(|x| x.into()).collect())
110    }
111}
112
113impl<'x, T, U> From<(U, Vec<T>)> for Address<'x>
114where
115    T: Into<Address<'x>>,
116    U: Into<Cow<'x, str>>,
117{
118    fn from(value: (U, Vec<T>)) -> Self {
119        Address::Group(GroupedAddresses {
120            name: Some(value.0.into()),
121            addresses: value.1.into_iter().map(|x| x.into()).collect(),
122        })
123    }
124}
125
126impl Header for Address<'_> {
127    fn write_header(
128        &self,
129        mut output: impl std::io::Write,
130        mut bytes_written: usize,
131    ) -> std::io::Result<usize> {
132        match self {
133            Address::Address(address) => {
134                address.write_header(&mut output, bytes_written)?;
135            }
136            Address::Group(group) => {
137                group.write_header(&mut output, bytes_written)?;
138            }
139            Address::List(list) => {
140                for (pos, address) in list.iter().enumerate() {
141                    if bytes_written
142                        + (match address {
143                            Address::Address(address) => {
144                                address.email.len()
145                                    + address.name.as_ref().map_or(0, |n| n.len() + 3)
146                                    + 2
147                            }
148                            Address::Group(group) => {
149                                group.name.as_ref().map_or(0, |name| name.len() + 2)
150                            }
151                            Address::List(_) => 0,
152                        })
153                        >= 76
154                    {
155                        output.write_all(b"\r\n\t")?;
156                        bytes_written = 1;
157                    }
158
159                    match address {
160                        Address::Address(address) => {
161                            bytes_written += address.write_header(&mut output, bytes_written)?;
162                            if pos < list.len() - 1 {
163                                output.write_all(b", ")?;
164                                bytes_written += 1;
165                            }
166                        }
167                        Address::Group(group) => {
168                            bytes_written += group.write_header(&mut output, bytes_written)?;
169                            if pos < list.len() - 1 {
170                                output.write_all(b" ")?;
171                                bytes_written += 1;
172                            }
173                        }
174                        Address::List(_) => unreachable!(),
175                    }
176                }
177            }
178        }
179        output.write_all(b"\r\n")?;
180        Ok(0)
181    }
182}
183
184impl Header for EmailAddress<'_> {
185    fn write_header(
186        &self,
187        mut output: impl std::io::Write,
188        mut bytes_written: usize,
189    ) -> std::io::Result<usize> {
190        if let Some(name) = &self.name {
191            bytes_written += rfc2047_encode(name, &mut output)?;
192            if bytes_written + self.email.len() + 2 >= 76 {
193                output.write_all(b"\r\n\t")?;
194                bytes_written = 1;
195            } else {
196                output.write_all(b" ")?;
197                bytes_written += 1;
198            }
199        }
200
201        output.write_all(b"<")?;
202        output.write_all(self.email.as_bytes())?;
203        output.write_all(b">")?;
204
205        Ok(bytes_written + self.email.len() + 2)
206    }
207}
208
209impl Header for GroupedAddresses<'_> {
210    fn write_header(
211        &self,
212        mut output: impl std::io::Write,
213        mut bytes_written: usize,
214    ) -> std::io::Result<usize> {
215        if let Some(name) = &self.name {
216            bytes_written += rfc2047_encode(name, &mut output)? + 2;
217            output.write_all(b": ")?;
218        }
219
220        for (pos, address) in self.addresses.iter().enumerate() {
221            let address = address.unwrap_address();
222
223            if bytes_written
224                + address.email.len()
225                + address.name.as_ref().map_or(0, |n| n.len() + 3)
226                + 2
227                >= 76
228            {
229                output.write_all(b"\r\n\t")?;
230                bytes_written = 1;
231            }
232
233            bytes_written += address.write_header(&mut output, bytes_written)?;
234            if pos < self.addresses.len() - 1 {
235                output.write_all(b", ")?;
236                bytes_written += 2;
237            }
238        }
239
240        output.write_all(b";")?;
241        bytes_written += 1;
242
243        Ok(bytes_written)
244    }
245}