Skip to main content

rusmes_smtp/
command.rs

1//! SMTP command types
2
3use rusmes_proto::MailAddress;
4use std::fmt;
5
6/// SMTP commands as defined in RFC 5321
7#[derive(Debug, Clone, PartialEq)]
8pub enum SmtpCommand {
9    /// HELO domain
10    Helo(String),
11    /// EHLO domain
12    Ehlo(String),
13    /// MAIL FROM:`<address>` \[parameters\]
14    Mail {
15        from: MailAddress,
16        params: Vec<MailParam>,
17    },
18    /// RCPT TO:`<address>` \[parameters\]
19    Rcpt {
20        to: MailAddress,
21        params: Vec<MailParam>,
22    },
23    /// DATA
24    Data,
25    /// BDAT `<chunk-size>` \[LAST\] - RFC 3030 CHUNKING
26    Bdat { chunk_size: usize, last: bool },
27    /// RSET - reset session
28    Rset,
29    /// NOOP - no operation
30    Noop,
31    /// QUIT - close connection
32    Quit,
33    /// VRFY `<string>` - verify address
34    Vrfy(String),
35    /// EXPN `<string>` - expand mailing list
36    Expn(String),
37    /// HELP \[`<string>`\]
38    Help(Option<String>),
39    /// STARTTLS - initiate TLS
40    StartTls,
41    /// AUTH `<mechanism>` [initial-response]
42    Auth {
43        mechanism: String,
44        initial_response: Option<String>,
45    },
46}
47
48impl fmt::Display for SmtpCommand {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            SmtpCommand::Helo(domain) => write!(f, "HELO {}", domain),
52            SmtpCommand::Ehlo(domain) => write!(f, "EHLO {}", domain),
53            SmtpCommand::Mail { from, .. } => write!(f, "MAIL FROM:<{}>", from),
54            SmtpCommand::Rcpt { to, .. } => write!(f, "RCPT TO:<{}>", to),
55            SmtpCommand::Data => write!(f, "DATA"),
56            SmtpCommand::Bdat { chunk_size, last } => {
57                if *last {
58                    write!(f, "BDAT {} LAST", chunk_size)
59                } else {
60                    write!(f, "BDAT {}", chunk_size)
61                }
62            }
63            SmtpCommand::Rset => write!(f, "RSET"),
64            SmtpCommand::Noop => write!(f, "NOOP"),
65            SmtpCommand::Quit => write!(f, "QUIT"),
66            SmtpCommand::Vrfy(addr) => write!(f, "VRFY {}", addr),
67            SmtpCommand::Expn(list) => write!(f, "EXPN {}", list),
68            SmtpCommand::Help(topic) => {
69                if let Some(t) = topic {
70                    write!(f, "HELP {}", t)
71                } else {
72                    write!(f, "HELP")
73                }
74            }
75            SmtpCommand::StartTls => write!(f, "STARTTLS"),
76            SmtpCommand::Auth { mechanism, .. } => write!(f, "AUTH {}", mechanism),
77        }
78    }
79}
80
81/// SMTP command parameters (ESMTP extensions)
82#[derive(Debug, Clone, PartialEq)]
83pub struct MailParam {
84    pub keyword: String,
85    pub value: Option<String>,
86}
87
88impl MailParam {
89    /// Create a new mail parameter
90    pub fn new(keyword: impl Into<String>, value: Option<String>) -> Self {
91        Self {
92            keyword: keyword.into(),
93            value,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_command_display() {
104        let cmd = SmtpCommand::Helo("example.com".to_string());
105        assert_eq!(cmd.to_string(), "HELO example.com");
106
107        let cmd = SmtpCommand::Data;
108        assert_eq!(cmd.to_string(), "DATA");
109
110        let cmd = SmtpCommand::Quit;
111        assert_eq!(cmd.to_string(), "QUIT");
112    }
113}