Skip to main content

mail_builder/headers/
address.rs

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