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