Skip to main content

io_smtp/
send.rs

1//! Base coroutine that every higher-level SMTP coroutine delegates
2//! to: serialises a command, runs the read/write exchange, and feeds
3//! the reply through [`SmtpResponse::is_complete`] / [`SmtpResponse::parse`].
4
5use core::{fmt, marker::PhantomData, mem};
6
7use alloc::{string::String, vec::Vec};
8
9use bounded_static::IntoBoundedStatic;
10use log::{debug, trace};
11use thiserror::Error;
12
13use crate::{
14    coroutine::*,
15    rfc5321::SmtpResponse,
16    utils::{escape_byte_string, parsers::format_rich_errors},
17};
18
19/// Failure causes raised by [`SmtpCommandSend`].
20#[derive(Clone, Debug, Error)]
21pub enum SmtpCommandSendError {
22    /// The stream reached EOF before a complete reply arrived.
23    #[error("Reached unexpected EOF on SMTP stream")]
24    Eof,
25    /// The reply could not be parsed as an SMTP response.
26    #[error("Parse SMTP response error: {0}")]
27    ParseResponse(String),
28}
29
30/// Successful step output emitted on [`SmtpCommandSend`] completion.
31pub struct SmtpCommandSendOk {
32    /// The parsed reply (possibly multi-line).
33    pub response: SmtpResponse<'static>,
34}
35
36enum State {
37    Write,
38    Read,
39    Parse,
40}
41
42impl fmt::Display for State {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::Write => f.write_str("write command"),
46            Self::Read => f.write_str("read response"),
47            Self::Parse => f.write_str("parse response"),
48        }
49    }
50}
51
52/// I/O-free coroutine sending one SMTP command and parsing its
53/// reply. `Cmd: Into<Vec<u8>>` is satisfied by every `Smtp*Command`
54/// struct in this crate.
55pub struct SmtpCommandSend<Cmd> {
56    bytes: Option<Vec<u8>>,
57    state: State,
58    wants_read: bool,
59    buf: Vec<u8>,
60    _cmd: PhantomData<Cmd>,
61}
62
63impl<Cmd: Into<Vec<u8>>> SmtpCommandSend<Cmd> {
64    /// Creates the coroutine, serialising `cmd` upfront.
65    pub fn new(cmd: Cmd) -> Self {
66        Self {
67            bytes: Some(cmd.into()),
68            state: State::Write,
69            wants_read: false,
70            buf: Vec::new(),
71            _cmd: PhantomData,
72        }
73    }
74}
75
76impl<Cmd> SmtpCoroutine for SmtpCommandSend<Cmd> {
77    type Yield = SmtpYield;
78    type Return = Result<SmtpCommandSendOk, SmtpCommandSendError>;
79
80    fn resume(&mut self, mut arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
81        loop {
82            if mem::take(&mut self.wants_read) {
83                return SmtpCoroutineState::Yielded(SmtpYield::WantsRead);
84            }
85
86            match &mut self.state {
87                State::Write => {
88                    let bytes = self.bytes.take().expect("command bytes taken twice");
89                    self.state = State::Read;
90                    debug!("command sent, awaiting response");
91                    return SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes));
92                }
93                State::Read => match arg.take() {
94                    Some(&[]) => {
95                        return SmtpCoroutineState::Complete(Err(SmtpCommandSendError::Eof));
96                    }
97                    Some(data) => {
98                        trace!("read bytes: {}", escape_byte_string(data));
99                        self.buf.extend_from_slice(data);
100
101                        if !SmtpResponse::is_complete(&self.buf) {
102                            self.wants_read = true;
103                            continue;
104                        }
105
106                        self.state = State::Parse;
107                        debug!("response complete, parsing");
108                    }
109                    None => {
110                        self.wants_read = true;
111                    }
112                },
113                State::Parse => {
114                    return match SmtpResponse::parse(&self.buf) {
115                        Ok(response) => {
116                            let response = response.into_static();
117                            let _ = mem::take(&mut self.buf);
118                            debug!("response parsed");
119                            trace!("{response:?}");
120                            SmtpCoroutineState::Complete(Ok(SmtpCommandSendOk { response }))
121                        }
122                        Err(errors) => {
123                            let reason = format_rich_errors(errors);
124                            let err = SmtpCommandSendError::ParseResponse(reason);
125                            SmtpCoroutineState::Complete(Err(err))
126                        }
127                    };
128                }
129            }
130        }
131    }
132}