use POP3StreamTypes::{Basic, Ssl};
use POP3Command::{Greet, User, Pass, Stat, UidlAll, UidlOne, ListAll, ListOne, Retr, Dele, Noop, Rset, Quit};
use std::string::String;
use async_std::net::{ToSocketAddrs,TcpStream};
use async_native_tls::{TlsStream, TlsConnector};
use std::str::FromStr;
use regex::Regex;
use lazy_static::lazy_static;
use async_std::io::{Error, ErrorKind, Result};
use async_std::prelude::*;
lazy_static! {
static ref ENDING_REGEX: Regex = Regex::new(r"^\.\r\n$").unwrap();
static ref OK_REGEX: Regex = Regex::new(r"\+OK(.*)").unwrap();
static ref ERR_REGEX: Regex = Regex::new(r"-ERR(.*)").unwrap();
static ref STAT_REGEX: Regex = Regex::new(r"\+OK (\d+) (\d+)\r\n").unwrap();
static ref MESSAGE_DATA_UIDL_ALL_REGEX: Regex = Regex::new(r"(\d+) ([\x21-\x7e]+)\r\n").unwrap();
static ref MESSAGE_DATA_UIDL_ONE_REGEX: Regex = Regex::new(r"\+OK (\d+) ([\x21-\x7e]+)\r\n").unwrap();
static ref MESSAGE_DATA_LIST_ALL_REGEX: Regex = Regex::new(r"(\d+) (\d+)\r\n").unwrap();
}
#[derive(Debug)]
enum POP3StreamTypes {
Basic(TcpStream),
Ssl(TlsStream<TcpStream>)
}
#[derive(Debug)]
pub struct POP3Stream {
stream: POP3StreamTypes,
pub is_authenticated: bool
}
#[derive(Clone)]
enum POP3Command {
Greet,
User,
Pass,
Stat,
UidlAll,
UidlOne,
ListAll,
ListOne,
Retr,
Dele,
Noop,
Rset,
Quit
}
impl POP3Stream {
pub async fn connect<A:ToSocketAddrs>(addr: A,ssl_context: Option<TlsConnector>,domain: &str) -> Result<POP3Stream> {
let tcp_stream = TcpStream::connect(addr).await?;
let mut socket = match ssl_context {
Some(context) => POP3Stream {
stream: Ssl(TlsConnector::connect(&context, domain,tcp_stream).await.unwrap()),
is_authenticated: false},
None => POP3Stream {
stream: Basic(tcp_stream),
is_authenticated: false},
};
match socket.read_response(Greet).await {
Ok(_) => (),
Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read greet response"))
}
Ok(socket)
}
async fn write_str(&mut self, s: &str) -> Result<()> {
match self.stream {
Ssl(ref mut stream) => stream.write_fmt(format_args!("{}", s)).await,
Basic(ref mut stream) => stream.write_fmt(format_args!("{}", s)).await,
}
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self.stream {
Ssl(ref mut stream) => stream.read(buf).await,
Basic(ref mut stream) => stream.read(buf).await,
}
}
pub async fn login(&mut self, username: &str, password: &str) -> POP3Result {
let user_command = format!("USER {}\r\n", username);
let pass_command = format!("PASS {}\r\n", password);
match self.write_str(&user_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(User).await {
Ok(_) => {
match self.write_str(&pass_command).await {
Ok(_) => self.is_authenticated = true,
Err(_) => panic!("Error writing"),
}
match self.read_response(Pass).await {
Ok(_) => {
POP3Result::POP3Ok
},
Err(_) => panic!("Failure to use PASS")
}
},
Err(_) => panic!("Failure to use USER")
}
}
pub async fn stat(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let stat_command = "STAT\r\n";
match self.write_str(&stat_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Stat).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn uidl(&mut self, message_number: Option<i32>) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let uidl_command = match message_number {
Some(i) => format!("UIDL {}\r\n", i),
None => format!("UIDL\r\n"),
};
let command_type = match message_number {
Some(_) => UidlOne,
None => UidlAll,
};
match self.write_str(&uidl_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(command_type).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn list(&mut self, message_number: Option<i32>) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let list_command = match message_number {
Some(i) => format!("LIST {}\r\n", i),
None => format!("LIST\r\n"),
};
let command_type = match message_number {
Some(_) => ListOne,
None => ListAll,
};
match self.write_str(&list_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(command_type).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn retr(&mut self, message_id: i32) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let retr_command = format!("RETR {}\r\n", message_id);
match self.write_str(&retr_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Retr).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn dele(&mut self, message_id: i32) -> POP3Result {
if !self.is_authenticated {
panic!("login");
}
let dele_command = format!("DELE {}\r\n", message_id);
match self.write_str(&dele_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Dele).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn rset(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("Not Logged In");
}
let retr_command = format!("RETR\r\n");
match self.write_str(&retr_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Rset).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn quit(&mut self) -> POP3Result {
let quit_command = "QUIT\r\n";
match self.write_str(&quit_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Quit).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => POP3Result::POP3Err
}
}
pub async fn noop(&mut self) -> POP3Result {
if !self.is_authenticated {
panic!("Not Logged In");
}
let noop_command = "noop\r\n";
match self.write_str(noop_command).await {
Ok(_) => {},
Err(_) => panic!("Error writing"),
}
match self.read_response(Noop).await {
Ok(res) => {
match res.result {
Some(s) => s,
None => POP3Result::POP3Err
}
},
Err(_) => panic!("Error noop")
}
}
async fn read_response(&mut self, command: POP3Command) -> Result<Box<POP3Response>> {
let mut response = Box::new(POP3Response::new());
let cr = 0x0d;
let lf = 0x0a;
let mut line_buffer: Vec<u8> = Vec::new();
while !response.complete {
while line_buffer.len() < 2 || (line_buffer[line_buffer.len()-1] != lf && line_buffer[line_buffer.len()-2] != cr) {
let byte_buffer: &mut [u8] = &mut [0];
match self.read(byte_buffer).await {
Ok(_) => {},
Err(_) => println!("Error Reading!"),
}
line_buffer.push(byte_buffer[0]);
}
match String::from_utf8(line_buffer.clone()) {
Ok(res) => {
response.add_line(res, command.clone());
line_buffer = Vec::new();
},
Err(_) => return Err(Error::new(ErrorKind::Other, "Failed to read the response"))
}
}
Ok(response)
}
}
#[derive(Clone,Copy,Debug)]
pub struct POP3EmailMetadata {
pub message_id: i32,
pub message_size: i32
}
#[derive(Clone,Debug)]
pub struct POP3EmailUidldata {
pub message_id: i32,
pub message_uid: String
}
#[derive(Debug)]
pub enum POP3Result {
POP3Ok,
POP3Err,
POP3Stat {
num_email: i32,
mailbox_size: i32
},
POP3Uidl {
emails_metadata: Vec<POP3EmailUidldata>,
},
POP3List {
emails_metadata: Vec<POP3EmailMetadata>,
},
POP3Message {
raw: Vec<String>,
},
}
#[derive(Default)]
struct POP3Response {
complete: bool,
lines: Vec<String>,
result: Option<POP3Result>
}
impl POP3Response {
fn new() -> POP3Response {
POP3Response {
complete: false,
lines: Vec::new(),
result: None
}
}
fn add_line(&mut self, line: String, command: POP3Command) {
if self.lines.len() == 0 {
if OK_REGEX.is_match(&line) {
self.lines.push(line);
match command {
Greet|User|Pass|Quit|Dele|Rset => {
self.result = Some(POP3Result::POP3Ok);
self.complete = true;
},
Stat => {
self.complete = true;
self.parse_stat()
},
UidlAll => {
},
UidlOne => {
self.complete = true;
self.parse_uidl_one();
},
ListAll => {
},
ListOne => {
self.complete = true;
self.parse_list_one();
},
Retr => {
},
_ => self.complete = true,
}
} else if ERR_REGEX.is_match(&line) {
self.lines.push(line);
self.result = Some(POP3Result::POP3Err);
self.complete = true;
}
} else {
if ENDING_REGEX.is_match(&line) {
self.lines.push(line);
match command {
UidlAll => {
self.complete = true;
self.parse_uidl_all();
},
ListAll => {
self.complete = true;
self.parse_list_all();
},
Retr => {
self.complete = true;
self.parse_message();
},
_ => self.complete = true,
}
} else {
self.lines.push(line);
}
}
}
fn parse_stat(&mut self) {
let caps = STAT_REGEX.captures(&self.lines[0]).unwrap();
let num_emails = FromStr::from_str(caps.get(1).unwrap().as_str());
let total_email_size = FromStr::from_str(caps.get(2).unwrap().as_str());
self.result = Some(POP3Result::POP3Stat {
num_email: num_emails.unwrap(),
mailbox_size: total_email_size.unwrap()
})
}
fn parse_uidl_all(&mut self) {
let mut metadata = Vec::new();
for i in 1..self.lines.len() - 1 {
let caps = MESSAGE_DATA_UIDL_ALL_REGEX.captures(&self.lines[i]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_uid = caps.get(2).unwrap().as_str();
metadata.push(POP3EmailUidldata {
message_id: message_id.unwrap(),
message_uid: message_uid.to_owned()
});
}
self.result = Some(POP3Result::POP3Uidl {
emails_metadata: metadata
});
}
fn parse_uidl_one(&mut self) {
let caps = MESSAGE_DATA_UIDL_ONE_REGEX.captures(&self.lines[0]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_uid = caps.get(2).unwrap().as_str();
self.result = Some(POP3Result::POP3Uidl {
emails_metadata: vec![POP3EmailUidldata{
message_id: message_id.unwrap(),
message_uid: message_uid.to_owned()
}]
});
}
fn parse_list_all(&mut self) {
let mut metadata = Vec::new();
for i in 1 .. self.lines.len()-1 {
let caps = MESSAGE_DATA_LIST_ALL_REGEX.captures(&self.lines[i]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
metadata.push(POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()});
}
self.result = Some(POP3Result::POP3List {
emails_metadata: metadata
});
}
fn parse_list_one(&mut self) {
let caps = STAT_REGEX.captures(&self.lines[0]).unwrap();
let message_id = FromStr::from_str(caps.get(1).unwrap().as_str());
let message_size = FromStr::from_str(caps.get(2).unwrap().as_str());
self.result = Some(POP3Result::POP3List {
emails_metadata: vec![POP3EmailMetadata{ message_id: message_id.unwrap(), message_size: message_size.unwrap()}]
});
}
fn parse_message(&mut self) {
let mut raw = Vec::new();
for i in 1 .. self.lines.len()-1 {
raw.push(self.lines[i].clone());
}
self.result = Some(POP3Result::POP3Message{
raw: raw
});
}
}