use log::trace;
use std::io;
pub(crate) const EMPTY_RESPONSE: Response = Response::empty();
pub(crate) const START_TLS: Response =
Response::fixed_action(220, "Ready to start TLS", Action::UpgradeTls);
pub const GOODBYE: Response = Response::fixed(221, "Goodbye");
pub const AUTH_OK: Response = Response::fixed(235, "Authentication succeeded");
pub const OK: Response = Response::fixed(250, "OK");
pub(crate) const VERIFY_RESPONSE: Response = Response::fixed(252, "Maybe");
pub(crate) const EMPTY_AUTH_CHALLENGE: Response = Response::fixed(334, "");
pub(crate) const USERNAME_AUTH_CHALLENGE: Response = Response::fixed(334, "VXNlcm5hbWU6");
pub(crate) const PASSWORD_AUTH_CHALLENGE: Response = Response::fixed(334, "UGFzc3dvcmQ6");
pub const START_DATA: Response = Response::fixed(354, "Start mail input; end with <CRLF>.<CRLF>");
pub(crate) const INVALID_STATE: Response =
Response::fixed(421, "Internal service error, closing connection");
pub const NO_SERVICE: Response = Response::fixed(421, "Service not available, closing connection");
pub const INTERNAL_ERROR: Response = Response::fixed(451, "Aborted: local error in processing");
pub const OUT_OF_SPACE: Response = Response::fixed(452, "Insufficient system storage");
pub const TEMP_AUTH_FAILURE: Response = Response::fixed(454, "Temporary authentication failure");
pub(crate) const SYNTAX_ERROR: Response = Response::fixed(500, "Syntax error");
pub(crate) const MISSING_PARAMETER: Response = Response::fixed(502, "Missing parameter");
pub(crate) const BAD_SEQUENCE_COMMANDS: Response = Response::fixed(503, "Bad sequence of commands");
pub const NO_STORAGE: Response = Response::fixed(552, "Exceeded storage allocation");
pub const AUTHENTICATION_REQUIRED: Response = Response::fixed(530, "Authentication required");
pub const INVALID_CREDENTIALS: Response = Response::fixed(535, "Invalid credentials");
pub const NO_MAILBOX: Response = Response::fixed(550, "Mailbox unavailable");
pub const BAD_HELLO: Response = Response::fixed(550, "Bad HELO");
pub const BLOCKED_IP: Response = Response::fixed(550, "IP address on blocklists");
pub const BAD_MAILBOX: Response = Response::fixed(553, "Mailbox name not allowed");
pub const TRANSACTION_FAILED: Response = Response::fixed(554, "Transaction failed");
#[derive(Clone, Debug, PartialEq)]
pub struct Response {
pub code: u16,
message: Message,
pub is_error: bool,
pub action: Action,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Message {
Fixed(&'static str),
Custom(String),
Dynamic(String, Vec<String>),
Empty,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Action {
Close,
UpgradeTls,
NoReply,
Reply,
}
impl Response {
pub(crate) const fn fixed(code: u16, message: &'static str) -> Self {
Self::fixed_action(code, message, Response::action_from_code(code))
}
const fn action_from_code(code: u16) -> Action {
match code {
221 | 421 => Action::Close,
_ => Action::Reply,
}
}
pub(crate) const fn fixed_action(code: u16, message: &'static str, action: Action) -> Self {
Self {
code,
message: Message::Fixed(message),
is_error: (code < 200 || code >= 400),
action,
}
}
pub const fn custom(code: u16, message: String) -> Self {
Self {
code,
message: Message::Custom(message),
is_error: (code < 200 || code >= 400),
action: Response::action_from_code(code),
}
}
pub(crate) fn dynamic(code: u16, head: String, tail: Vec<String>) -> Self {
Self {
code,
message: Message::Dynamic(head, tail),
is_error: false,
action: Action::Reply,
}
}
pub(crate) const fn empty() -> Self {
Self {
code: 0,
message: Message::Empty,
is_error: false,
action: Action::NoReply,
}
}
pub fn write_to(&self, out: &mut dyn io::Write) -> io::Result<()> {
match &self.message {
Message::Dynamic(ref head, ref tail) => {
if tail.is_empty() {
write!(out, "{} {}\r\n", self.code, head)?;
} else {
write!(out, "{}-{}\r\n", self.code, head)?;
for i in 0..tail.len() {
if tail.len() > 1 && i < tail.len() - 1 {
write!(out, "{}-{}\r\n", self.code, tail[i])?;
} else {
write!(out, "{} {}\r\n", self.code, tail[i])?;
}
}
}
}
Message::Fixed(s) => write!(out, "{} {}\r\n", self.code, s)?,
Message::Custom(s) => write!(out, "{} {}\r\n", self.code, s)?,
Message::Empty => (),
};
Ok(())
}
pub fn buffer(&self) -> io::Result<Vec<u8>> {
let mut buf = Vec::new();
self.write_to(&mut buf)?;
Ok(buf)
}
pub(crate) fn log(&self) {
match self.message {
Message::Empty => (),
_ => {
let buf = self.buffer().unwrap_or_default();
trace!("< {}", String::from_utf8_lossy(&buf));
}
}
}
}