use alloc::{string::String, vec::Vec};
use std::io::{self, Read, Write};
#[cfg(any(
feature = "rustls-ring",
feature = "rustls-aws",
feature = "native-tls"
))]
use io_smtp::rfc5321::types::ehlo_domain::EhloDomain;
use io_smtp::{
client::{SmtpClientStd as InnerSmtpClientStd, SmtpClientStdError as InnerSmtpClientStdError},
coroutine::*,
};
#[cfg(any(
feature = "rustls-ring",
feature = "rustls-aws",
feature = "native-tls"
))]
use pimalaya_stream::{sasl::Sasl, tls::Tls};
use thiserror::Error;
#[cfg(any(
feature = "rustls-ring",
feature = "rustls-aws",
feature = "native-tls"
))]
use url::Url;
use crate::message::smtp::send::{SmtpMessageSend, SmtpMessageSendError};
#[derive(Debug, Error)]
pub enum SmtpClientError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
MessageSend(#[from] SmtpMessageSendError),
#[error(transparent)]
Inner(#[from] InnerSmtpClientStdError),
}
const READ_BUFFER_SIZE: usize = 16 * 1024;
pub struct SmtpClientStd {
pub inner: InnerSmtpClientStd,
pub default_reverse_path: Option<String>,
}
impl SmtpClientStd {
pub fn new<S: Read + Write + Send + 'static>(stream: S) -> Self {
Self {
inner: InnerSmtpClientStd::new(stream),
default_reverse_path: None,
}
}
pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, SmtpClientError>
where
C: SmtpCoroutine<Yield = SmtpYield, Return = Result<T, E>>,
SmtpClientError: From<E>,
{
let mut buf = [0u8; READ_BUFFER_SIZE];
let mut arg: Option<&[u8]> = None;
loop {
match coroutine.resume(arg.take()) {
SmtpCoroutineState::Complete(Ok(out)) => return Ok(out),
SmtpCoroutineState::Complete(Err(err)) => return Err(err.into()),
SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
let n = self.inner.stream.read(&mut buf)?;
arg = Some(&buf[..n]);
}
SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
self.inner.stream.write_all(&bytes)?;
}
}
}
}
pub fn ping(&mut self) -> Result<(), SmtpClientError> {
Ok(self.inner.noop()?)
}
pub fn send_message(&mut self, raw: Vec<u8>) -> Result<(), SmtpClientError> {
let coroutine = {
let override_reverse = self.default_reverse_path.as_deref();
SmtpMessageSend::new(raw, override_reverse)?
};
self.run(coroutine)
}
}
#[cfg(any(
feature = "rustls-ring",
feature = "rustls-aws",
feature = "native-tls"
))]
impl SmtpClientStd {
pub fn connect(
url: &Url,
tls: &Tls,
starttls: bool,
domain: EhloDomain<'_>,
sasl: Option<impl Into<Sasl>>,
) -> Result<Self, SmtpClientError> {
let inner = InnerSmtpClientStd::connect(url, tls, starttls, domain, sasl)?;
Ok(Self {
inner,
default_reverse_path: None,
})
}
}