Skip to main content

io_smtp/
send.rs

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