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