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::types::{
17//! domain::Domain, ehlo_domain::EhloDomain, forward_path::ForwardPath,
18//! local_part::LocalPart, mailbox::Mailbox, reverse_path::ReversePath,
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 = Mailbox {
28//! local_part: LocalPart(Cow::Borrowed("alice")),
29//! domain: EhloDomain::Domain(Domain(Cow::Borrowed("example.org"))),
30//! };
31//! let bob = Mailbox {
32//! local_part: LocalPart(Cow::Borrowed("bob")),
33//! domain: EhloDomain::Domain(Domain(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//! ReversePath::Mailbox(alice),
40//! [ForwardPath(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::trace;
66use thiserror::Error;
67
68use crate::{
69 coroutine::*,
70 rfc5321::{
71 data::{SmtpData, SmtpDataError},
72 mail::{SmtpMail, SmtpMailError},
73 rcpt::{SmtpRcpt, SmtpRcptError},
74 types::{forward_path::ForwardPath, reverse_path::ReversePath},
75 },
76 smtp_try,
77};
78
79/// Failure causes during the SMTP send composite coroutine.
80#[derive(Debug, Error)]
81pub enum SmtpMessageSendError {
82 #[error(transparent)]
83 MailFrom(#[from] SmtpMailError),
84 #[error(transparent)]
85 RcptTo(#[from] SmtpRcptError),
86 #[error(transparent)]
87 Data(#[from] SmtpDataError),
88}
89
90/// I/O-free SMTP composite send coroutine.
91pub struct SmtpMessageSend {
92 state: State,
93 forward_paths: VecDeque<ForwardPath<'static>>,
94 message: Option<Vec<u8>>,
95}
96
97impl SmtpMessageSend {
98 pub fn new<'a>(
99 reverse_path: ReversePath<'_>,
100 forward_paths: impl IntoIterator<Item = ForwardPath<'a>>,
101 message: Vec<u8>,
102 ) -> Self {
103 let forward_paths = forward_paths
104 .into_iter()
105 .map(IntoBoundedStatic::into_static)
106 .collect();
107
108 Self {
109 state: State::MailFrom(SmtpMail::new(reverse_path.into_static(), Vec::new())),
110 forward_paths,
111 message: Some(message),
112 }
113 }
114}
115
116impl SmtpCoroutine for SmtpMessageSend {
117 type Yield = SmtpYield;
118 type Return = Result<(), SmtpMessageSendError>;
119
120 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
121 loop {
122 trace!("message send: {}", self.state);
123
124 match &mut self.state {
125 State::MailFrom(mail) => {
126 let () = smtp_try!(mail, arg);
127 self.state = self.next_rcpt_or_data();
128 }
129 State::RcptTo(rcpt) => {
130 let () = smtp_try!(rcpt, arg);
131 self.state = self.next_rcpt_or_data();
132 }
133 State::Data(data) => {
134 let () = smtp_try!(data, arg);
135 return SmtpCoroutineState::Complete(Ok(()));
136 }
137 }
138 }
139 }
140}
141
142impl SmtpMessageSend {
143 fn next_rcpt_or_data(&mut self) -> State {
144 match self.forward_paths.pop_front() {
145 Some(path) => State::RcptTo(SmtpRcpt::new(path, Vec::new())),
146 None => {
147 let body = self.message.take().expect("message taken twice");
148 State::Data(SmtpData::new(body))
149 }
150 }
151 }
152}
153
154enum State {
155 MailFrom(SmtpMail),
156 RcptTo(SmtpRcpt),
157 Data(SmtpData),
158}
159
160impl fmt::Display for State {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 Self::MailFrom(_) => f.write_str("mail from"),
164 Self::RcptTo(_) => f.write_str("rcpt to"),
165 Self::Data(_) => f.write_str("data"),
166 }
167 }
168}