io_email/message/smtp/
send.rs1use alloc::{
20 borrow::Cow,
21 string::{String, ToString},
22 vec::Vec,
23};
24
25use io_smtp::{
26 coroutine::{SmtpCoroutine, SmtpCoroutineState, SmtpYield},
27 message::{SmtpMessageSend as InnerSend, SmtpMessageSendError as InnerErr},
28 rfc5321::types::{
29 domain::Domain, ehlo_domain::EhloDomain, forward_path::ForwardPath, local_part::LocalPart,
30 mailbox::Mailbox as SmtpMailbox, reverse_path::ReversePath,
31 },
32};
33use log::trace;
34use mail_parser::{Address as MailParserAddress, MessageParser};
35use thiserror::Error;
36
37#[derive(Debug, Error)]
39pub enum SmtpMessageSendError {
40 #[error(transparent)]
41 Send(#[from] InnerErr),
42 #[error("could not parse raw RFC 5322 message")]
43 Parse,
44 #[error("no `From:` header found in raw message and no SMTP override set")]
45 MissingReversePath,
46 #[error("no `To:` / `Cc:` / `Bcc:` recipients found in raw message")]
47 MissingForwardPaths,
48 #[error("invalid email address `{0}` in envelope")]
49 InvalidAddress(String),
50}
51
52pub struct SmtpMessageSend {
55 inner: InnerSend,
56}
57
58impl SmtpMessageSend {
59 pub fn new(
65 raw: Vec<u8>,
66 override_reverse_path: Option<&str>,
67 ) -> Result<Self, SmtpMessageSendError> {
68 trace!("prepare SMTP message send");
69
70 let parsed = MessageParser::default()
71 .parse_headers(&raw)
72 .ok_or(SmtpMessageSendError::Parse)?;
73
74 let reverse = match override_reverse_path {
75 Some(addr) => parse_smtp_mailbox(addr)?,
76 None => parsed
77 .from()
78 .and_then(first_address)
79 .ok_or(SmtpMessageSendError::MissingReversePath)
80 .and_then(|addr| parse_smtp_mailbox(&addr))?,
81 };
82
83 let mut forwards: Vec<SmtpMailbox<'static>> = Vec::new();
84 for addrs in [parsed.to(), parsed.cc(), parsed.bcc()]
85 .into_iter()
86 .flatten()
87 {
88 for addr in addresses(addrs) {
89 forwards.push(parse_smtp_mailbox(&addr)?);
90 }
91 }
92 if forwards.is_empty() {
93 return Err(SmtpMessageSendError::MissingForwardPaths);
94 }
95
96 let reverse_path = ReversePath::Mailbox(reverse);
97 let forward_paths = forwards.into_iter().map(ForwardPath::from);
98 Ok(Self {
99 inner: InnerSend::new(reverse_path, forward_paths, raw),
100 })
101 }
102}
103
104fn addresses(addrs: &MailParserAddress<'_>) -> Vec<String> {
107 addrs
108 .clone()
109 .into_list()
110 .into_iter()
111 .filter_map(|a| {
112 let email = a.address?.into_owned();
113 if email.is_empty() { None } else { Some(email) }
114 })
115 .collect()
116}
117
118fn first_address(addrs: &MailParserAddress<'_>) -> Option<String> {
120 addresses(addrs).into_iter().next()
121}
122
123impl SmtpCoroutine for SmtpMessageSend {
124 type Yield = SmtpYield;
125 type Return = Result<(), SmtpMessageSendError>;
126
127 fn resume(&mut self, bytes: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
128 match self.inner.resume(bytes) {
129 SmtpCoroutineState::Yielded(y) => SmtpCoroutineState::Yielded(y),
130 SmtpCoroutineState::Complete(r) => SmtpCoroutineState::Complete(r.map_err(Into::into)),
131 }
132 }
133}
134
135fn parse_smtp_mailbox(addr: &str) -> Result<SmtpMailbox<'static>, SmtpMessageSendError> {
137 let (local, domain) = addr
138 .rsplit_once('@')
139 .ok_or_else(|| SmtpMessageSendError::InvalidAddress(addr.to_string()))?;
140 if local.is_empty() || domain.is_empty() {
141 return Err(SmtpMessageSendError::InvalidAddress(addr.to_string()));
142 }
143 Ok(SmtpMailbox {
144 local_part: LocalPart(Cow::Owned(local.to_string())),
145 domain: EhloDomain::Domain(Domain(Cow::Owned(domain.to_string()))),
146 })
147}