1use std::borrow::Cow;
2use std::marker::PhantomData;
3use std::ops::{RangeFrom, RangeInclusive};
4use std::str;
5
6use crate::types::{AttrMacro, Attribute, State};
7
8pub struct CommandBuilder {}
9
10impl CommandBuilder {
11 pub fn check() -> Command {
12 let args = b"CHECK".to_vec();
13 Command {
14 args,
15 next_state: None,
16 }
17 }
18
19 pub fn close() -> Command {
20 let args = b"CLOSE".to_vec();
21 Command {
22 args,
23 next_state: Some(State::Authenticated),
24 }
25 }
26
27 pub fn examine(mailbox: &str) -> SelectCommand<select::NoParams> {
28 let args = format!("EXAMINE \"{}\"", quoted_string(mailbox).unwrap()).into_bytes();
29 SelectCommand {
30 args,
31 state: PhantomData,
32 }
33 }
34
35 pub fn fetch() -> FetchCommand<fetch::Empty> {
36 FetchCommand {
37 args: b"FETCH ".to_vec(),
38 state: PhantomData,
39 }
40 }
41
42 pub fn list(reference: &str, glob: &str) -> Command {
43 let args = format!(
44 "LIST \"{}\" \"{}\"",
45 quoted_string(reference).unwrap(),
46 quoted_string(glob).unwrap()
47 )
48 .into_bytes();
49 Command {
50 args,
51 next_state: None,
52 }
53 }
54
55 pub fn login(user_name: &str, password: &str) -> Command {
56 let args = format!(
57 "LOGIN \"{}\" \"{}\"",
58 quoted_string(user_name).unwrap(),
59 quoted_string(password).unwrap()
60 )
61 .into_bytes();
62 Command {
63 args,
64 next_state: Some(State::Authenticated),
65 }
66 }
67
68 pub fn select(mailbox: &str) -> SelectCommand<select::NoParams> {
69 let args = format!("SELECT \"{}\"", quoted_string(mailbox).unwrap()).into_bytes();
70 SelectCommand {
71 args,
72 state: PhantomData,
73 }
74 }
75
76 pub fn uid_fetch() -> FetchCommand<fetch::Empty> {
77 FetchCommand {
78 args: b"UID FETCH ".to_vec(),
79 state: PhantomData,
80 }
81 }
82}
83
84pub struct Command {
85 pub args: Vec<u8>,
86 pub next_state: Option<State>,
87}
88
89pub struct SelectCommand<T> {
90 args: Vec<u8>,
91 state: PhantomData<T>,
92}
93
94impl SelectCommand<select::NoParams> {
95 pub fn cond_store(mut self) -> SelectCommand<select::Params> {
97 self.args.extend(b" (CONDSTORE");
98 SelectCommand {
99 args: self.args,
100 state: PhantomData,
101 }
102 }
103}
104
105impl From<SelectCommand<select::NoParams>> for Command {
106 fn from(cmd: SelectCommand<select::NoParams>) -> Command {
107 Command {
108 args: cmd.args,
109 next_state: Some(State::Selected),
110 }
111 }
112}
113
114impl From<SelectCommand<select::Params>> for Command {
115 fn from(mut cmd: SelectCommand<select::Params>) -> Command {
116 cmd.args.push(b')');
117 Command {
118 args: cmd.args,
119 next_state: Some(State::Selected),
120 }
121 }
122}
123
124pub mod select {
125 pub struct NoParams;
126 pub struct Params;
127}
128
129pub mod fetch {
130 pub struct Empty;
131 pub struct Messages;
132 pub struct Attributes;
133 pub struct Modifiers;
134}
135
136pub struct FetchCommand<T> {
137 args: Vec<u8>,
138 state: PhantomData<T>,
139}
140
141impl FetchCommand<fetch::Empty> {
142 pub fn num(mut self, num: u32) -> FetchCommand<fetch::Messages> {
143 sequence_num(&mut self.args, num);
144 FetchCommand {
145 args: self.args,
146 state: PhantomData,
147 }
148 }
149
150 pub fn range(mut self, range: RangeInclusive<u32>) -> FetchCommand<fetch::Messages> {
151 sequence_range(&mut self.args, range);
152 FetchCommand {
153 args: self.args,
154 state: PhantomData,
155 }
156 }
157
158 pub fn range_from(mut self, range: RangeFrom<u32>) -> FetchCommand<fetch::Messages> {
159 range_from(&mut self.args, range);
160 FetchCommand {
161 args: self.args,
162 state: PhantomData,
163 }
164 }
165}
166
167impl FetchCommand<fetch::Messages> {
168 pub fn num(mut self, num: u32) -> FetchCommand<fetch::Messages> {
169 self.args.extend(b",");
170 sequence_num(&mut self.args, num);
171 self
172 }
173
174 pub fn range(mut self, range: RangeInclusive<u32>) -> FetchCommand<fetch::Messages> {
175 self.args.extend(b",");
176 sequence_range(&mut self.args, range);
177 self
178 }
179
180 pub fn range_from(mut self, range: RangeFrom<u32>) -> FetchCommand<fetch::Messages> {
181 self.args.extend(b",");
182 range_from(&mut self.args, range);
183 self
184 }
185
186 pub fn attr_macro(mut self, named: AttrMacro) -> FetchCommand<fetch::Modifiers> {
187 self.args.push(b' ');
188 self.args.extend(
189 match named {
190 AttrMacro::All => "ALL",
191 AttrMacro::Fast => "FAST",
192 AttrMacro::Full => "FULL",
193 }
194 .as_bytes(),
195 );
196 FetchCommand {
197 args: self.args,
198 state: PhantomData,
199 }
200 }
201
202 pub fn attr(mut self, attr: Attribute) -> FetchCommand<fetch::Attributes> {
203 self.args.extend(b" (");
204 push_attr(&mut self.args, attr);
205 FetchCommand {
206 args: self.args,
207 state: PhantomData,
208 }
209 }
210}
211
212fn sequence_num(cmd: &mut Vec<u8>, num: u32) {
213 cmd.extend(num.to_string().as_bytes());
214}
215
216fn sequence_range(cmd: &mut Vec<u8>, range: RangeInclusive<u32>) {
217 cmd.extend(range.start().to_string().as_bytes());
218 cmd.push(b':');
219 cmd.extend(range.end().to_string().as_bytes());
220}
221
222fn range_from(cmd: &mut Vec<u8>, range: RangeFrom<u32>) {
223 cmd.extend(range.start.to_string().as_bytes());
224 cmd.extend(b":*");
225}
226
227impl FetchCommand<fetch::Attributes> {
228 pub fn attr(mut self, attr: Attribute) -> FetchCommand<fetch::Attributes> {
229 self.args.push(b' ');
230 push_attr(&mut self.args, attr);
231 self
232 }
233
234 pub fn changed_since(mut self, seq: u64) -> FetchCommand<fetch::Modifiers> {
235 self.args.push(b')');
236 changed_since(&mut self.args, seq);
237 FetchCommand {
238 args: self.args,
239 state: PhantomData,
240 }
241 }
242}
243
244fn push_attr(cmd: &mut Vec<u8>, attr: Attribute) {
245 cmd.extend(
246 match attr {
247 Attribute::Body => "BODY",
248 Attribute::Envelope => "ENVELOPE",
249 Attribute::Flags => "FLAGS",
250 Attribute::InternalDate => "INTERNALDATE",
251 Attribute::ModSeq => "MODSEQ",
252 Attribute::Rfc822 => "RFC822",
253 Attribute::Rfc822Size => "RFC822.SIZE",
254 Attribute::Rfc822Text => "RFC822.TEXT",
255 Attribute::Uid => "UID",
256 Attribute::GmailLabels => "X-GM-LABELS",
257 Attribute::GmailMsgId => "X-GM-MSGID",
258 Attribute::GmailThrId => "X-GM-THRID",
259 }
260 .as_bytes(),
261 );
262}
263
264impl From<FetchCommand<fetch::Attributes>> for Command {
265 fn from(mut cmd: FetchCommand<fetch::Attributes>) -> Command {
266 cmd.args.push(b')');
267 Command {
268 args: cmd.args,
269 next_state: None,
270 }
271 }
272}
273
274impl From<FetchCommand<fetch::Modifiers>> for Command {
275 fn from(cmd: FetchCommand<fetch::Modifiers>) -> Command {
276 Command {
277 args: cmd.args,
278 next_state: None,
279 }
280 }
281}
282
283impl FetchCommand<fetch::Modifiers> {
284 pub fn changed_since(mut self, seq: u64) -> FetchCommand<fetch::Modifiers> {
285 changed_since(&mut self.args, seq);
286 self
287 }
288}
289
290fn changed_since(cmd: &mut Vec<u8>, seq: u64) {
291 cmd.extend(b" (CHANGEDSINCE ");
292 cmd.extend(seq.to_string().as_bytes());
293 cmd.push(b')');
294}
295
296fn quoted_string(s: &str) -> Result<Cow<'_, str>, &'static str> {
308 let bytes = s.as_bytes();
309 let (mut start, mut new) = (0, Vec::<u8>::new());
310 for (i, b) in bytes.iter().enumerate() {
311 match *b {
312 b'\r' | b'\n' => {
313 return Err("CR and LF not allowed in quoted strings");
314 }
315 b'\\' | b'"' => {
316 if start < i {
317 new.extend(&bytes[start..i]);
318 }
319 new.push(b'\\');
320 new.push(*b);
321 start = i + 1;
322 }
323 _ => {}
324 };
325 }
326 if start == 0 {
327 Ok(Cow::Borrowed(s))
328 } else {
329 if start < bytes.len() {
330 new.extend(&bytes[start..]);
331 }
332 Ok(Cow::Owned(String::from_utf8(new).unwrap()))
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::{quoted_string, Attribute, Command, CommandBuilder};
342
343 #[test]
344 fn login() {
345 assert_eq!(
346 CommandBuilder::login("djc", "s3cr3t").args,
347 b"LOGIN \"djc\" \"s3cr3t\""
348 );
349 assert_eq!(
350 CommandBuilder::login("djc", "domain\\password").args,
351 b"LOGIN \"djc\" \"domain\\\\password\""
352 );
353 }
354
355 #[test]
356 fn select() {
357 let cmd = Command::from(CommandBuilder::select("INBOX"));
358 assert_eq!(&cmd.args, br#"SELECT "INBOX""#);
359 let cmd = Command::from(CommandBuilder::examine("INBOX").cond_store());
360 assert_eq!(&cmd.args, br#"EXAMINE "INBOX" (CONDSTORE)"#);
361 }
362
363 #[test]
364 fn fetch() {
365 let cmd: Command = CommandBuilder::fetch()
366 .range_from(1..)
367 .attr(Attribute::Uid)
368 .attr(Attribute::ModSeq)
369 .changed_since(13)
370 .into();
371 assert_eq!(cmd.args, &b"FETCH 1:* (UID MODSEQ) (CHANGEDSINCE 13)"[..]);
372
373 let cmd: Command = CommandBuilder::fetch()
374 .num(1)
375 .num(2)
376 .attr(Attribute::Uid)
377 .attr(Attribute::ModSeq)
378 .into();
379 assert_eq!(cmd.args, &b"FETCH 1,2 (UID MODSEQ)"[..]);
380 }
381
382 #[test]
383 fn test_quoted_string() {
384 assert_eq!(quoted_string("a").unwrap(), "a");
385 assert_eq!(quoted_string("").unwrap(), "");
386 assert_eq!(quoted_string("a\"b\\c").unwrap(), "a\\\"b\\\\c");
387 assert_eq!(quoted_string("\"foo\\").unwrap(), "\\\"foo\\\\");
388 assert!(quoted_string("\n").is_err());
389 }
390}