1use core::fmt;
41
42use alloc::{
43 string::{String, ToString},
44 vec::Vec,
45};
46
47use bounded_static::IntoBoundedStatic;
48use log::trace;
49use thiserror::Error;
50
51use crate::{
52 coroutine::*,
53 rfc5321::types::{parameter::Parameter, reply_code::ReplyCode, reverse_path::ReversePath},
54 send::*,
55 smtp_try,
56};
57
58pub struct SmtpMailCommand<'a> {
60 pub reverse_path: ReversePath<'a>,
62 pub parameters: Vec<Parameter<'a>>,
64}
65
66impl<'a> From<SmtpMailCommand<'a>> for Vec<u8> {
67 fn from(cmd: SmtpMailCommand<'a>) -> Vec<u8> {
68 let mut buf = String::from("MAIL FROM:");
69 buf.push_str(&cmd.reverse_path.to_string());
70 for p in cmd.parameters {
71 buf.push(' ');
72 buf.push_str(&p.to_string());
73 }
74 buf.push_str("\r\n");
75 buf.into_bytes()
76 }
77}
78
79#[derive(Clone, Debug, Error)]
81pub enum SmtpMailError {
82 #[error("SMTP MAIL FROM failed: rejected {code} {message}")]
83 Rejected { code: u16, message: String },
84 #[error("SMTP MAIL FROM failed: {0}")]
85 Send(#[from] SendSmtpCommandError),
86}
87
88pub struct SmtpMail {
90 state: State,
91}
92
93impl SmtpMail {
94 pub fn new(reverse_path: ReversePath<'_>, parameters: Vec<Parameter<'_>>) -> Self {
98 let cmd = SmtpMailCommand {
99 reverse_path: reverse_path.into_static(),
100 parameters: parameters.into_iter().map(|p| p.into_static()).collect(),
101 };
102
103 Self {
104 state: State::Send(SendSmtpCommand::new(cmd)),
105 }
106 }
107}
108
109impl SmtpCoroutine for SmtpMail {
110 type Yield = SmtpYield;
111 type Return = Result<(), SmtpMailError>;
112
113 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
114 loop {
115 trace!("mail: {}", self.state);
116
117 match &mut self.state {
118 State::Send(send) => {
119 let out = smtp_try!(send, arg);
120
121 if out.response.code == ReplyCode::OK {
122 return SmtpCoroutineState::Complete(Ok(()));
123 }
124
125 let code = out.response.code.code();
126 let message = out.response.text().to_string();
127 return SmtpCoroutineState::Complete(Err(SmtpMailError::Rejected {
128 code,
129 message,
130 }));
131 }
132 }
133 }
134 }
135}
136
137enum State {
138 Send(SendSmtpCommand<SmtpMailCommand<'static>>),
139}
140
141impl fmt::Display for State {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::Send(_) => f.write_str("send mail from"),
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 fn null_path() -> ReversePath<'static> {
154 ReversePath::Null
155 }
156
157 #[test]
158 fn success_returns_ok() {
159 let mut mail = SmtpMail::new(null_path(), Vec::new());
160
161 let bytes = expect_wants_write(&mut mail, None);
162 assert!(bytes.starts_with(b"MAIL FROM:"));
163
164 expect_wants_read(&mut mail);
165 expect_complete_ok(&mut mail, b"250 sender ok\r\n");
166 }
167
168 #[test]
169 fn rejected_returns_rejected_error() {
170 let mut mail = SmtpMail::new(null_path(), Vec::new());
171 let _ = expect_wants_write(&mut mail, None);
172 expect_wants_read(&mut mail);
173
174 let err = expect_complete_err(&mut mail, b"550 mailbox unavailable\r\n");
175 let SmtpMailError::Rejected { code, message } = err else {
176 panic!("expected SmtpMailError::Rejected, got {err:?}");
177 };
178 assert_eq!(code, 550);
179 assert_eq!(message, "mailbox unavailable");
180 }
181
182 #[test]
183 fn eof_returns_eof_error() {
184 let mut mail = SmtpMail::new(null_path(), Vec::new());
185 let _ = expect_wants_write(&mut mail, None);
186 expect_wants_read(&mut mail);
187
188 let err = expect_complete_err(&mut mail, b"");
189 assert!(matches!(
190 err,
191 SmtpMailError::Send(SendSmtpCommandError::Eof)
192 ));
193 }
194
195 fn expect_wants_write(cor: &mut SmtpMail, arg: Option<&[u8]>) -> Vec<u8> {
198 match cor.resume(arg) {
199 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
200 state => panic!("expected WantsWrite, got {state:?}"),
201 }
202 }
203
204 fn expect_wants_read(cor: &mut SmtpMail) {
205 match cor.resume(None) {
206 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
207 state => panic!("expected WantsRead, got {state:?}"),
208 }
209 }
210
211 fn expect_complete_ok(cor: &mut SmtpMail, reply: &[u8]) {
212 match cor.resume(Some(reply)) {
213 SmtpCoroutineState::Complete(Ok(())) => {}
214 state => panic!("expected Complete(Ok), got {state:?}"),
215 }
216 }
217
218 fn expect_complete_err(cor: &mut SmtpMail, reply: &[u8]) -> SmtpMailError {
219 match cor.resume(Some(reply)) {
220 SmtpCoroutineState::Complete(Err(err)) => err,
221 state => panic!("expected Complete(Err), got {state:?}"),
222 }
223 }
224}