1use std::{
12 borrow::Cow,
13 fmt::{Debug, Display},
14};
15
16#[cfg(feature = "builder")]
17use mail_builder::{
18 headers::{address, HeaderType},
19 MessageBuilder,
20};
21#[cfg(feature = "parser")]
22use mail_parser::{HeaderName, HeaderValue};
23use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
24
25use crate::SmtpClient;
26
27#[derive(Debug, Default, Clone)]
28pub struct Message<'x> {
29 pub mail_from: Address<'x>,
30 pub rcpt_to: Vec<Address<'x>>,
31 pub body: Cow<'x, [u8]>,
32}
33
34#[derive(Debug, Default, Clone)]
35pub struct Address<'x> {
36 pub email: Cow<'x, str>,
37 pub parameters: Parameters<'x>,
38}
39
40#[derive(Debug, Default, Clone)]
41pub struct Parameters<'x> {
42 params: Vec<Parameter<'x>>,
43}
44
45#[derive(Debug, Default, Clone)]
46pub struct Parameter<'x> {
47 key: Cow<'x, str>,
48 value: Option<Cow<'x, str>>,
49}
50
51impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {
52 pub async fn send<'x>(&mut self, message: impl IntoMessage<'x>) -> crate::Result<()> {
54 let message = message.into_message()?;
56 self.mail_from(
57 message.mail_from.email.as_ref(),
58 &message.mail_from.parameters,
59 )
60 .await?;
61
62 for rcpt in &message.rcpt_to {
64 self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?;
65 }
66
67 self.data(message.body.as_ref()).await
69 }
70
71 #[cfg(feature = "dkim")]
73 pub async fn send_signed<'x, V: mail_auth::common::crypto::SigningKey>(
74 &mut self,
75 message: impl IntoMessage<'x>,
76 signer: &mail_auth::dkim::DkimSigner<V, mail_auth::dkim::Done>,
77 ) -> crate::Result<()> {
78 use mail_auth::common::headers::HeaderWriter;
81 let message = message.into_message()?;
82 self.mail_from(
83 message.mail_from.email.as_ref(),
84 &message.mail_from.parameters,
85 )
86 .await?;
87
88 for rcpt in &message.rcpt_to {
90 self.rcpt_to(rcpt.email.as_ref(), &rcpt.parameters).await?;
91 }
92
93 let signature = signer
95 .sign(message.body.as_ref())
96 .map_err(|_| crate::Error::MissingCredentials)?;
97 let mut signed_message = Vec::with_capacity(message.body.len() + 64);
98 signature.write_header(&mut signed_message);
99 signed_message.extend_from_slice(message.body.as_ref());
100
101 self.data(&signed_message).await
103 }
104
105 pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> {
106 let mut is_cr_or_lf = false;
108
109 let mut last_pos = 0;
116 for (pos, byte) in message.iter().enumerate() {
117 if *byte == b'.' && is_cr_or_lf {
118 if let Some(bytes) = message.get(last_pos..pos) {
119 self.stream.write_all(bytes).await?;
120 self.stream.write_all(b".").await?;
121 last_pos = pos;
122 }
123 is_cr_or_lf = false;
124 } else {
125 is_cr_or_lf = *byte == b'\n' || *byte == b'\r';
126 }
127 }
128 if let Some(bytes) = message.get(last_pos..) {
129 self.stream.write_all(bytes).await?;
130 }
131 self.stream.write_all("\r\n.\r\n".as_bytes()).await?;
132 self.stream.flush().await
133 }
134}
135
136impl<'x> Message<'x> {
137 pub fn new<T, U, V>(from: T, to: U, body: V) -> Self
139 where
140 T: Into<Address<'x>>,
141 U: IntoIterator<Item = T>,
142 V: Into<Cow<'x, [u8]>>,
143 {
144 Message {
145 mail_from: from.into(),
146 rcpt_to: to.into_iter().map(Into::into).collect(),
147 body: body.into(),
148 }
149 }
150
151 pub fn empty() -> Self {
153 Message {
154 mail_from: Address::default(),
155 rcpt_to: Vec::new(),
156 body: Default::default(),
157 }
158 }
159
160 pub fn from(mut self, address: impl Into<Address<'x>>) -> Self {
162 self.mail_from = address.into();
163 self
164 }
165
166 pub fn to(mut self, address: impl Into<Address<'x>>) -> Self {
168 self.rcpt_to.push(address.into());
169 self
170 }
171
172 pub fn body(mut self, body: impl Into<Cow<'x, [u8]>>) -> Self {
174 self.body = body.into();
175 self
176 }
177}
178
179impl<'x> From<&'x str> for Address<'x> {
180 fn from(email: &'x str) -> Self {
181 Address {
182 email: email.into(),
183 parameters: Parameters::default(),
184 }
185 }
186}
187
188impl From<String> for Address<'_> {
189 fn from(email: String) -> Self {
190 Address {
191 email: email.into(),
192 parameters: Parameters::default(),
193 }
194 }
195}
196
197impl<'x> Address<'x> {
198 pub fn new(email: impl Into<Cow<'x, str>>, parameters: Parameters<'x>) -> Self {
199 Address {
200 email: email.into(),
201 parameters,
202 }
203 }
204}
205
206impl<'x> Parameters<'x> {
207 pub fn new() -> Self {
208 Self { params: Vec::new() }
209 }
210
211 pub fn add(&mut self, param: impl Into<Parameter<'x>>) -> &mut Self {
212 self.params.push(param.into());
213 self
214 }
215}
216
217impl<'x> From<&'x str> for Parameter<'x> {
218 fn from(value: &'x str) -> Self {
219 Parameter {
220 key: value.into(),
221 value: None,
222 }
223 }
224}
225
226impl<'x> From<(&'x str, &'x str)> for Parameter<'x> {
227 fn from(value: (&'x str, &'x str)) -> Self {
228 Parameter {
229 key: value.0.into(),
230 value: Some(value.1.into()),
231 }
232 }
233}
234
235impl From<(String, String)> for Parameter<'_> {
236 fn from(value: (String, String)) -> Self {
237 Parameter {
238 key: value.0.into(),
239 value: Some(value.1.into()),
240 }
241 }
242}
243
244impl From<String> for Parameter<'_> {
245 fn from(value: String) -> Self {
246 Parameter {
247 key: value.into(),
248 value: None,
249 }
250 }
251}
252
253impl Display for Parameters<'_> {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 if !self.params.is_empty() {
256 for param in &self.params {
257 f.write_str(" ")?;
258 Display::fmt(¶m, f)?;
259 }
260 }
261 Ok(())
262 }
263}
264
265impl Display for Parameter<'_> {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 if let Some(value) = &self.value {
268 write!(f, "{}={}", self.key, value)
269 } else {
270 f.write_str(&self.key)
271 }
272 }
273}
274
275pub trait IntoMessage<'x> {
276 fn into_message(self) -> crate::Result<Message<'x>>;
277}
278
279impl<'x> IntoMessage<'x> for Message<'x> {
280 fn into_message(self) -> crate::Result<Message<'x>> {
281 Ok(self)
282 }
283}
284
285#[cfg(feature = "builder")]
286impl<'x> IntoMessage<'x> for MessageBuilder<'_> {
287 fn into_message(self) -> crate::Result<Message<'x>> {
288 let mut mail_from = None;
289 let mut rcpt_to = std::collections::HashSet::new();
290
291 for (key, value) in self.headers.iter() {
292 if key.eq_ignore_ascii_case("from") {
293 if let HeaderType::Address(address::Address::Address(addr)) = value {
294 let email = addr.email.trim();
295 if !email.is_empty() {
296 mail_from = email.to_string().into();
297 }
298 }
299 } else if key.eq_ignore_ascii_case("to")
300 || key.eq_ignore_ascii_case("cc")
301 || key.eq_ignore_ascii_case("bcc")
302 {
303 if let HeaderType::Address(addr) = value {
304 match addr {
305 address::Address::Address(addr) => {
306 let email = addr.email.trim();
307 if !email.is_empty() {
308 rcpt_to.insert(email.to_string());
309 }
310 }
311 address::Address::Group(group) => {
312 for addr in &group.addresses {
313 if let address::Address::Address(addr) = addr {
314 let email = addr.email.trim();
315 if !email.is_empty() {
316 rcpt_to.insert(email.to_string());
317 }
318 }
319 }
320 }
321 address::Address::List(list) => {
322 for addr in list {
323 if let address::Address::Address(addr) = addr {
324 let email = addr.email.trim();
325 if !email.is_empty() {
326 rcpt_to.insert(email.to_string());
327 }
328 }
329 }
330 }
331 }
332 }
333 }
334 }
335
336 if rcpt_to.is_empty() {
337 return Err(crate::Error::MissingRcptTo);
338 }
339
340 Ok(Message {
341 mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
342 rcpt_to: rcpt_to
343 .into_iter()
344 .map(|email| Address {
345 email: email.into(),
346 parameters: Parameters::default(),
347 })
348 .collect(),
349 body: self.write_to_vec()?.into(),
350 })
351 }
352}
353
354#[cfg(feature = "parser")]
355impl<'x> IntoMessage<'x> for mail_parser::Message<'x> {
356 fn into_message(self) -> crate::Result<Message<'x>> {
357 let mut mail_from = None;
358 let mut rcpt_to = std::collections::HashSet::new();
359
360 let find_address = |addr: &mail_parser::Addr| -> Option<String> {
361 addr.address
362 .as_ref()
363 .filter(|address| !address.trim().is_empty())
364 .map(|address| address.trim().to_string())
365 };
366
367 for header in self.headers() {
368 match &header.name {
369 HeaderName::From => match header.value() {
370 HeaderValue::Address(mail_parser::Address::List(addrs)) => {
371 if let Some(email) = addrs.iter().find_map(find_address) {
372 mail_from = email.to_string().into();
373 }
374 }
375 HeaderValue::Address(mail_parser::Address::Group(groups)) => {
376 if let Some(grps) = groups.first() {
377 if let Some(email) = grps.addresses.iter().find_map(find_address) {
378 mail_from = email.to_string().into();
379 }
380 }
381 }
382 _ => (),
383 },
384 HeaderName::To | HeaderName::Cc | HeaderName::Bcc => match header.value() {
385 HeaderValue::Address(mail_parser::Address::List(addrs)) => {
386 rcpt_to.extend(addrs.iter().filter_map(find_address));
387 }
388 HeaderValue::Address(mail_parser::Address::Group(grps)) => {
389 rcpt_to.extend(
390 grps.iter()
391 .flat_map(|grp| grp.addresses.iter())
392 .filter_map(find_address),
393 );
394 }
395 _ => (),
396 },
397 _ => (),
398 };
399 }
400
401 if rcpt_to.is_empty() {
402 return Err(crate::Error::MissingRcptTo);
403 }
404
405 Ok(Message {
406 mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
407 rcpt_to: rcpt_to
408 .into_iter()
409 .map(|email| Address {
410 email: email.into(),
411 parameters: Parameters::default(),
412 })
413 .collect(),
414 body: self.raw_message,
415 })
416 }
417}