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