1use alloc::{string::String, vec::Vec};
18use std::io::{self, Read, Write};
19
20#[cfg(any(
21 feature = "rustls-ring",
22 feature = "rustls-aws",
23 feature = "native-tls"
24))]
25use io_smtp::rfc5321::types::ehlo_domain::EhloDomain;
26use io_smtp::{
27 client::{SmtpClientStd as InnerSmtpClientStd, SmtpClientStdError as InnerSmtpClientStdError},
28 coroutine::*,
29};
30#[cfg(any(
31 feature = "rustls-ring",
32 feature = "rustls-aws",
33 feature = "native-tls"
34))]
35use pimalaya_stream::{sasl::Sasl, tls::Tls};
36use thiserror::Error;
37#[cfg(any(
38 feature = "rustls-ring",
39 feature = "rustls-aws",
40 feature = "native-tls"
41))]
42use url::Url;
43
44use crate::message::smtp::send::{SmtpMessageSend, SmtpMessageSendError};
45
46#[derive(Debug, Error)]
48pub enum SmtpClientError {
49 #[error(transparent)]
50 Io(#[from] io::Error),
51 #[error(transparent)]
52 MessageSend(#[from] SmtpMessageSendError),
53 #[error(transparent)]
54 Inner(#[from] InnerSmtpClientStdError),
55}
56
57const READ_BUFFER_SIZE: usize = 16 * 1024;
58
59pub struct SmtpClientStd {
65 pub inner: InnerSmtpClientStd,
66 pub default_reverse_path: Option<String>,
67}
68
69impl SmtpClientStd {
70 pub fn new<S: Read + Write + Send + 'static>(stream: S) -> Self {
73 Self {
74 inner: InnerSmtpClientStd::new(stream),
75 default_reverse_path: None,
76 }
77 }
78
79 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, SmtpClientError>
87 where
88 C: SmtpCoroutine<Yield = SmtpYield, Return = Result<T, E>>,
89 SmtpClientError: From<E>,
90 {
91 let mut buf = [0u8; READ_BUFFER_SIZE];
92 let mut arg: Option<&[u8]> = None;
93
94 loop {
95 match coroutine.resume(arg.take()) {
96 SmtpCoroutineState::Complete(Ok(out)) => return Ok(out),
97 SmtpCoroutineState::Complete(Err(err)) => return Err(err.into()),
98 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
99 let n = self.inner.stream.read(&mut buf)?;
100 arg = Some(&buf[..n]);
101 }
102 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
103 self.inner.stream.write_all(&bytes)?;
104 }
105 }
106 }
107 }
108
109 pub fn ping(&mut self) -> Result<(), SmtpClientError> {
113 Ok(self.inner.noop()?)
114 }
115
116 pub fn send_message(&mut self, raw: Vec<u8>) -> Result<(), SmtpClientError> {
122 let coroutine = {
123 let override_reverse = self.default_reverse_path.as_deref();
124 SmtpMessageSend::new(raw, override_reverse)?
125 };
126 self.run(coroutine)
127 }
128}
129
130#[cfg(any(
131 feature = "rustls-ring",
132 feature = "rustls-aws",
133 feature = "native-tls"
134))]
135impl SmtpClientStd {
136 pub fn connect(
141 url: &Url,
142 tls: &Tls,
143 starttls: bool,
144 domain: EhloDomain<'_>,
145 sasl: Option<impl Into<Sasl>>,
146 ) -> Result<Self, SmtpClientError> {
147 let inner = InnerSmtpClientStd::connect(url, tls, starttls, domain, sasl)?;
148 Ok(Self {
149 inner,
150 default_reverse_path: None,
151 })
152 }
153}