async_pop3/
lib.rs

1use POP3StreamTypes::{Basic, Ssl};
2use POP3Command::{Greet, User, Pass, Stat, UidlAll, UidlOne, ListAll, ListOne, Retr, Dele, Noop, Rset, Quit};
3use std::string::String;
4use async_std::net::{ToSocketAddrs,TcpStream};
5use async_native_tls::{TlsStream, TlsConnector};
6use std::str::FromStr;
7use regex::Regex;
8use lazy_static::lazy_static;
9use async_std::io::{Error, ErrorKind, Result};
10use async_std::prelude::*;
11
12lazy_static! {
13    static ref ENDING_REGEX: Regex = Regex::new(r"^\.\r\n$").unwrap();
14    static ref OK_REGEX: Regex = Regex::new(r"\+OK(.*)").unwrap();
15    static ref ERR_REGEX: Regex = Regex::new(r"-ERR(.*)").unwrap();
16    static ref STAT_REGEX: Regex = Regex::new(r"\+OK (\d+) (\d+)\r\n").unwrap();
17    static ref MESSAGE_DATA_UIDL_ALL_REGEX: Regex = Regex::new(r"(\d+) ([\x21-\x7e]+)\r\n").unwrap();
18    static ref MESSAGE_DATA_UIDL_ONE_REGEX: Regex = Regex::new(r"\+OK (\d+) ([\x21-\x7e]+)\r\n").unwrap();
19    static ref MESSAGE_DATA_LIST_ALL_REGEX: Regex = Regex::new(r"(\d+) (\d+)\r\n").unwrap();
20}
21
22/// Wrapper for a regular TcpStream or a SslStream.
23#[derive(Debug)]
24enum POP3StreamTypes {
25	Basic(TcpStream),
26	Ssl(TlsStream<TcpStream>)
27}
28
29/// The stream to use for interfacing with the POP3 Server.
30#[derive(Debug)]
31pub struct POP3Stream {
32	stream: POP3StreamTypes,
33	pub is_authenticated: bool
34}
35
36/// List of POP3 Commands
37#[derive(Clone)]
38enum POP3Command {
39	Greet,
40	User,
41	Pass,
42	Stat,
43    UidlAll,
44    UidlOne,
45	ListAll,
46	ListOne,
47	Retr,
48	Dele,
49	Noop,
50	Rset,
51	Quit
52}
53
54impl POP3Stream {
55
56	/// Creates a new POP3Stream.
57	pub async fn connect<A:ToSocketAddrs>(addr: A,ssl_context: Option<TlsConnector>,domain: &str) -> Result<POP3Stream> {
58		let tcp_stream = TcpStream::connect(addr).await?;
59		let mut socket = match ssl_context {
60			Some(context) => POP3Stream {
61                stream: Ssl(TlsConnector::connect(&context, domain,tcp_stream).await.unwrap()),
62                is_authenticated: false},
63			None => POP3Stream {
64                stream: Basic(tcp_stream),
65                is_authenticated: false},
66		};
67		match socket.read_response(Greet).await {
68			Ok(_) => (),
69			Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read greet response"))
70		}
71		Ok(socket)
72	}
73
74	async fn write_str(&mut self, s: &str) -> Result<()> {
75		match self.stream {
76			Ssl(ref mut stream) => stream.write_fmt(format_args!("{}", s)).await,
77			Basic(ref mut stream) => stream.write_fmt(format_args!("{}", s)).await,
78		}
79	}
80
81	async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
82		match self.stream {
83			Ssl(ref mut stream) => stream.read(buf).await,
84			Basic(ref mut stream) => stream.read(buf).await,
85		}
86	}
87
88	/// Login to the POP3 server.
89	pub async fn login(&mut self, username: &str, password: &str) -> POP3Result {
90		let user_command = format!("USER {}\r\n", username);
91		let pass_command = format!("PASS {}\r\n", password);
92		//Send user command
93		match self.write_str(&user_command).await {
94			Ok(_) => {},
95			Err(_) => panic!("Error writing"),
96		}
97		match self.read_response(User).await {
98			Ok(_) => {
99				match self.write_str(&pass_command).await {
100					Ok(_) => self.is_authenticated = true,
101					Err(_) => panic!("Error writing"),
102				}
103				match self.read_response(Pass).await {
104					Ok(_) => {
105						POP3Result::POP3Ok
106					},
107					Err(_) => panic!("Failure to use PASS")
108				}
109			},
110			Err(_) => panic!("Failure to use USER")
111		}
112	}
113
114	/// Gives the current number of messages in the mailbox and the total size in bytes of the mailbox.
115	pub async fn stat(&mut self) -> POP3Result {
116		if !self.is_authenticated {
117			panic!("login");
118		}
119
120		let stat_command = "STAT\r\n";
121		match self.write_str(&stat_command).await {
122			Ok(_) => {},
123			Err(_) => panic!("Error writing"),
124		}
125		match self.read_response(Stat).await {
126			Ok(res) => {
127				match res.result {
128					Some(s) => s,
129					None => POP3Result::POP3Err
130				}
131			},
132			Err(_) => POP3Result::POP3Err
133		}
134	}
135
136    pub async fn uidl(&mut self, message_number: Option<i32>) -> POP3Result {
137        if !self.is_authenticated {
138            panic!("login");
139        }
140
141        let uidl_command = match message_number {
142            Some(i) => format!("UIDL {}\r\n", i),
143            None => format!("UIDL\r\n"),
144        };
145        let command_type = match message_number {
146            Some(_) => UidlOne,
147            None => UidlAll,
148        };
149
150        match self.write_str(&uidl_command).await {
151            Ok(_) => {},
152            Err(_) => panic!("Error writing"),
153        }
154
155        match self.read_response(command_type).await {
156            Ok(res) => {
157                match res.result {
158                    Some(s) => s,
159                    None => POP3Result::POP3Err
160                }
161            },
162            Err(_) => POP3Result::POP3Err
163        }
164    }
165
166	/// List displays a summary of messages where each message number is shown and the size of the message in bytes.
167	pub async fn list(&mut self, message_number: Option<i32>) -> POP3Result {
168		if !self.is_authenticated {
169			panic!("login");
170		}
171
172		let list_command = match message_number {
173							Some(i) => format!("LIST {}\r\n", i),
174							None => format!("LIST\r\n"),
175						};
176		let command_type = match message_number {
177							Some(_) => ListOne,
178							None => ListAll,
179						};
180
181		match self.write_str(&list_command).await {
182			Ok(_) => {},
183			Err(_) => panic!("Error writing"),
184		}
185
186		match self.read_response(command_type).await {
187			Ok(res) => {
188				match res.result {
189					Some(s) => s,
190					None => POP3Result::POP3Err
191				}
192			},
193			Err(_) => POP3Result::POP3Err
194		}
195	}
196
197	/// retrieves the message of the message id given.
198	pub async fn retr(&mut self, message_id: i32) -> POP3Result {
199		if !self.is_authenticated {
200			panic!("login");
201		}
202
203		let retr_command = format!("RETR {}\r\n", message_id);
204
205		match self.write_str(&retr_command).await {
206			Ok(_) => {},
207			Err(_) => panic!("Error writing"),
208		}
209
210		match self.read_response(Retr).await {
211			Ok(res) => {
212				match res.result {
213					Some(s) => s,
214					None => POP3Result::POP3Err
215				}
216			},
217			Err(_) => POP3Result::POP3Err
218		}
219	}
220
221	/// Delete the message with the given message id.
222	pub async fn dele(&mut self, message_id: i32) -> POP3Result {
223		if !self.is_authenticated {
224			panic!("login");
225		}
226
227		let dele_command = format!("DELE {}\r\n", message_id);
228
229		match self.write_str(&dele_command).await {
230			Ok(_) => {},
231			Err(_) => panic!("Error writing"),
232		}
233
234		match self.read_response(Dele).await {
235			Ok(res) => {
236				match res.result {
237					Some(s) => s,
238					None => POP3Result::POP3Err
239				}
240			},
241			Err(_) => POP3Result::POP3Err
242		}
243	}
244
245	/// This resets the session to its original state.
246	pub async fn rset(&mut self) -> POP3Result {
247		if !self.is_authenticated {
248			panic!("Not Logged In");
249		}
250
251		let retr_command = format!("RETR\r\n");
252
253		match self.write_str(&retr_command).await {
254			Ok(_) => {},
255			Err(_) => panic!("Error writing"),
256		}
257
258		match self.read_response(Rset).await {
259			Ok(res) => {
260				match res.result {
261					Some(s) => s,
262					None => POP3Result::POP3Err
263				}
264			},
265			Err(_) => POP3Result::POP3Err
266		}
267	}
268
269	/// Quits the current session.
270	pub async fn quit(&mut self) -> POP3Result {
271		let quit_command = "QUIT\r\n";
272
273		match self.write_str(&quit_command).await {
274			Ok(_) => {},
275			Err(_) => panic!("Error writing"),
276		}
277
278		match self.read_response(Quit).await {
279			Ok(res) => {
280				match res.result {
281					Some(s) => s,
282					None => POP3Result::POP3Err
283				}
284			},
285			Err(_) => POP3Result::POP3Err
286		}
287	}
288
289	/// Doesn't do anything. This is usually just used to keep the connection open.
290	pub async fn noop(&mut self) -> POP3Result {
291		if !self.is_authenticated {
292			panic!("Not Logged In");
293		}
294
295		let noop_command = "noop\r\n";
296
297		match self.write_str(noop_command).await {
298			Ok(_) => {},
299			Err(_) => panic!("Error writing"),
300		}
301
302		match self.read_response(Noop).await {
303			Ok(res) => {
304				match res.result {
305					Some(s) => s,
306					None => POP3Result::POP3Err
307				}
308			},
309			Err(_) => panic!("Error noop")
310		}
311	}
312
313	async fn read_response(&mut self, command: POP3Command) -> Result<Box<POP3Response>> {
314		let mut response = Box::new(POP3Response::new());
315		//Carriage return
316		let cr = 0x0d;
317		//Line Feed
318		let lf = 0x0a;
319		let mut line_buffer: Vec<u8> = Vec::new();
320
321		while !response.complete {
322			while line_buffer.len() < 2 || (line_buffer[line_buffer.len()-1] != lf && line_buffer[line_buffer.len()-2] != cr) {
323				let byte_buffer: &mut [u8] = &mut [0];
324				match self.read(byte_buffer).await {
325					Ok(_) => {},
326					Err(_) => println!("Error Reading!"),
327				}
328				line_buffer.push(byte_buffer[0]);
329			}
330
331			match String::from_utf8(line_buffer.clone()) {
332        		Ok(res) => {
333          			response.add_line(res, command.clone());
334            		line_buffer = Vec::new();
335        		},
336        		Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read the response"))
337      		}
338		}
339		Ok(response)
340	}
341}
342
343#[derive(Clone,Copy,Debug)]
344pub struct POP3EmailMetadata {
345	pub message_id: i32,
346	pub message_size: i32
347}
348
349#[derive(Clone,Debug)]
350pub struct POP3EmailUidldata {
351    pub message_id: i32,
352    pub message_uid: String
353}
354
355#[derive(Debug)]
356pub enum POP3Result {
357	POP3Ok,
358	POP3Err,
359	POP3Stat {
360		num_email: i32,
361		mailbox_size: i32
362	},
363    POP3Uidl {
364        emails_metadata: Vec<POP3EmailUidldata>,
365    },
366	POP3List {
367		emails_metadata: Vec<POP3EmailMetadata>,
368	},
369	POP3Message {
370		raw: Vec<String>,
371	},
372}
373
374#[derive(Default)]
375struct POP3Response {
376	complete: bool,
377	lines: Vec<String>,
378	result: Option<POP3Result>
379}
380
381impl POP3Response {
382	fn new() -> POP3Response {
383		POP3Response {
384			complete: false,
385			lines: Vec::new(),
386			result: None
387		}
388	}
389
390	fn add_line(&mut self, line: String, command: POP3Command) {
391		//We are retreiving status line
392		if self.lines.len() == 0 {
393			if OK_REGEX.is_match(&line) {
394				self.lines.push(line);
395				match command {
396					Greet|User|Pass|Quit|Dele|Rset => {
397						self.result = Some(POP3Result::POP3Ok);
398						self.complete = true;
399					},
400					Stat => {
401						self.complete = true;
402						self.parse_stat()
403					},
404                                    UidlAll => {
405
406                                    },
407                                    UidlOne => {
408                                        self.complete = true;
409                                        self.parse_uidl_one();
410                                    },
411					ListAll => {
412
413					},
414					ListOne => {
415						self.complete = true;
416						self.parse_list_one();
417					},
418					Retr => {
419
420					},
421					_ => self.complete = true,
422				}
423			} else if ERR_REGEX.is_match(&line) {
424				self.lines.push(line);
425				self.result = Some(POP3Result::POP3Err);
426				self.complete = true;
427			}
428		} else {
429			if ENDING_REGEX.is_match(&line) {
430				self.lines.push(line);
431				match command {
432                                    UidlAll => {
433                                        self.complete = true;
434                                        self.parse_uidl_all();
435                                    },
436					ListAll => {
437						self.complete = true;
438						self.parse_list_all();
439					},
440					Retr => {
441						self.complete = true;
442						self.parse_message();
443					},
444					_ => self.complete = true,
445				}
446			} else {
447				self.lines.push(line);
448			}
449		}
450	}
451
452	fn parse_stat(&mut self) {
453		let caps = STAT_REGEX.captures(&self.lines[0]).unwrap();
454		let num_emails = FromStr::from_str(caps.get(1).unwrap().as_str());
455		let total_email_size = FromStr::from_str(caps.get(2).unwrap().as_str());
456		self.result = Some(POP3Result::POP3Stat {
457			num_email: num_emails.unwrap(),
458			mailbox_size: total_email_size.unwrap()
459		})
460	}
461
462
463    fn parse_uidl_all(&mut self) {
464        let mut metadata = Vec::new();
465
466        for i in 1..self.lines.len() - 1 {
467            let caps = MESSAGE_DATA_UIDL_ALL_REGEX.captures(&self.lines[i]).unwrap();
468            let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
469            let message_uid = caps.get(2).unwrap().as_str();
470
471            metadata.push(POP3EmailUidldata {
472                message_id: message_id.unwrap(),
473                message_uid: message_uid.to_owned()
474            });
475        }
476
477        self.result = Some(POP3Result::POP3Uidl {
478            emails_metadata: metadata
479        });
480    }
481
482    fn parse_uidl_one(&mut self) {
483        let caps = MESSAGE_DATA_UIDL_ONE_REGEX.captures(&self.lines[0]).unwrap();
484        let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
485        let message_uid = caps.get(2).unwrap().as_str();
486
487        self.result = Some(POP3Result::POP3Uidl {
488            emails_metadata: vec![POP3EmailUidldata{
489                    message_id: message_id.unwrap(),
490                    message_uid: message_uid.to_owned()
491            }]
492        });
493    }
494
495	fn parse_list_all(&mut self) {
496		let mut metadata = Vec::new();
497
498		for i in 1 .. self.lines.len()-1 {
499			let caps = MESSAGE_DATA_LIST_ALL_REGEX.captures(&self.lines[i]).unwrap();
500			let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
501			let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
502			metadata.push(POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()});
503		}
504		self.result = Some(POP3Result::POP3List {
505			emails_metadata: metadata
506		});
507	}
508
509	fn parse_list_one(&mut self) {
510		let caps = STAT_REGEX.captures(&self.lines[0]).unwrap();
511		let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
512		let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
513		self.result = Some(POP3Result::POP3List {
514			emails_metadata: vec![POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()}]
515		});
516	}
517
518	fn parse_message(&mut self) {
519		let mut raw = Vec::new();
520		for i in 1 .. self.lines.len()-1 {
521			raw.push(self.lines[i].clone());
522		}
523		self.result = Some(POP3Result::POP3Message{
524			raw: raw
525		});
526	}
527}