1use config::any_err;
2use dns_resolver::DomainClassification;
3use mailparsing::{Address, AddressList, EncodeHeaderValue, Mailbox};
4#[cfg(feature = "impl")]
5use mlua::{MetaMethod, UserData, UserDataFields, UserDataMethods};
6use rfc5321::{ForwardPath, ReversePath};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
10#[serde(transparent)]
11pub struct EnvelopeAddress(String);
12
13impl EnvelopeAddress {
14 pub fn parse(text: &str) -> anyhow::Result<Self> {
15 if text.is_empty() {
16 Ok(Self::null_sender())
17 } else {
18 let fields: Vec<&str> = text.split('@').collect();
19 anyhow::ensure!(fields.len() == 2, "expected user@domain");
20 let domain = &fields[1];
22 Self::validate_domain(domain)?;
23 Ok(Self(text.to_string()))
24 }
25 }
26
27 fn validate_domain(domain: &str) -> anyhow::Result<()> {
28 let classified = DomainClassification::classify(domain)?;
34 if classified.has_port() {
35 anyhow::bail!("EnvelopeAddress does not allow port numbers in the domain ({domain})");
36 }
37
38 Ok(())
39 }
40
41 pub fn user(&self) -> &str {
42 match self.0.find('@') {
43 Some(at) => &self.0[..at],
44 None => "",
45 }
46 }
47
48 pub fn domain(&self) -> &str {
49 match self.0.find('@') {
50 Some(at) => &self.0[at + 1..],
51 None => "",
52 }
53 }
54
55 pub fn null_sender() -> Self {
56 Self("".to_string())
57 }
58
59 pub fn to_string(&self) -> String {
60 self.0.to_string()
61 }
62}
63
64impl TryInto<EnvelopeAddress> for &Mailbox {
65 type Error = anyhow::Error;
66 fn try_into(self) -> anyhow::Result<EnvelopeAddress> {
67 EnvelopeAddress::parse(&self.address.encode_value())
68 }
69}
70
71impl TryInto<EnvelopeAddress> for &Address {
72 type Error = anyhow::Error;
73 fn try_into(self) -> anyhow::Result<EnvelopeAddress> {
74 EnvelopeAddress::parse(&self.encode_value())
75 }
76}
77
78impl TryInto<ForwardPath> for EnvelopeAddress {
79 type Error = String;
80 fn try_into(self) -> Result<ForwardPath, Self::Error> {
81 ForwardPath::try_from(self.0.as_str())
82 }
83}
84
85impl TryInto<ReversePath> for EnvelopeAddress {
86 type Error = String;
87 fn try_into(self) -> Result<ReversePath, Self::Error> {
88 ReversePath::try_from(self.0.as_str())
89 }
90}
91
92#[cfg(feature = "impl")]
93impl UserData for EnvelopeAddress {
94 fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
95 fields.add_field_method_get("user", |_, this| Ok(this.user().to_string()));
96 fields.add_field_method_get("domain", |_, this| Ok(this.domain().to_string()));
97 fields.add_field_method_get("email", |_, this| Ok(this.0.to_string()));
98 }
99
100 fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
101 methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
102 Ok(this.0.to_string())
103 });
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct HeaderAddressList(Vec<HeaderAddressEntry>);
109
110impl HeaderAddressList {
111 pub fn domain(&self) -> anyhow::Result<&str> {
114 let (_local, domain) = self.single_address_cracked()?;
115 Ok(domain)
116 }
117
118 pub fn user(&self) -> anyhow::Result<&str> {
121 let (user, _domain) = self.single_address_cracked()?;
122 Ok(user)
123 }
124
125 pub fn name(&self) -> anyhow::Result<Option<&str>> {
128 let addr = self.single_address()?;
129 Ok(addr.name.as_deref())
130 }
131
132 pub fn email(&self) -> anyhow::Result<Option<&str>> {
133 let addr = self.single_address()?;
134 Ok(addr.address.as_deref())
135 }
136
137 pub fn flatten(&self) -> Vec<&HeaderAddress> {
140 let mut res = vec![];
141 for entry in &self.0 {
142 match entry {
143 HeaderAddressEntry::Address(a) => res.push(a),
144 HeaderAddressEntry::Group(group) => {
145 for addr in &group.addresses {
146 res.push(addr);
147 }
148 }
149 }
150 }
151 res
152 }
153
154 pub fn single_address_cracked(&self) -> anyhow::Result<(&str, &str)> {
155 let addr = self.single_address_string()?;
156 let tuple = addr
157 .split_once('@')
158 .ok_or_else(|| anyhow::anyhow!("no @ in address"))?;
159 Ok(tuple)
160 }
161
162 pub fn single_address_string(&self) -> anyhow::Result<&str> {
163 let addr = self.single_address()?;
164 match &addr.address {
165 None => anyhow::bail!("no address"),
166 Some(addr) => Ok(addr),
167 }
168 }
169
170 pub fn single_address(&self) -> anyhow::Result<&HeaderAddress> {
171 match self.0.len() {
172 0 => anyhow::bail!("no addresses"),
173 1 => match &self.0[0] {
174 HeaderAddressEntry::Address(a) => Ok(a),
175 _ => anyhow::bail!("is not a simple address"),
176 },
177 _ => anyhow::bail!("is not a simple single address"),
178 }
179 }
180}
181
182impl From<AddressList> for HeaderAddressList {
183 fn from(input: AddressList) -> HeaderAddressList {
184 let addresses: Vec<HeaderAddressEntry> = input.0.iter().map(Into::into).collect();
185 HeaderAddressList(addresses)
186 }
187}
188
189#[cfg(feature = "impl")]
190impl UserData for HeaderAddressList {
191 fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
192 fields.add_field_method_get("user", |_, this| {
193 Ok(this.user().map_err(any_err)?.to_string())
194 });
195 fields.add_field_method_get("domain", |_, this| {
196 Ok(this.domain().map_err(any_err)?.to_string())
197 });
198 fields.add_field_method_get("email", |_, this| {
199 Ok(this.email().map_err(any_err)?.map(|s| s.to_string()))
200 });
201 fields.add_field_method_get("name", |_, this| {
202 Ok(this.name().map_err(any_err)?.map(|s| s.to_string()))
203 });
204 fields.add_field_method_get("list", |_, this| {
205 Ok(this
206 .flatten()
207 .into_iter()
208 .cloned()
209 .collect::<Vec<HeaderAddress>>())
210 });
211 }
212
213 fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
214 methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
215 let json = serde_json::to_string(&this.0).map_err(any_err)?;
216 Ok(json)
217 });
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub enum HeaderAddressEntry {
223 Address(HeaderAddress),
224 Group(AddressGroup),
225}
226
227impl From<&Address> for HeaderAddressEntry {
228 fn from(addr: &Address) -> HeaderAddressEntry {
229 match addr {
230 Address::Mailbox(mbox) => HeaderAddressEntry::Address(mbox.into()),
231 Address::Group { name, entries } => {
232 let addresses = entries.0.iter().map(Into::into).collect();
233 HeaderAddressEntry::Group(AddressGroup {
234 name: if name.is_empty() {
235 None
236 } else {
237 Some(name.to_string())
238 },
239 addresses,
240 })
241 }
242 }
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub struct HeaderAddress {
248 pub name: Option<String>,
249 pub address: Option<String>,
250}
251
252impl From<&Mailbox> for HeaderAddress {
253 fn from(mbox: &Mailbox) -> HeaderAddress {
254 Self {
255 name: mbox.name.clone(),
256 address: Some(mbox.address.encode_value().to_string()),
257 }
258 }
259}
260
261impl HeaderAddress {
262 pub fn user(&self) -> anyhow::Result<&str> {
263 let (user, _domain) = self.crack_address().map_err(any_err)?;
264 Ok(user)
265 }
266 pub fn domain(&self) -> anyhow::Result<&str> {
267 let (_user, domain) = self.crack_address().map_err(any_err)?;
268 Ok(domain)
269 }
270 pub fn email(&self) -> Option<&str> {
271 self.address.as_deref()
272 }
273 pub fn name(&self) -> Option<&str> {
274 self.name.as_deref()
275 }
276
277 pub fn crack_address(&self) -> anyhow::Result<(&str, &str)> {
278 let address = self
279 .address
280 .as_ref()
281 .ok_or_else(|| anyhow::anyhow!("no address"))?;
282
283 address
284 .split_once('@')
285 .ok_or_else(|| anyhow::anyhow!("no @ in address"))
286 }
287}
288
289#[cfg(feature = "impl")]
290impl UserData for HeaderAddress {
291 fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
292 fields.add_field_method_get("user", |_, this| {
293 Ok(this.user().map_err(any_err)?.to_string())
294 });
295 fields.add_field_method_get("domain", |_, this| {
296 Ok(this.domain().map_err(any_err)?.to_string())
297 });
298 fields.add_field_method_get("email", |_, this| Ok(this.email().map(|s| s.to_string())));
299 fields.add_field_method_get("name", |_, this| Ok(this.name().map(|s| s.to_string())));
300 }
301 fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
302 methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
303 let json = serde_json::to_string(&this).map_err(any_err)?;
304 Ok(json)
305 });
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
310pub struct AddressGroup {
311 pub name: Option<String>,
312 pub addresses: Vec<HeaderAddress>,
313}
314
315#[cfg(test)]
316mod test {
317 use super::*;
318
319 #[test]
320 fn no_ports_in_domain() {
321 k9::snapshot!(
322 EnvelopeAddress::parse("user@example.com:2025").unwrap_err(),
323 "EnvelopeAddress does not allow port numbers in the domain (example.com:2025)"
324 );
325 }
326}