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