io_smtp/message.rs
1//! SMTP composite coroutine; chains MAIL FROM, one RCPT TO per
2//! recipient, then DATA.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//! borrow::Cow,
9//! io::{Read, Write},
10//! net::TcpStream,
11//! };
12//!
13//! use io_smtp::{
14//! coroutine::{SmtpCoroutine, SmtpCoroutineState, SmtpYield},
15//! message::SmtpMessageSend,
16//! rfc5321::{
17//! SmtpDomain, SmtpEhloDomain, SmtpForwardPath,
18//! SmtpLocalPart, SmtpMailbox, SmtpReversePath,
19//! },
20//! };
21//!
22//! // Ready stream needed (TCP-connected, TLS-negociated, AUTH consumed)
23//! let mut stream = TcpStream::connect("localhost:25").unwrap();
24//!
25//! let mut buf = [0u8; 4096];
26//!
27//! let alice = SmtpMailbox {
28//! local_part: SmtpLocalPart(Cow::Borrowed("alice")),
29//! domain: SmtpEhloDomain::SmtpDomain(SmtpDomain(Cow::Borrowed("example.org"))),
30//! };
31//! let bob = SmtpMailbox {
32//! local_part: SmtpLocalPart(Cow::Borrowed("bob")),
33//! domain: SmtpEhloDomain::SmtpDomain(SmtpDomain(Cow::Borrowed("example.org"))),
34//! };
35//! let message =
36//! b"From: alice@example.org\r\nTo: bob@example.org\r\nSubject: hi\r\n\r\nhello\r\n".to_vec();
37//!
38//! let mut coroutine = SmtpMessageSend::new(
39//! SmtpReversePath::SmtpMailbox(alice),
40//! [SmtpForwardPath(bob)],
41//! message,
42//! );
43//! let mut arg = None;
44//!
45//! loop {
46//! match coroutine.resume(arg.take()) {
47//! SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
48//! stream.write_all(&bytes).unwrap();
49//! }
50//! SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
51//! let n = stream.read(&mut buf).unwrap();
52//! arg = Some(&buf[..n]);
53//! }
54//! SmtpCoroutineState::Complete(Ok(())) => break,
55//! SmtpCoroutineState::Complete(Err(err)) => panic!("{err}"),
56//! }
57//! }
58//! ```
59
60use core::fmt;
61
62use alloc::{collections::VecDeque, vec::Vec};
63
64use bounded_static::IntoBoundedStatic;
65use log::debug;
66use thiserror::Error;
67
68use crate::{
69 coroutine::*,
70 rfc5321::{
71 SmtpForwardPath, SmtpReversePath,
72 data::{SmtpData, SmtpDataError},
73 mail::{SmtpMail, SmtpMailError},
74 rcpt::{SmtpRcpt, SmtpRcptError},
75 },
76 smtp_try,
77};
78
79/// Failure causes during the SMTP send composite coroutine.
80#[derive(Debug, Error)]
81pub enum SmtpMessageSendError {
82 /// The MAIL FROM step failed.
83 #[error(transparent)]
84 MailFrom(#[from] SmtpMailError),
85 /// A RCPT TO step failed.
86 #[error(transparent)]
87 RcptTo(#[from] SmtpRcptError),
88 /// The DATA step failed.
89 #[error(transparent)]
90 Data(#[from] SmtpDataError),
91}
92
93/// I/O-free SMTP composite send coroutine.
94pub struct SmtpMessageSend {
95 state: State,
96 forward_paths: VecDeque<SmtpForwardPath<'static>>,
97 message: Option<Vec<u8>>,
98}
99
100impl SmtpMessageSend {
101 /// Creates the coroutine from the sender path, the recipient
102 /// paths and the complete message (headers plus body).
103 pub fn new<'a>(
104 reverse_path: SmtpReversePath<'_>,
105 forward_paths: impl IntoIterator<Item = SmtpForwardPath<'a>>,
106 message: Vec<u8>,
107 ) -> Self {
108 let forward_paths = forward_paths
109 .into_iter()
110 .map(IntoBoundedStatic::into_static)
111 .collect();
112
113 Self {
114 state: State::MailFrom(SmtpMail::new(reverse_path.into_static(), Vec::new())),
115 forward_paths,
116 message: Some(message),
117 }
118 }
119}
120
121impl SmtpCoroutine for SmtpMessageSend {
122 type Yield = SmtpYield;
123 type Return = Result<(), SmtpMessageSendError>;
124
125 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
126 loop {
127 match &mut self.state {
128 State::MailFrom(mail) => {
129 let () = smtp_try!(mail, arg);
130 self.state = self.next_rcpt_or_data();
131 debug!("mail from accepted, next: {}", self.state);
132 }
133 State::RcptTo(rcpt) => {
134 let () = smtp_try!(rcpt, arg);
135 self.state = self.next_rcpt_or_data();
136 debug!("rcpt to accepted, next: {}", self.state);
137 }
138 State::Data(data) => {
139 let () = smtp_try!(data, arg);
140 debug!("message sent");
141 return SmtpCoroutineState::Complete(Ok(()));
142 }
143 }
144 }
145 }
146}
147
148impl SmtpMessageSend {
149 fn next_rcpt_or_data(&mut self) -> State {
150 match self.forward_paths.pop_front() {
151 Some(path) => State::RcptTo(SmtpRcpt::new(path, Vec::new())),
152 None => {
153 let body = self.message.take().expect("message taken twice");
154 State::Data(SmtpData::new(body))
155 }
156 }
157 }
158}
159
160enum State {
161 MailFrom(SmtpMail),
162 RcptTo(SmtpRcpt),
163 Data(SmtpData),
164}
165
166impl fmt::Display for State {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 Self::MailFrom(_) => f.write_str("mail from"),
170 Self::RcptTo(_) => f.write_str("rcpt to"),
171 Self::Data(_) => f.write_str("data"),
172 }
173 }
174}