1#[cfg(feature = "impl")]
2use config::any_err;
3use mailparsing::{AddrSpec, Address, AddressList, EncodeHeaderValue, Mailbox};
4#[cfg(feature = "impl")]
5use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct HeaderAddressList(Vec<HeaderAddressEntry>);
10
11impl HeaderAddressList {
12 pub fn domain(&self) -> anyhow::Result<&str> {
15 let addr = self.single_address()?;
16 addr.domain()
17 }
18
19 pub fn user(&self) -> anyhow::Result<&str> {
22 let addr = self.single_address()?;
23 addr.user()
24 }
25
26 pub fn name(&self) -> anyhow::Result<Option<&str>> {
29 let addr = self.single_address()?;
30 Ok(addr.name.as_deref())
31 }
32
33 pub fn email(&self) -> anyhow::Result<Option<String>> {
34 let addr = self.single_address()?;
35 Ok(addr.email())
36 }
37
38 pub fn flatten(&self) -> Vec<&HeaderAddress> {
41 let mut res = vec![];
42 for entry in &self.0 {
43 match entry {
44 HeaderAddressEntry::Address(a) => res.push(a),
45 HeaderAddressEntry::Group(group) => {
46 for addr in &group.addresses {
47 res.push(addr);
48 }
49 }
50 }
51 }
52 res
53 }
54
55 pub fn single_address(&self) -> anyhow::Result<&HeaderAddress> {
56 match self.0.len() {
57 0 => anyhow::bail!("no addresses"),
58 1 => match &self.0[0] {
59 HeaderAddressEntry::Address(a) => Ok(a),
60 _ => anyhow::bail!("is not a simple address"),
61 },
62 _ => anyhow::bail!("is not a simple single address"),
63 }
64 }
65}
66
67impl From<AddressList> for HeaderAddressList {
68 fn from(input: AddressList) -> HeaderAddressList {
69 let addresses: Vec<HeaderAddressEntry> = input.0.iter().map(Into::into).collect();
70 HeaderAddressList(addresses)
71 }
72}
73
74#[cfg(feature = "impl")]
75impl UserData for HeaderAddressList {
76 fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
77 fields.add_field_method_get("user", |_, this| {
78 Ok(this.user().map_err(any_err)?.to_string())
79 });
80 fields.add_field_method_get("domain", |_, this| {
81 Ok(this.domain().map_err(any_err)?.to_string())
82 });
83 fields.add_field_method_get("email", |_, this| Ok(this.email().map_err(any_err)?));
84 fields.add_field_method_get("name", |_, this| {
85 Ok(this.name().map_err(any_err)?.map(|s| s.to_string()))
86 });
87 fields.add_field_method_get("list", |_, this| {
88 Ok(this
89 .flatten()
90 .into_iter()
91 .cloned()
92 .collect::<Vec<HeaderAddress>>())
93 });
94 }
95
96 fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
97 methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
98 let json = serde_json::to_string(&this.0).map_err(any_err)?;
99 Ok(json)
100 });
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
105pub enum HeaderAddressEntry {
106 Address(HeaderAddress),
107 Group(AddressGroup),
108}
109
110impl From<&Address> for HeaderAddressEntry {
111 fn from(addr: &Address) -> HeaderAddressEntry {
112 match addr {
113 Address::Mailbox(mbox) => HeaderAddressEntry::Address(mbox.into()),
114 Address::Group { name, entries } => {
115 let addresses = entries.0.iter().map(Into::into).collect();
116 HeaderAddressEntry::Group(AddressGroup {
117 name: if name.is_empty() {
118 None
119 } else {
120 Some(name.clone())
121 },
122 addresses,
123 })
124 }
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
133struct HeaderAddressWire {
134 pub name: Option<String>,
135 pub address: Option<String>,
136}
137
138impl From<HeaderAddress> for HeaderAddressWire {
139 fn from(addr: HeaderAddress) -> Self {
140 let address = addr.email();
141 Self {
142 name: addr.name,
143 address,
144 }
145 }
146}
147
148impl TryFrom<HeaderAddressWire> for HeaderAddress {
149 type Error = anyhow::Error;
150
151 fn try_from(wire: HeaderAddressWire) -> anyhow::Result<Self> {
152 let (user, domain) = match &wire.address {
153 Some(email) => {
154 let parsed = AddrSpec::parse(email)?;
155 (Some(parsed.local_part), Some(parsed.domain))
156 }
157 None => (None, None),
158 };
159 Ok(Self {
160 name: wire.name,
161 user,
162 domain,
163 })
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(try_from = "HeaderAddressWire", into = "HeaderAddressWire")]
169pub struct HeaderAddress {
170 pub name: Option<String>,
171 pub user: Option<String>,
172 pub domain: Option<String>,
173}
174
175impl From<&Mailbox> for HeaderAddress {
176 fn from(mbox: &Mailbox) -> HeaderAddress {
177 Self {
178 name: mbox.name.clone(),
179 user: Some(mbox.address.local_part.clone()),
180 domain: Some(mbox.address.domain.clone()),
181 }
182 }
183}
184
185impl HeaderAddress {
186 pub fn user(&self) -> anyhow::Result<&str> {
187 self.user
188 .as_deref()
189 .ok_or_else(|| anyhow::anyhow!("no address"))
190 }
191 pub fn domain(&self) -> anyhow::Result<&str> {
192 self.domain
193 .as_deref()
194 .ok_or_else(|| anyhow::anyhow!("no address"))
195 }
196 pub fn email(&self) -> Option<String> {
197 match (&self.user, &self.domain) {
198 (Some(user), Some(domain)) => {
199 Some(AddrSpec::new(user, domain).encode_value().to_string())
200 }
201 _ => None,
202 }
203 }
204 pub fn name(&self) -> Option<&str> {
205 self.name.as_deref()
206 }
207}
208
209#[cfg(feature = "impl")]
210impl UserData for HeaderAddress {
211 fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
212 fields.add_field_method_get("user", |_, this| {
213 Ok(this.user().map_err(any_err)?.to_string())
214 });
215 fields.add_field_method_get("domain", |_, this| {
216 Ok(this.domain().map_err(any_err)?.to_string())
217 });
218 fields.add_field_method_get("email", |_, this| Ok(this.email()));
219 fields.add_field_method_get("name", |_, this| Ok(this.name().map(|s| s.to_string())));
220 }
221 fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
222 methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
223 let json = serde_json::to_string(&this).map_err(any_err)?;
224 Ok(json)
225 });
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230pub struct AddressGroup {
231 pub name: Option<String>,
232 pub addresses: Vec<HeaderAddress>,
233}
234
235#[cfg(test)]
236mod test {
237 use super::*;
238 use mailparsing::Parser;
239
240 #[test]
242 fn header_address_list_with_at_in_local_part() {
243 let header_value = "\"info@\"@example.com";
245 let addr_list = Parser::parse_address_list_header(header_value.as_bytes())
246 .expect("failed to parse address list");
247
248 let header_addr_list: HeaderAddressList = addr_list.into();
250
251 let user = header_addr_list.user().expect("failed to get user");
253 k9::assert_equal!(user, "info@");
254 let domain = header_addr_list.domain().expect("failed to get domain");
255 k9::assert_equal!(domain, "example.com");
256
257 let addr = header_addr_list
258 .single_address()
259 .expect("expected single address");
260
261 k9::assert_equal!(addr.user().unwrap(), "info@");
263 k9::assert_equal!(addr.domain().unwrap(), "example.com");
264 k9::assert_equal!(addr.email().unwrap(), "\"info@\"@example.com");
266 }
267
268 #[test]
270 fn header_address_serde_roundtrip() {
271 let addr = HeaderAddress {
272 name: Some("Test User".into()),
273 user: Some("test".into()),
274 domain: Some("example.com".into()),
275 };
276
277 let json = serde_json::to_string(&addr).unwrap();
278 k9::assert_equal!(json, r#"{"name":"Test User","address":"test@example.com"}"#);
279
280 let roundtripped: HeaderAddress = serde_json::from_str(&json).unwrap();
281 k9::assert_equal!(roundtripped, addr);
282 }
283
284 #[test]
286 fn header_address_serde_roundtrip_quoted() {
287 let addr = HeaderAddress {
288 name: None,
289 user: Some("info@".into()),
290 domain: Some("example.com".into()),
291 };
292
293 let json = serde_json::to_string(&addr).unwrap();
294 k9::assert_equal!(json, r#"{"name":null,"address":"\"info@\"@example.com"}"#);
295
296 let roundtripped: HeaderAddress = serde_json::from_str(&json).unwrap();
297 k9::assert_equal!(roundtripped, addr);
298 }
299}