pop3_codec/types/
command.rs

1#[cfg(feature = "serdex")]
2use serde::{Deserialize, Serialize};
3
4// 9. POP3 Command Summary
5#[cfg_attr(feature = "serdex", derive(Serialize, Deserialize))]
6#[derive(Clone, Debug, Eq, PartialEq, Hash)]
7pub enum Command {
8    // Minimal POP3 Commands:
9    // -- AUTHORIZATION state --
10    /// USER name
11    User(String),
12    /// PASS string
13    Pass(String),
14    // -- TRANSACTION state --
15    /// STAT
16    Stat,
17    /// LIST
18    ListAll,
19    /// LIST msg
20    List {
21        msg: u32,
22    },
23    /// RETR msg
24    Retr {
25        msg: u32,
26    },
27    /// DELE msg
28    Dele {
29        msg: u32,
30    },
31    /// NOOP
32    Noop,
33    /// RSET
34    Rset,
35    // -- AUTHORIZATION state + TRANSACTION state --
36    /// QUIT
37    Quit,
38
39    // Optional POP3 Commands:
40    // -- AUTHORIZATION state --
41    /// APOP name digest
42    Apop {
43        name: String,
44        digest: String,
45    },
46    // -- TRANSACTION state --
47    /// TOP msg n
48    Top {
49        msg: u32,
50        n: u32,
51    },
52    /// UIDL
53    UidlAll,
54    /// UIDL msg
55    Uidl {
56        msg: u32,
57    },
58
59    // RFC2449 (POP3 Extension Mechanism):
60    /// CAPA
61    Capa,
62
63    // RFC2595 (Using TLS with IMAP, POP3 and ACAP):
64    /// STLS
65    Stls,
66    // RFC5034 (POP3 SASL Authentication Mechanism)
67    // TODO: Where is "AUTH\r\n" (without mechanism) defined?
68    // rfc1939? no.
69    // rfc1734? yes, but mechanism is required due to formal syntax and obsoleted.
70    // rfc5034? yes, but mechanism is required due to formal syntax.
71    AuthAll,
72    Auth {
73        mechanism: String,
74        initial_response: Option<String>,
75    },
76
77    // RFC6856
78    Utf8,
79    LangAll,
80    Lang {
81        lang_or_wild: Language,
82    },
83}
84
85impl Command {
86    pub fn name(&self) -> &'static str {
87        match self {
88            Command::User(_) => "USER",
89            Command::Pass(_) => "PASS",
90            Command::Stat => "STAT",
91            Command::ListAll => "LISTALL",
92            Command::List { .. } => "LIST",
93            Command::Retr { .. } => "RETR",
94            Command::Dele { .. } => "DELE",
95            Command::Noop => "NOOP",
96            Command::Rset => "RSET",
97            Command::Quit => "QUIT",
98            Command::Apop { .. } => "APOP",
99            Command::Top { .. } => "TOP",
100            Command::UidlAll => "UIDLALL",
101            Command::Uidl { .. } => "UIDL",
102            Command::Capa => "CAPA",
103            Command::Stls => "STLS",
104            Command::AuthAll => "AUTHALL",
105            Command::Auth { .. } => "AUTH",
106            Command::Utf8 => "UTF8",
107            Command::LangAll => "LANGALL",
108            Command::Lang { .. } => "LANG",
109        }
110    }
111
112    pub fn serialize(&self) -> Vec<u8> {
113        match self {
114            Command::User(user) => format!("USER {}\r\n", user).into_bytes(),
115            Command::Pass(pass) => format!("PASS {}\r\n", pass).into_bytes(),
116            Command::Stat => b"STAT\r\n".to_vec(),
117            Command::ListAll => b"LIST\r\n".to_vec(),
118            Command::List { msg } => format!("LIST {}\r\n", msg).into_bytes(),
119            Command::Retr { msg } => format!("RETR {}\r\n", msg).into_bytes(),
120            Command::Dele { msg } => format!("DELE {}\r\n", msg).into_bytes(),
121            Command::Noop => b"NOOP\r\n".to_vec(),
122            Command::Rset => b"RSET\r\n".to_vec(),
123            Command::Quit => b"QUIT\r\n".to_vec(),
124            Command::Apop { name, digest } => format!("APOP {} {}\r\n", name, digest).into_bytes(),
125            Command::Top { msg, n } => format!("TOP {} {}\r\n", msg, n).into_bytes(),
126            Command::UidlAll => b"UIDL\r\n".to_vec(),
127            Command::Uidl { msg } => format!("UIDL {}\r\n", msg).into_bytes(),
128            Command::Capa => b"CAPA\r\n".to_vec(),
129            Command::Stls => b"STLS\r\n".to_vec(),
130            Command::AuthAll => b"AUTH\r\n".to_vec(),
131            Command::Auth {
132                mechanism,
133                initial_response,
134            } => match initial_response {
135                Some(initial_response) => {
136                    format!("AUTH {} {}\r\n", mechanism, initial_response).into_bytes()
137                }
138                None => format!("AUTH {}\r\n", mechanism).into_bytes(),
139            },
140            Command::Utf8 => b"UTF8\r\n".to_vec(),
141            Command::LangAll => b"LANG\r\n".to_vec(),
142            Command::Lang { lang_or_wild } => {
143                format!("LANG {}\r\n", lang_or_wild.to_string()).into_bytes()
144            }
145        }
146    }
147}
148
149#[cfg_attr(feature = "serdex", derive(Serialize, Deserialize))]
150#[derive(Clone, Debug, Eq, PartialEq, Hash)]
151pub enum Language {
152    Lang(String),
153    Wild,
154}
155
156impl ToString for Language {
157    fn to_string(&self) -> String {
158        match self {
159            Language::Lang(lang) => lang.clone(),
160            Language::Wild => "*".to_owned(),
161        }
162    }
163}
164
165#[cfg(test)]
166mod test {
167    use super::Command;
168
169    #[test]
170    fn test_serialize() {
171        assert_eq!(Command::User("alice".into()).serialize(), b"USER alice\r\n");
172        assert_eq!(
173            Command::Pass("password".into()).serialize(),
174            b"PASS password\r\n"
175        );
176        assert_eq!(Command::Stat.serialize(), b"STAT\r\n");
177        assert_eq!(Command::ListAll.serialize(), b"LIST\r\n");
178        assert_eq!(Command::List { msg: 1 }.serialize(), b"LIST 1\r\n");
179        assert_eq!(Command::Retr { msg: 1 }.serialize(), b"RETR 1\r\n");
180        assert_eq!(Command::Dele { msg: 1 }.serialize(), b"DELE 1\r\n");
181        assert_eq!(Command::Noop.serialize(), b"NOOP\r\n");
182        assert_eq!(Command::Rset.serialize(), b"RSET\r\n");
183        assert_eq!(Command::Quit.serialize(), b"QUIT\r\n");
184        assert_eq!(
185            Command::Apop {
186                name: "alice".into(),
187                digest: "aabbccddeeff".into()
188            }
189            .serialize(),
190            b"APOP alice aabbccddeeff\r\n"
191        );
192        assert_eq!(Command::Top { msg: 1, n: 5 }.serialize(), b"TOP 1 5\r\n");
193        assert_eq!(Command::UidlAll.serialize(), b"UIDL\r\n");
194        assert_eq!(Command::Uidl { msg: 1 }.serialize(), b"UIDL 1\r\n");
195        assert_eq!(Command::Capa.serialize(), b"CAPA\r\n");
196        assert_eq!(Command::Stls.serialize(), b"STLS\r\n");
197        assert_eq!(
198            Command::Auth {
199                mechanism: "PLAIN".into(),
200                initial_response: None
201            }
202            .serialize(),
203            b"AUTH PLAIN\r\n"
204        );
205        assert_eq!(
206            Command::Auth {
207                mechanism: "PLAIN".into(),
208                initial_response: Some("XXX".into())
209            }
210            .serialize(),
211            b"AUTH PLAIN XXX\r\n"
212        );
213        assert_eq!(Command::AuthAll.serialize(), b"AUTH\r\n");
214    }
215}