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 pub fn domain(&self) -> anyhow::Result<&str> {
141 let (_local, domain) = self.single_address_cracked()?;
142 Ok(domain)
143 }
144
145 pub fn user(&self) -> anyhow::Result<&str> {
148 let (user, _domain) = self.single_address_cracked()?;
149 Ok(user)
150 }
151
152 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 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}