1use core::fmt;
42
43use alloc::{
44 string::{String, ToString},
45 vec::Vec,
46};
47
48use log::{debug, trace};
49use thiserror::Error;
50
51use crate::{coroutine::*, rfc5321::SmtpReplyCode, send::*, smtp_try};
52
53pub struct SmtpDataCommand;
55
56impl From<SmtpDataCommand> for Vec<u8> {
57 fn from(_: SmtpDataCommand) -> Vec<u8> {
58 b"DATA\r\n".to_vec()
59 }
60}
61
62pub struct SmtpDataBody(pub Vec<u8>);
65
66impl From<SmtpDataBody> for Vec<u8> {
67 fn from(body: SmtpDataBody) -> Vec<u8> {
68 body.0
69 }
70}
71
72#[derive(Clone, Debug, Error)]
74pub enum SmtpDataError {
75 #[error("SMTP DATA command failed: rejected {code} {message}")]
77 CommandRejected {
78 code: u16,
80 message: String,
82 },
83 #[error("SMTP DATA body failed: rejected {code} {message}")]
85 BodyRejected {
86 code: u16,
88 message: String,
90 },
91 #[error("SMTP DATA failed: {0}")]
93 Send(#[from] SmtpCommandSendError),
94}
95
96pub struct SmtpData {
98 state: State,
99 body: Option<Vec<u8>>,
100}
101
102impl SmtpData {
103 pub fn new(message: Vec<u8>) -> Self {
106 Self {
107 state: State::SendCommand(SmtpCommandSend::new(SmtpDataCommand)),
108 body: Some(message),
109 }
110 }
111
112 fn prepare_body(message: Vec<u8>) -> Vec<u8> {
115 let mut result = Vec::with_capacity(message.len() + 5);
116
117 let mut at_line_start = true;
118 for &byte in &message {
119 if at_line_start && byte == b'.' {
120 result.push(b'.');
121 }
122 result.push(byte);
123 at_line_start = byte == b'\n';
124 }
125
126 if !result.ends_with(b"\r\n") {
127 if result.ends_with(b"\n") {
128 result.pop();
129 result.extend_from_slice(b"\r\n");
130 } else if result.ends_with(b"\r") {
131 result.push(b'\n');
132 } else {
133 result.extend_from_slice(b"\r\n");
134 }
135 }
136
137 result.extend_from_slice(b".\r\n");
138
139 result
140 }
141}
142
143impl SmtpCoroutine for SmtpData {
144 type Yield = SmtpYield;
145 type Return = Result<(), SmtpDataError>;
146
147 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
148 loop {
149 match &mut self.state {
150 State::SendCommand(send) => {
151 let out = smtp_try!(send, arg);
152
153 if out.response.code != SmtpReplyCode::START_MAIL_INPUT {
154 let code = out.response.code.code();
155 let message = out.response.text().to_string();
156 return SmtpCoroutineState::Complete(Err(SmtpDataError::CommandRejected {
157 code,
158 message,
159 }));
160 }
161
162 let body = self.body.take().expect("body taken twice");
163 let prepared = Self::prepare_body(body);
164
165 let len = prepared.len();
166 self.state = State::SendBody(SmtpCommandSend::new(SmtpDataBody(prepared)));
167 debug!("data accepted, sending body");
168 trace!("prepared body: {len} bytes");
169 }
170 State::SendBody(send) => {
171 let out = smtp_try!(send, arg);
172
173 if out.response.code == SmtpReplyCode::OK {
174 debug!("body accepted");
175 return SmtpCoroutineState::Complete(Ok(()));
176 }
177
178 let code = out.response.code.code();
179 let message = out.response.text().to_string();
180 return SmtpCoroutineState::Complete(Err(SmtpDataError::BodyRejected {
181 code,
182 message,
183 }));
184 }
185 }
186 }
187 }
188}
189
190enum State {
191 SendCommand(SmtpCommandSend<SmtpDataCommand>),
192 SendBody(SmtpCommandSend<SmtpDataBody>),
193}
194
195impl fmt::Display for State {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 match self {
198 Self::SendCommand(_) => f.write_str("send data"),
199 Self::SendBody(_) => f.write_str("send body"),
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use alloc::vec::Vec;
207
208 use crate::{coroutine::*, rfc5321::data::*, send::SmtpCommandSendError};
209
210 #[test]
211 fn success_returns_ok() {
212 let mut data = SmtpData::new(b"Subject: hi\r\n\r\nbody\r\n".to_vec());
213
214 let bytes = expect_wants_write(&mut data, None);
215 assert_eq!(bytes, b"DATA\r\n");
216
217 expect_wants_read(&mut data);
218 let body_bytes = expect_wants_write(&mut data, Some(b"354 send body\r\n"));
219 assert!(body_bytes.ends_with(b"\r\n.\r\n"));
220
221 expect_wants_read(&mut data);
222 expect_complete_ok(&mut data, b"250 message accepted\r\n");
223 }
224
225 #[test]
226 fn command_rejected_returns_command_error() {
227 let mut data = SmtpData::new(b"hi\r\n".to_vec());
228 let _ = expect_wants_write(&mut data, None);
229 expect_wants_read(&mut data);
230
231 let err = expect_complete_err(&mut data, b"503 bad sequence\r\n");
232 let SmtpDataError::CommandRejected { code, message } = err else {
233 panic!("expected SmtpDataError::CommandRejected, got {err:?}");
234 };
235 assert_eq!(code, 503);
236 assert_eq!(message, "bad sequence");
237 }
238
239 #[test]
240 fn body_rejected_returns_body_error() {
241 let mut data = SmtpData::new(b"hi\r\n".to_vec());
242 let _ = expect_wants_write(&mut data, None);
243 expect_wants_read(&mut data);
244 let _ = expect_wants_write(&mut data, Some(b"354 send body\r\n"));
245 expect_wants_read(&mut data);
246
247 let err = expect_complete_err(&mut data, b"552 too large\r\n");
248 let SmtpDataError::BodyRejected { code, message } = err else {
249 panic!("expected SmtpDataError::BodyRejected, got {err:?}");
250 };
251 assert_eq!(code, 552);
252 assert_eq!(message, "too large");
253 }
254
255 #[test]
256 fn dot_stuffs_leading_dot_lines() {
257 let body = SmtpData::prepare_body(b".hello\r\n".to_vec());
258 assert_eq!(body, b"..hello\r\n.\r\n");
259 }
260
261 #[test]
262 fn eof_returns_eof_error() {
263 let mut data = SmtpData::new(b"hi\r\n".to_vec());
264 let _ = expect_wants_write(&mut data, None);
265 expect_wants_read(&mut data);
266
267 let err = expect_complete_err(&mut data, b"");
268 assert!(matches!(
269 err,
270 SmtpDataError::Send(SmtpCommandSendError::Eof)
271 ));
272 }
273
274 fn expect_wants_write(cor: &mut SmtpData, arg: Option<&[u8]>) -> Vec<u8> {
275 match cor.resume(arg) {
276 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
277 state => panic!("expected WantsWrite, got {state:?}"),
278 }
279 }
280
281 fn expect_wants_read(cor: &mut SmtpData) {
282 match cor.resume(None) {
283 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
284 state => panic!("expected WantsRead, got {state:?}"),
285 }
286 }
287
288 fn expect_complete_ok(cor: &mut SmtpData, reply: &[u8]) {
289 match cor.resume(Some(reply)) {
290 SmtpCoroutineState::Complete(Ok(())) => {}
291 state => panic!("expected Complete(Ok), got {state:?}"),
292 }
293 }
294
295 fn expect_complete_err(cor: &mut SmtpData, reply: &[u8]) -> SmtpDataError {
296 match cor.resume(Some(reply)) {
297 SmtpCoroutineState::Complete(Err(err)) => err,
298 state => panic!("expected Complete(Err), got {state:?}"),
299 }
300 }
301}