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 pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> {
102 let mut is_cr_or_lf = false;
104
105 let mut last_pos = 0;
112 for (pos, byte) in message.iter().enumerate() {
113 if *byte == b'.' && is_cr_or_lf {
114 if let Some(bytes) = message.get(last_pos..pos) {
115 self.stream.write_all(bytes).await?;
116 self.stream.write_all(b".").await?;
117 last_pos = pos;
118 }
119 is_cr_or_lf = false;
120 } else {
121 is_cr_or_lf = *byte == b'\n' || *byte == b'\r';
122 }
123 }
124 if let Some(bytes) = message.get(last_pos..) {
125 self.stream.write_all(bytes).await?;
126 }
127 self.stream.write_all("\r\n.\r\n".as_bytes()).await?;
128 self.stream.flush().await
129 }
130}
131
132impl<'x> Message<'x> {
133 pub fn new<T, U, V>(from: T, to: U, body: V) -> Self
135 where
136 T: Into<Address<'x>>,
137 U: IntoIterator<Item = T>,
138 V: Into<Cow<'x, [u8]>>,
139 {
140 Message {
141 mail_from: from.into(),
142 rcpt_to: to.into_iter().map(Into::into).collect(),
143 body: body.into(),
144 }
145 }
146
147 pub fn empty() -> Self {
149 Message {
150 mail_from: Address::default(),
151 rcpt_to: Vec::new(),
152 body: Default::default(),
153 }
154 }
155
156 pub fn from(mut self, address: impl Into<Address<'x>>) -> Self {
158 self.mail_from = address.into();
159 self
160 }
161
162 pub fn to(mut self, address: impl Into<Address<'x>>) -> Self {
164 self.rcpt_to.push(address.into());
165 self
166 }
167
168 pub fn body(mut self, body: impl Into<Cow<'x, [u8]>>) -> Self {
170 self.body = body.into();
171 self
172 }
173}
174
175impl<'x> From<&'x str> for Address<'x> {
176 fn from(email: &'x str) -> Self {
177 Address {
178 email: email.into(),
179 parameters: Parameters::default(),
180 }
181 }
182}
183
184impl From<String> for Address<'_> {
185 fn from(email: String) -> Self {
186 Address {
187 email: email.into(),
188 parameters: Parameters::default(),
189 }
190 }
191}
192
193impl<'x> Address<'x> {
194 pub fn new(email: impl Into<Cow<'x, str>>, parameters: Parameters<'x>) -> Self {
195 Address {
196 email: email.into(),
197 parameters,
198 }
199 }
200}
201
202impl<'x> Parameters<'x> {
203 pub fn new() -> Self {
204 Self { params: Vec::new() }
205 }
206
207 pub fn add(&mut self, param: impl Into<Parameter<'x>>) -> &mut Self {
208 self.params.push(param.into());
209 self
210 }
211}
212
213impl<'x> From<&'x str> for Parameter<'x> {
214 fn from(value: &'x str) -> Self {
215 Parameter {
216 key: value.into(),
217 value: None,
218 }
219 }
220}
221
222impl<'x> From<(&'x str, &'x str)> for Parameter<'x> {
223 fn from(value: (&'x str, &'x str)) -> Self {
224 Parameter {
225 key: value.0.into(),
226 value: Some(value.1.into()),
227 }
228 }
229}
230
231impl From<(String, String)> for Parameter<'_> {
232 fn from(value: (String, String)) -> Self {
233 Parameter {
234 key: value.0.into(),
235 value: Some(value.1.into()),
236 }
237 }
238}
239
240impl From<String> for Parameter<'_> {
241 fn from(value: String) -> Self {
242 Parameter {
243 key: value.into(),
244 value: None,
245 }
246 }
247}
248
249impl Display for Parameters<'_> {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 if !self.params.is_empty() {
252 for param in &self.params {
253 f.write_str(" ")?;
254 Display::fmt(¶m, f)?;
255 }
256 }
257 Ok(())
258 }
259}
260
261impl Display for Parameter<'_> {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 if let Some(value) = &self.value {
264 write!(f, "{}={}", self.key, value)
265 } else {
266 f.write_str(&self.key)
267 }
268 }
269}
270
271pub trait IntoMessage<'x> {
272 fn into_message(self) -> crate::Result<Message<'x>>;
273}
274
275impl<'x> IntoMessage<'x> for Message<'x> {
276 fn into_message(self) -> crate::Result<Message<'x>> {
277 Ok(self)
278 }
279}
280
281#[cfg(feature = "builder")]
282impl<'x> IntoMessage<'x> for MessageBuilder<'_> {
283 fn into_message(self) -> crate::Result<Message<'x>> {
284 let mut mail_from = None;
285 let mut rcpt_to = std::collections::HashSet::new();
286
287 for (key, value) in self.headers.iter() {
288 if key.eq_ignore_ascii_case("from") {
289 if let HeaderType::Address(address::Address::Address(addr)) = value {
290 let email = addr.email.trim();
291 if !email.is_empty() {
292 mail_from = email.to_string().into();
293 }
294 }
295 } else if (key.eq_ignore_ascii_case("to")
296 || key.eq_ignore_ascii_case("cc")
297 || key.eq_ignore_ascii_case("bcc"))
298 && let HeaderType::Address(addr) = value
299 {
300 match addr {
301 address::Address::Address(addr) => {
302 let email = addr.email.trim();
303 if !email.is_empty() {
304 rcpt_to.insert(email.to_string());
305 }
306 }
307 address::Address::Group(group) => {
308 for addr in &group.addresses {
309 if let address::Address::Address(addr) = addr {
310 let email = addr.email.trim();
311 if !email.is_empty() {
312 rcpt_to.insert(email.to_string());
313 }
314 }
315 }
316 }
317 address::Address::List(list) => {
318 for addr in list {
319 if let address::Address::Address(addr) = addr {
320 let email = addr.email.trim();
321 if !email.is_empty() {
322 rcpt_to.insert(email.to_string());
323 }
324 }
325 }
326 }
327 }
328 }
329 }
330
331 if rcpt_to.is_empty() {
332 return Err(crate::Error::MissingRcptTo);
333 }
334
335 Ok(Message {
336 mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
337 rcpt_to: rcpt_to
338 .into_iter()
339 .map(|email| Address {
340 email: email.into(),
341 parameters: Parameters::default(),
342 })
343 .collect(),
344 body: self.write_to_vec()?.into(),
345 })
346 }
347}
348
349#[cfg(feature = "parser")]
350impl<'x> IntoMessage<'x> for mail_parser::Message<'x> {
351 fn into_message(self) -> crate::Result<Message<'x>> {
352 let mut mail_from = None;
353 let mut rcpt_to = std::collections::HashSet::new();
354
355 let find_address = |addr: &mail_parser::Addr| -> Option<String> {
356 addr.address
357 .as_ref()
358 .filter(|address| !address.trim().is_empty())
359 .map(|address| address.trim().to_string())
360 };
361
362 for header in self.headers() {
363 match &header.name {
364 HeaderName::From => match header.value() {
365 HeaderValue::Address(mail_parser::Address::List(addrs)) => {
366 if let Some(email) = addrs.iter().find_map(find_address) {
367 mail_from = email.to_string().into();
368 }
369 }
370 HeaderValue::Address(mail_parser::Address::Group(groups)) => {
371 if let Some(grps) = groups.first() {
372 if let Some(email) = grps.addresses.iter().find_map(find_address) {
373 mail_from = email.to_string().into();
374 }
375 }
376 }
377 _ => (),
378 },
379 HeaderName::To | HeaderName::Cc | HeaderName::Bcc => match header.value() {
380 HeaderValue::Address(mail_parser::Address::List(addrs)) => {
381 rcpt_to.extend(addrs.iter().filter_map(find_address));
382 }
383 HeaderValue::Address(mail_parser::Address::Group(grps)) => {
384 rcpt_to.extend(
385 grps.iter()
386 .flat_map(|grp| grp.addresses.iter())
387 .filter_map(find_address),
388 );
389 }
390 _ => (),
391 },
392 _ => (),
393 };
394 }
395
396 if rcpt_to.is_empty() {
397 return Err(crate::Error::MissingRcptTo);
398 }
399
400 Ok(Message {
401 mail_from: mail_from.ok_or(crate::Error::MissingMailFrom)?.into(),
402 rcpt_to: rcpt_to
403 .into_iter()
404 .map(|email| Address {
405 email: email.into(),
406 parameters: Parameters::default(),
407 })
408 .collect(),
409 body: self.raw_message,
410 })
411 }
412}