1use core::fmt;
51
52use alloc::{
53 string::{String, ToString},
54 vec::Vec,
55};
56
57use bounded_static::IntoBoundedStatic;
58use log::trace;
59use thiserror::Error;
60
61use crate::{
62 coroutine::*,
63 rfc5321::types::{forward_path::ForwardPath, parameter::Parameter, reply_code::ReplyCode},
64 send::*,
65 smtp_try,
66};
67
68pub struct SmtpRcptCommand<'a> {
70 pub forward_path: ForwardPath<'a>,
72 pub parameters: Vec<Parameter<'a>>,
74}
75
76impl<'a> From<SmtpRcptCommand<'a>> for Vec<u8> {
77 fn from(cmd: SmtpRcptCommand<'a>) -> Vec<u8> {
78 let mut buf = String::from("RCPT TO:");
79 buf.push_str(&cmd.forward_path.to_string());
80 for p in cmd.parameters {
81 buf.push(' ');
82 buf.push_str(&p.to_string());
83 }
84 buf.push_str("\r\n");
85 buf.into_bytes()
86 }
87}
88
89#[derive(Clone, Debug, Error)]
91pub enum SmtpRcptError {
92 #[error("SMTP RCPT TO failed: rejected {code} {message}")]
93 Rejected { code: u16, message: String },
94 #[error("SMTP RCPT TO failed: {0}")]
95 Send(#[from] SendSmtpCommandError),
96}
97
98pub struct SmtpRcpt {
100 state: State,
101}
102
103impl SmtpRcpt {
104 pub fn new(forward_path: ForwardPath<'_>, parameters: Vec<Parameter<'_>>) -> Self {
108 let cmd = SmtpRcptCommand {
109 forward_path: forward_path.into_static(),
110 parameters: parameters.into_iter().map(|p| p.into_static()).collect(),
111 };
112
113 Self {
114 state: State::Send(SendSmtpCommand::new(cmd)),
115 }
116 }
117}
118
119impl SmtpCoroutine for SmtpRcpt {
120 type Yield = SmtpYield;
121 type Return = Result<(), SmtpRcptError>;
122
123 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
124 loop {
125 trace!("rcpt: {}", self.state);
126
127 match &mut self.state {
128 State::Send(send) => {
129 let out = smtp_try!(send, arg);
130
131 if out.response.code == ReplyCode::OK
132 || out.response.code == ReplyCode::USER_NOT_LOCAL_WILL_FORWARD
133 {
134 return SmtpCoroutineState::Complete(Ok(()));
135 }
136
137 let code = out.response.code.code();
138 let message = out.response.text().to_string();
139 return SmtpCoroutineState::Complete(Err(SmtpRcptError::Rejected {
140 code,
141 message,
142 }));
143 }
144 }
145 }
146 }
147}
148
149enum State {
150 Send(SendSmtpCommand<SmtpRcptCommand<'static>>),
151}
152
153impl fmt::Display for State {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match self {
156 Self::Send(_) => f.write_str("send rcpt to"),
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use alloc::borrow::Cow;
164
165 use crate::rfc5321::types::{
166 domain::Domain, ehlo_domain::EhloDomain, local_part::LocalPart, mailbox::Mailbox,
167 };
168
169 use super::*;
170
171 fn forward_path() -> ForwardPath<'static> {
172 ForwardPath(Mailbox {
173 local_part: LocalPart(Cow::Borrowed("alice")),
174 domain: EhloDomain::Domain(Domain(Cow::Borrowed("example.com"))),
175 })
176 }
177
178 #[test]
179 fn success_returns_ok() {
180 let mut rcpt = SmtpRcpt::new(forward_path(), Vec::new());
181
182 let bytes = expect_wants_write(&mut rcpt, None);
183 assert!(bytes.starts_with(b"RCPT TO:"));
184
185 expect_wants_read(&mut rcpt);
186 expect_complete_ok(&mut rcpt, b"250 recipient ok\r\n");
187 }
188
189 #[test]
190 fn forwarded_returns_ok() {
191 let mut rcpt = SmtpRcpt::new(forward_path(), Vec::new());
192 let _ = expect_wants_write(&mut rcpt, None);
193 expect_wants_read(&mut rcpt);
194
195 expect_complete_ok(&mut rcpt, b"251 user not local, forwarding\r\n");
196 }
197
198 #[test]
199 fn rejected_returns_rejected_error() {
200 let mut rcpt = SmtpRcpt::new(forward_path(), Vec::new());
201 let _ = expect_wants_write(&mut rcpt, None);
202 expect_wants_read(&mut rcpt);
203
204 let err = expect_complete_err(&mut rcpt, b"550 no such user\r\n");
205 let SmtpRcptError::Rejected { code, message } = err else {
206 panic!("expected SmtpRcptError::Rejected, got {err:?}");
207 };
208 assert_eq!(code, 550);
209 assert_eq!(message, "no such user");
210 }
211
212 #[test]
213 fn eof_returns_eof_error() {
214 let mut rcpt = SmtpRcpt::new(forward_path(), Vec::new());
215 let _ = expect_wants_write(&mut rcpt, None);
216 expect_wants_read(&mut rcpt);
217
218 let err = expect_complete_err(&mut rcpt, b"");
219 assert!(matches!(
220 err,
221 SmtpRcptError::Send(SendSmtpCommandError::Eof)
222 ));
223 }
224
225 fn expect_wants_write(cor: &mut SmtpRcpt, arg: Option<&[u8]>) -> Vec<u8> {
228 match cor.resume(arg) {
229 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
230 state => panic!("expected WantsWrite, got {state:?}"),
231 }
232 }
233
234 fn expect_wants_read(cor: &mut SmtpRcpt) {
235 match cor.resume(None) {
236 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
237 state => panic!("expected WantsRead, got {state:?}"),
238 }
239 }
240
241 fn expect_complete_ok(cor: &mut SmtpRcpt, reply: &[u8]) {
242 match cor.resume(Some(reply)) {
243 SmtpCoroutineState::Complete(Ok(())) => {}
244 state => panic!("expected Complete(Ok), got {state:?}"),
245 }
246 }
247
248 fn expect_complete_err(cor: &mut SmtpRcpt, reply: &[u8]) -> SmtpRcptError {
249 match cor.resume(Some(reply)) {
250 SmtpCoroutineState::Complete(Err(err)) => err,
251 state => panic!("expected Complete(Err), got {state:?}"),
252 }
253 }
254}