Skip to main content

io_smtp/
message.rs

1//! SMTP composite coroutine; chains MAIL FROM, one RCPT TO per
2//! recipient, then DATA.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//!     borrow::Cow,
9//!     io::{Read, Write},
10//!     net::TcpStream,
11//! };
12//!
13//! use io_smtp::{
14//!     coroutine::{SmtpCoroutine, SmtpCoroutineState, SmtpYield},
15//!     message::SmtpMessageSend,
16//!     rfc5321::{
17//!         SmtpDomain, SmtpEhloDomain, SmtpForwardPath,
18//!         SmtpLocalPart, SmtpMailbox, SmtpReversePath,
19//!     },
20//! };
21//!
22//! // Ready stream needed (TCP-connected, TLS-negociated, AUTH consumed)
23//! let mut stream = TcpStream::connect("localhost:25").unwrap();
24//!
25//! let mut buf = [0u8; 4096];
26//!
27//! let alice = SmtpMailbox {
28//!     local_part: SmtpLocalPart(Cow::Borrowed("alice")),
29//!     domain: SmtpEhloDomain::SmtpDomain(SmtpDomain(Cow::Borrowed("example.org"))),
30//! };
31//! let bob = SmtpMailbox {
32//!     local_part: SmtpLocalPart(Cow::Borrowed("bob")),
33//!     domain: SmtpEhloDomain::SmtpDomain(SmtpDomain(Cow::Borrowed("example.org"))),
34//! };
35//! let message =
36//!     b"From: alice@example.org\r\nTo: bob@example.org\r\nSubject: hi\r\n\r\nhello\r\n".to_vec();
37//!
38//! let mut coroutine = SmtpMessageSend::new(
39//!     SmtpReversePath::SmtpMailbox(alice),
40//!     [SmtpForwardPath(bob)],
41//!     message,
42//! );
43//! let mut arg = None;
44//!
45//! loop {
46//!     match coroutine.resume(arg.take()) {
47//!         SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
48//!             stream.write_all(&bytes).unwrap();
49//!         }
50//!         SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
51//!             let n = stream.read(&mut buf).unwrap();
52//!             arg = Some(&buf[..n]);
53//!         }
54//!         SmtpCoroutineState::Complete(Ok(())) => break,
55//!         SmtpCoroutineState::Complete(Err(err)) => panic!("{err}"),
56//!     }
57//! }
58//! ```
59
60use core::fmt;
61
62use alloc::{collections::VecDeque, vec::Vec};
63
64use bounded_static::IntoBoundedStatic;
65use log::debug;
66use thiserror::Error;
67
68use crate::{
69    coroutine::*,
70    rfc5321::{
71        SmtpForwardPath, SmtpReversePath,
72        data::{SmtpData, SmtpDataError},
73        mail::{SmtpMail, SmtpMailError},
74        rcpt::{SmtpRcpt, SmtpRcptError},
75    },
76    smtp_try,
77};
78
79/// Failure causes during the SMTP send composite coroutine.
80#[derive(Debug, Error)]
81pub enum SmtpMessageSendError {
82    /// The MAIL FROM step failed.
83    #[error(transparent)]
84    MailFrom(#[from] SmtpMailError),
85    /// A RCPT TO step failed.
86    #[error(transparent)]
87    RcptTo(#[from] SmtpRcptError),
88    /// The DATA step failed.
89    #[error(transparent)]
90    Data(#[from] SmtpDataError),
91}
92
93/// I/O-free SMTP composite send coroutine.
94pub struct SmtpMessageSend {
95    state: State,
96    forward_paths: VecDeque<SmtpForwardPath<'static>>,
97    message: Option<Vec<u8>>,
98}
99
100impl SmtpMessageSend {
101    /// Creates the coroutine from the sender path, the recipient
102    /// paths and the complete message (headers plus body).
103    pub fn new<'a>(
104        reverse_path: SmtpReversePath<'_>,
105        forward_paths: impl IntoIterator<Item = SmtpForwardPath<'a>>,
106        message: Vec<u8>,
107    ) -> Self {
108        let forward_paths = forward_paths
109            .into_iter()
110            .map(IntoBoundedStatic::into_static)
111            .collect();
112
113        Self {
114            state: State::MailFrom(SmtpMail::new(reverse_path.into_static(), Vec::new())),
115            forward_paths,
116            message: Some(message),
117        }
118    }
119}
120
121impl SmtpCoroutine for SmtpMessageSend {
122    type Yield = SmtpYield;
123    type Return = Result<(), SmtpMessageSendError>;
124
125    fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
126        loop {
127            match &mut self.state {
128                State::MailFrom(mail) => {
129                    let () = smtp_try!(mail, arg);
130                    self.state = self.next_rcpt_or_data();
131                    debug!("mail from accepted, next: {}", self.state);
132                }
133                State::RcptTo(rcpt) => {
134                    let () = smtp_try!(rcpt, arg);
135                    self.state = self.next_rcpt_or_data();
136                    debug!("rcpt to accepted, next: {}", self.state);
137                }
138                State::Data(data) => {
139                    let () = smtp_try!(data, arg);
140                    debug!("message sent");
141                    return SmtpCoroutineState::Complete(Ok(()));
142                }
143            }
144        }
145    }
146}
147
148impl SmtpMessageSend {
149    fn next_rcpt_or_data(&mut self) -> State {
150        match self.forward_paths.pop_front() {
151            Some(path) => State::RcptTo(SmtpRcpt::new(path, Vec::new())),
152            None => {
153                let body = self.message.take().expect("message taken twice");
154                State::Data(SmtpData::new(body))
155            }
156        }
157    }
158}
159
160enum State {
161    MailFrom(SmtpMail),
162    RcptTo(SmtpRcpt),
163    Data(SmtpData),
164}
165
166impl fmt::Display for State {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        match self {
169            Self::MailFrom(_) => f.write_str("mail from"),
170            Self::RcptTo(_) => f.write_str("rcpt to"),
171            Self::Data(_) => f.write_str("data"),
172        }
173    }
174}