message/
address.rs

1use anyhow::anyhow;
2use bstr::{BStr, BString, ByteSlice};
3use config::any_err;
4use mailparsing::{Address, AddressList, EncodeHeaderValue, Mailbox};
5#[cfg(feature = "impl")]
6use mlua::{FromLua, MetaMethod, UserData, UserDataFields, UserDataMethods};
7use rfc5321::{EnvelopeAddress as EnvelopeAddress5321, ForwardPath, ReversePath};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, PartialEq, Serialize, Deserialize, Eq)]
11#[serde(transparent)]
12pub struct EnvelopeAddress(EnvelopeAddress5321);
13
14impl std::fmt::Debug for EnvelopeAddress {
15    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
16        write!(fmt, "<{}>", self.0.to_string())
17    }
18}
19
20#[cfg(feature = "impl")]
21impl FromLua for EnvelopeAddress {
22    fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
23        match value {
24            mlua::Value::String(s) => s
25                .to_str()?
26                .parse::<EnvelopeAddress5321>()
27                .map_err(any_err)
28                .map(Self),
29            _ => {
30                let ud = mlua::UserDataRef::<EnvelopeAddress>::from_lua(value, lua)?;
31                Ok(ud.clone())
32            }
33        }
34    }
35}
36
37impl EnvelopeAddress {
38    pub fn parse(text: &str) -> anyhow::Result<Self> {
39        let addr = text
40            .parse::<EnvelopeAddress5321>()
41            .map_err(|err| anyhow!("{err}"))?;
42        Ok(Self(addr))
43    }
44
45    pub fn user(&self) -> String {
46        match &self.0 {
47            EnvelopeAddress5321::Postmaster => "postmaster".to_string(),
48            EnvelopeAddress5321::Null => "".to_string(),
49            EnvelopeAddress5321::Path(path) => path.mailbox.local_part().into(),
50        }
51    }
52
53    pub fn domain(&self) -> String {
54        match &self.0 {
55            EnvelopeAddress5321::Postmaster | EnvelopeAddress5321::Null => "".to_string(),
56            EnvelopeAddress5321::Path(path) => path.mailbox.domain.to_string(),
57        }
58    }
59
60    pub fn null_sender() -> Self {
61        Self(EnvelopeAddress5321::Null)
62    }
63
64    pub fn to_string(&self) -> String {
65        self.0.to_string()
66    }
67}
68
69impl TryInto<EnvelopeAddress> for &Mailbox {
70    type Error = anyhow::Error;
71    fn try_into(self) -> anyhow::Result<EnvelopeAddress> {
72        if self.address.local_part.is_empty() && self.address.domain.is_empty() {
73            Ok(EnvelopeAddress::null_sender())
74        } else {
75            EnvelopeAddress::parse(&format!(
76                "{}@{}",
77                self.address.local_part, self.address.domain
78            ))
79        }
80    }
81}
82
83impl TryInto<EnvelopeAddress> for &Address {
84    type Error = anyhow::Error;
85    fn try_into(self) -> anyhow::Result<EnvelopeAddress> {
86        match self {
87            Address::Mailbox(mbox) => mbox.try_into(),
88            Address::Group { name: _, entries } => {
89                if entries.len() == 1 {
90                    (&entries[0]).try_into()
91                } else {
92                    anyhow::bail!("Cannot convert an Address::Group to an EnvelopeAddress unless it has exactly one entry");
93                }
94            }
95        }
96    }
97}
98
99impl TryInto<ForwardPath> for EnvelopeAddress {
100    type Error = String;
101    fn try_into(self) -> Result<ForwardPath, Self::Error> {
102        self.0.try_into()
103    }
104}
105
106impl TryInto<ReversePath> for EnvelopeAddress {
107    type Error = String;
108    fn try_into(self) -> Result<ReversePath, Self::Error> {
109        self.0.try_into()
110    }
111}
112
113impl From<ForwardPath> for EnvelopeAddress {
114    fn from(fp: ForwardPath) -> EnvelopeAddress {
115        EnvelopeAddress(fp.into())
116    }
117}
118
119#[cfg(feature = "impl")]
120impl UserData for EnvelopeAddress {
121    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
122        fields.add_field_method_get("user", |_, this| Ok(this.user().to_string()));
123        fields.add_field_method_get("domain", |_, this| Ok(this.domain().to_string()));
124        fields.add_field_method_get("email", |_, this| Ok(this.0.to_string()));
125    }
126
127    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
128        methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
129            Ok(this.0.to_string())
130        });
131    }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
135pub struct HeaderAddressList(Vec<HeaderAddressEntry>);
136
137impl HeaderAddressList {
138    /// If the address list is comprised of a single entry,
139    /// returns just the email domain from that entry
140    pub fn domain(&self) -> anyhow::Result<&str> {
141        let (_local, domain) = self.single_address_cracked()?;
142        Ok(domain)
143    }
144
145    /// If the address list is comprised of a single entry,
146    /// returns just the email domain from that entry
147    pub fn user(&self) -> anyhow::Result<&str> {
148        let (user, _domain) = self.single_address_cracked()?;
149        Ok(user)
150    }
151
152    /// If the address list is comprised of a single entry,
153    /// returns just the display name portion, if any
154    pub fn name(&self) -> anyhow::Result<Option<&BStr>> {
155        let addr = self.single_address()?;
156        Ok(addr.name.as_ref().map(|b| b.as_bstr()))
157    }
158
159    pub fn email(&self) -> anyhow::Result<Option<&str>> {
160        let addr = self.single_address()?;
161        Ok(addr.address.as_deref())
162    }
163
164    /// Flattens the groups and list and returns a simple list
165    /// of addresses
166    pub fn flatten(&self) -> Vec<&HeaderAddress> {
167        let mut res = vec![];
168        for entry in &self.0 {
169            match entry {
170                HeaderAddressEntry::Address(a) => res.push(a),
171                HeaderAddressEntry::Group(group) => {
172                    for addr in &group.addresses {
173                        res.push(addr);
174                    }
175                }
176            }
177        }
178        res
179    }
180
181    pub fn single_address_cracked(&self) -> anyhow::Result<(&str, &str)> {
182        let addr = self.single_address_string()?;
183        let tuple = addr
184            .split_once('@')
185            .ok_or_else(|| anyhow::anyhow!("no @ in address"))?;
186        Ok(tuple)
187    }
188
189    pub fn single_address_string(&self) -> anyhow::Result<&str> {
190        let addr = self.single_address()?;
191        match &addr.address {
192            None => anyhow::bail!("no address"),
193            Some(addr) => Ok(addr),
194        }
195    }
196
197    pub fn single_address(&self) -> anyhow::Result<&HeaderAddress> {
198        match self.0.len() {
199            0 => anyhow::bail!("no addresses"),
200            1 => match &self.0[0] {
201                HeaderAddressEntry::Address(a) => Ok(a),
202                _ => anyhow::bail!("is not a simple address"),
203            },
204            _ => anyhow::bail!("is not a simple single address"),
205        }
206    }
207}
208
209impl From<AddressList> for HeaderAddressList {
210    fn from(input: AddressList) -> HeaderAddressList {
211        let addresses: Vec<HeaderAddressEntry> = input.0.iter().map(Into::into).collect();
212        HeaderAddressList(addresses)
213    }
214}
215
216#[cfg(feature = "impl")]
217impl UserData for HeaderAddressList {
218    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
219        fields.add_field_method_get("user", |_, this| {
220            Ok(this.user().map_err(any_err)?.to_string())
221        });
222        fields.add_field_method_get("domain", |_, this| {
223            Ok(this.domain().map_err(any_err)?.to_string())
224        });
225        fields.add_field_method_get("email", |_, this| {
226            Ok(this.email().map_err(any_err)?.map(|s| s.to_string()))
227        });
228        fields.add_field_method_get("name", |_, this| {
229            Ok(this.name().map_err(any_err)?.map(|s| s.to_string()))
230        });
231        fields.add_field_method_get("list", |_, this| {
232            Ok(this
233                .flatten()
234                .into_iter()
235                .cloned()
236                .collect::<Vec<HeaderAddress>>())
237        });
238    }
239
240    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
241        methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
242            let json = serde_json::to_string(&this.0).map_err(any_err)?;
243            Ok(json)
244        });
245    }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub enum HeaderAddressEntry {
250    Address(HeaderAddress),
251    Group(AddressGroup),
252}
253
254impl From<&Address> for HeaderAddressEntry {
255    fn from(addr: &Address) -> HeaderAddressEntry {
256        match addr {
257            Address::Mailbox(mbox) => HeaderAddressEntry::Address(mbox.into()),
258            Address::Group { name, entries } => {
259                let addresses = entries.0.iter().map(Into::into).collect();
260                HeaderAddressEntry::Group(AddressGroup {
261                    name: if name.is_empty() {
262                        None
263                    } else {
264                        Some(name.to_string())
265                    },
266                    addresses,
267                })
268            }
269        }
270    }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub struct HeaderAddress {
275    pub name: Option<BString>,
276    pub address: Option<String>,
277}
278
279impl From<&Mailbox> for HeaderAddress {
280    fn from(mbox: &Mailbox) -> HeaderAddress {
281        Self {
282            name: mbox.name.clone(),
283            address: Some(mbox.address.encode_value().to_string()),
284        }
285    }
286}
287
288impl HeaderAddress {
289    pub fn user(&self) -> anyhow::Result<&str> {
290        let (user, _domain) = self.crack_address().map_err(any_err)?;
291        Ok(user)
292    }
293    pub fn domain(&self) -> anyhow::Result<&str> {
294        let (_user, domain) = self.crack_address().map_err(any_err)?;
295        Ok(domain)
296    }
297    pub fn email(&self) -> Option<&str> {
298        self.address.as_deref()
299    }
300    pub fn name(&self) -> Option<&BStr> {
301        self.name.as_ref().map(|b| b.as_bstr())
302    }
303
304    pub fn crack_address(&self) -> anyhow::Result<(&str, &str)> {
305        let address = self
306            .address
307            .as_ref()
308            .ok_or_else(|| anyhow::anyhow!("no address"))?;
309
310        address
311            .split_once('@')
312            .ok_or_else(|| anyhow::anyhow!("no @ in address"))
313    }
314}
315
316#[cfg(feature = "impl")]
317impl UserData for HeaderAddress {
318    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
319        fields.add_field_method_get("user", |_, this| {
320            Ok(this.user().map_err(any_err)?.to_string())
321        });
322        fields.add_field_method_get("domain", |_, this| {
323            Ok(this.domain().map_err(any_err)?.to_string())
324        });
325        fields.add_field_method_get("email", |_, this| Ok(this.email().map(|s| s.to_string())));
326        fields.add_field_method_get("name", |_, this| Ok(this.name().map(|s| s.to_string())));
327    }
328    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
329        methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
330            let json = serde_json::to_string(&this).map_err(any_err)?;
331            Ok(json)
332        });
333    }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337pub struct AddressGroup {
338    pub name: Option<String>,
339    pub addresses: Vec<HeaderAddress>,
340}
341
342#[cfg(test)]
343mod test {
344    use super::*;
345
346    #[test]
347    fn no_ports_in_domain() {
348        k9::snapshot!(
349            EnvelopeAddress::parse("user@example.com:2025").unwrap_err(),
350            "
351 --> 1:17
352  |
3531 | user@example.com:2025
354  |                 ^---
355  |
356  = expected EOI, alpha, digit, or utf8_non_ascii
357"
358        );
359    }
360}