use std::borrow::Cow;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::error::Result;
use crate::smtp::codec::TranscriptSink;
#[derive(Debug, Clone)]
pub struct SmtpReply {
pub code: u16,
pub lines: Vec<Cow<'static, str>>,
}
impl SmtpReply {
pub fn new(code: u16, line: impl Into<Cow<'static, str>>) -> Self {
Self {
code,
lines: vec![line.into()],
}
}
pub fn multi(code: u16, lines: Vec<Cow<'static, str>>) -> Self {
Self { code, lines }
}
pub fn greet(host: &str) -> Self {
SmtpReply::new(220, format!("{host} ESMTP Postcrate ready"))
}
pub fn ok() -> Self {
SmtpReply::new(250, "OK")
}
pub fn ok_msg(msg: impl Into<Cow<'static, str>>) -> Self {
SmtpReply::new(250, msg)
}
pub fn bye() -> Self {
SmtpReply::new(221, "Bye")
}
pub fn start_mail_input() -> Self {
SmtpReply::new(354, "End data with <CR><LF>.<CR><LF>")
}
pub fn bad_sequence() -> Self {
SmtpReply::new(503, "Bad sequence of commands")
}
pub fn syntax_error() -> Self {
SmtpReply::new(500, "Syntax error, command unrecognized")
}
pub fn command_not_implemented() -> Self {
SmtpReply::new(502, "Command not implemented")
}
pub fn line_too_long() -> Self {
SmtpReply::new(500, "Line too long")
}
pub fn transaction_failed() -> Self {
SmtpReply::new(554, "Transaction failed")
}
pub fn size_exceeded() -> Self {
SmtpReply::new(552, "Message size exceeds fixed maximum")
}
pub fn vrfy_cannot() -> Self {
SmtpReply::new(252, "Cannot VRFY user; try RCPT")
}
pub fn help_lines() -> Self {
SmtpReply::multi(
214,
vec![
"Postcrate supports the following commands:".into(),
"HELO EHLO MAIL RCPT DATA RSET NOOP QUIT VRFY HELP".into(),
],
)
}
pub fn custom(code: u16, msg: impl Into<Cow<'static, str>>) -> Self {
SmtpReply::new(code, msg)
}
pub fn start_tls_ready() -> Self {
SmtpReply::new(220, "Ready to start TLS")
}
pub fn tls_not_available() -> Self {
SmtpReply::new(454, "TLS not available")
}
pub fn tls_already_active() -> Self {
SmtpReply::new(503, "TLS already active")
}
pub fn auth_ok() -> Self {
SmtpReply::new(235, "Authentication successful")
}
pub fn auth_continue(prompt_b64: &str) -> Self {
SmtpReply::new(334, prompt_b64.to_string())
}
pub fn auth_failed() -> Self {
SmtpReply::new(535, "Authentication failed")
}
pub fn auth_unsupported() -> Self {
SmtpReply::new(504, "Unsupported authentication mechanism")
}
}
pub struct ReplyWriter<W> {
inner: W,
transcript: Option<TranscriptSink>,
}
impl<W: AsyncWrite + Unpin> ReplyWriter<W> {
pub fn new(inner: W) -> Self {
Self {
inner,
transcript: None,
}
}
pub fn with_transcript(mut self, sink: Option<TranscriptSink>) -> Self {
self.transcript = sink;
self
}
pub fn into_inner(self) -> W {
self.inner
}
pub async fn send(&mut self, reply: &SmtpReply) -> Result<()> {
let n = reply.lines.len();
if n == 0 {
let line = format!("{} \r\n", reply.code);
self.inner.write_all(line.as_bytes()).await?;
self.inner.flush().await?;
if let Some(t) = &self.transcript {
t.lock().push(format!("< {}", reply.code));
}
return Ok(());
}
for (i, l) in reply.lines.iter().enumerate() {
let sep = if i + 1 == n { ' ' } else { '-' };
let line = format!("{}{sep}{}\r\n", reply.code, l);
self.inner.write_all(line.as_bytes()).await?;
if let Some(t) = &self.transcript {
t.lock().push(format!("< {}{sep}{}", reply.code, l));
}
}
self.inner.flush().await?;
Ok(())
}
pub async fn send_raw(&mut self, bytes: &[u8]) -> Result<()> {
self.inner.write_all(bytes).await?;
self.inner.flush().await?;
if let Some(t) = &self.transcript {
t.lock().push(format!(
"< [raw {} bytes] {}",
bytes.len(),
String::from_utf8_lossy(bytes).trim_end()
));
}
Ok(())
}
}