#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use std::marker::PhantomData;
use std::{
ffi::OsString,
io::Write,
process::{Command, Stdio},
};
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use async_trait::async_trait;
pub use self::error::Error;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Executor;
#[cfg(feature = "tokio1")]
use crate::Tokio1Executor;
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use crate::{AsyncTransport, Executor};
use crate::{Transport, address::Envelope};
mod error;
const DEFAULT_SENDMAIL: &str = "sendmail";
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(docsrs, doc(cfg(feature = "sendmail-transport")))]
pub struct SendmailTransport {
command: OsString,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
pub struct AsyncSendmailTransport<E: Executor> {
inner: SendmailTransport,
marker_: PhantomData<E>,
}
impl SendmailTransport {
pub fn new() -> SendmailTransport {
SendmailTransport {
command: DEFAULT_SENDMAIL.into(),
}
}
pub fn new_with_command<S: Into<OsString>>(command: S) -> SendmailTransport {
SendmailTransport {
command: command.into(),
}
}
fn command(&self, envelope: &Envelope) -> Command {
let mut c = Command::new(&self.command);
c.arg("-i");
if let Some(from) = envelope.from() {
c.arg("-f").arg(from);
}
c.arg("--")
.args(envelope.to())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
c
}
}
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
impl<E> AsyncSendmailTransport<E>
where
E: Executor,
{
pub fn new() -> Self {
Self {
inner: SendmailTransport::new(),
marker_: PhantomData,
}
}
pub fn new_with_command<S: Into<OsString>>(command: S) -> Self {
Self {
inner: SendmailTransport::new_with_command(command),
marker_: PhantomData,
}
}
#[cfg(feature = "tokio1")]
fn tokio1_command(&self, envelope: &Envelope) -> tokio1_crate::process::Command {
use tokio1_crate::process::Command;
let mut c = Command::new(&self.inner.command);
c.kill_on_drop(true);
c.arg("-i");
if let Some(from) = envelope.from() {
c.arg("-f").arg(from);
}
c.arg("--")
.args(envelope.to())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
c
}
#[cfg(feature = "async-std1")]
fn async_std_command(&self, envelope: &Envelope) -> async_std::process::Command {
use async_std::process::Command;
let mut c = Command::new(&self.inner.command);
c.arg("-i");
if let Some(from) = envelope.from() {
c.arg("-f").arg(from);
}
c.arg("--")
.args(envelope.to())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
c
}
}
impl Default for SendmailTransport {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
impl<E> Default for AsyncSendmailTransport<E>
where
E: Executor,
{
fn default() -> Self {
Self::new()
}
}
impl Transport for SendmailTransport {
type Ok = ();
type Error = Error;
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
#[cfg(feature = "tracing")]
tracing::debug!(command = ?self.command, "sending email with");
let mut process = self.command(envelope).spawn().map_err(error::client)?;
process
.stdin
.as_mut()
.unwrap()
.write_all(email)
.map_err(error::client)?;
let output = process.wait_with_output().map_err(error::client)?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8(output.stderr).map_err(error::response)?;
Err(error::client(stderr))
}
}
}
#[cfg(feature = "async-std1")]
#[async_trait]
impl AsyncTransport for AsyncSendmailTransport<AsyncStd1Executor> {
type Ok = ();
type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use async_std::io::prelude::WriteExt;
#[cfg(feature = "tracing")]
tracing::debug!(command = ?self.inner.command, "sending email with");
let mut command = self.async_std_command(envelope);
let mut process = command.spawn().map_err(error::client)?;
process
.stdin
.as_mut()
.unwrap()
.write_all(email)
.await
.map_err(error::client)?;
let output = process.output().await.map_err(error::client)?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8(output.stderr).map_err(error::response)?;
Err(error::client(stderr))
}
}
}
#[cfg(feature = "tokio1")]
#[async_trait]
impl AsyncTransport for AsyncSendmailTransport<Tokio1Executor> {
type Ok = ();
type Error = Error;
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio1_crate::io::AsyncWriteExt;
#[cfg(feature = "tracing")]
tracing::debug!(command = ?self.inner.command, "sending email with");
let mut command = self.tokio1_command(envelope);
let mut process = command.spawn().map_err(error::client)?;
process
.stdin
.as_mut()
.unwrap()
.write_all(email)
.await
.map_err(error::client)?;
let output = process.wait_with_output().await.map_err(error::client)?;
if output.status.success() {
Ok(())
} else {
let stderr = String::from_utf8(output.stderr).map_err(error::response)?;
Err(error::client(stderr))
}
}
}