From 8a1162ada4cddd43c9c8a223459f4c5469ef442c Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Tue, 28 Jun 2016 21:48:25 -0400 Subject: [PATCH] Initial testing for authentication --- Cargo.toml | 3 +++ examples/gmail_oauth2.rs | 47 ++++++++++++++++++++++++++++++++++++++++ src/client.rs | 43 +++++++++++++++++++++++------------- src/error.rs | 2 +- src/parse.rs | 12 ++++------ 5 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 examples/gmail_oauth2.rs diff --git a/Cargo.toml b/Cargo.toml index 3a2a0f0..75584b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,6 @@ path = "src/lib.rs" [dependencies] openssl = "0.7.13" regex = "0.1.71" + +[dev-dependencies] +base64 = "0.2.0" diff --git a/examples/gmail_oauth2.rs b/examples/gmail_oauth2.rs new file mode 100644 index 0000000..381b4f0 --- /dev/null +++ b/examples/gmail_oauth2.rs @@ -0,0 +1,47 @@ +extern crate imap; +extern crate openssl; +extern crate base64; + +use openssl::ssl::{SslContext, SslMethod}; +use base64::{encode, decode}; +use imap::client::Client; +use imap::authenticator::Authenticator; + +struct GmailOAuth2 { + user: String, + access_token: String +} + +impl Authenticator for GmailOAuth2 { + fn process(&self, data: String) -> String { + String::from("dXNlcj1tYXR0bWNjb3kxMTBAZ21haWwuY29tAWF1dGg9QmVhcmVyIHlhMjkuQ2k4UUEzQ1Y5SW1OQ0Z1NDNpbkZRcngtSUR0cjVFSkZHNXdEM1IySzBXdTdiM1dzVG1Md") + } +} + +fn main() { + let mut gmail_auth = GmailOAuth2{ + user: String::from("email@gmail.com"), + access_token: String::from("") + }; + let mut imap_socket = Client::secure_connect(("imap.gmail.com", 993), SslContext::new(SslMethod::Sslv23).unwrap()).unwrap(); + + imap_socket.authenticate("XOAUTH2", gmail_auth).unwrap(); + + match imap_socket.select("INBOX") { + Ok(mailbox) => { + println!("{}", mailbox); + }, + Err(e) => println!("Error selecting INBOX: {}", e) + }; + + match imap_socket.fetch("2", "body[text]") { + Ok(lines) => { + for line in lines.iter() { + print!("{}", line); + } + }, + Err(e) => println!("Error Fetching email 2: {}", e) + }; + + imap_socket.logout().unwrap(); +} diff --git a/src/client.rs b/src/client.rs index 0f5e98d..4954172 100644 --- a/src/client.rs +++ b/src/client.rs @@ -61,14 +61,20 @@ impl Client { } pub fn authenticate(&mut self, auth_type: &str, authenticator: A) -> Result<()> { - match self.run_command(&format!("AUTHENTICATE {}\r\n", auth_type).to_string()) { - Ok(lines) => { + match self.run_command(&format!("AUTHENTICATE {}", auth_type).to_string()) { + Ok(_) => { + let line = match self.readline() { + Ok(l) => l, + Err(e) => return Err(e) + }; // TODO test this for the many authentication use cases - let data = match parse_authenticate_response(lines) { + let data = match parse_authenticate_response(String::from_utf8(line).unwrap()) { Ok(d) => d, Err(e) => return Err(e) }; + println!("Done parsing authenticating response"); let auth_response = authenticator.process(data); + println!("Writing: {}", auth_response.clone()); match self.stream.write_all(auth_response.into_bytes().as_slice()) { Err(e) => return Err(Error::Io(e)), _ => {} @@ -79,7 +85,7 @@ impl Client { }; match self.read_response() { Ok(_) => Ok(()), - Err(e) => return Err(e) + Err(e) => Err(e) } }, Err(e) => Err(e) @@ -93,7 +99,7 @@ impl Client { /// Selects a mailbox pub fn select(&mut self, mailbox_name: &str) -> Result { - match self.run_command(&format!("SELECT {}", mailbox_name).to_string()) { + match self.run_command_and_read_response(&format!("SELECT {}", mailbox_name).to_string()) { Ok(lines) => parse_select_or_examine(lines), Err(e) => Err(e) } @@ -101,7 +107,7 @@ impl Client { /// Examine is identical to Select, but the selected mailbox is identified as read-only pub fn examine(&mut self, mailbox_name: &str) -> Result { - match self.run_command(&format!("EXAMINE {}", mailbox_name).to_string()) { + match self.run_command_and_read_response(&format!("EXAMINE {}", mailbox_name).to_string()) { Ok(lines) => parse_select_or_examine(lines), Err(e) => Err(e) } @@ -109,7 +115,7 @@ impl Client { /// Fetch retreives data associated with a message in the mailbox. pub fn fetch(&mut self, sequence_set: &str, query: &str) -> Result> { - self.run_command(&format!("FETCH {} {}", sequence_set, query).to_string()) + self.run_command_and_read_response(&format!("FETCH {} {}", sequence_set, query).to_string()) } /// Noop always succeeds, and it does nothing. @@ -151,7 +157,7 @@ impl Client { /// Capability requests a listing of capabilities that the server supports. pub fn capability(&mut self) -> Result> { - match self.run_command(&format!("CAPABILITY").to_string()) { + match self.run_command_and_read_response(&format!("CAPABILITY").to_string()) { Ok(lines) => parse_capability(lines), Err(e) => Err(e) } @@ -198,7 +204,7 @@ impl Client { /// Runs a command and checks if it returns OK. pub fn run_command_and_check_ok(&mut self, command: &str) -> Result<()> { - match self.run_command(command) { + match self.run_command_and_read_response(command) { Ok(lines) => parse_response_ok(lines), Err(e) => Err(e) } @@ -206,22 +212,27 @@ impl Client { // Run a command and parse the status response. pub fn run_command_and_parse(&mut self, command: &str) -> Result> { - match self.run_command(command) { + match self.run_command_and_read_response(command) { Ok(lines) => parse_response(lines), Err(e) => Err(e) } } /// Runs any command passed to it. - pub fn run_command(&mut self, untagged_command: &str) -> Result> { + pub fn run_command(&mut self, untagged_command: &str) -> Result<()> { let command = self.create_command(untagged_command.to_string()); match self.stream.write_fmt(format_args!("{}", &*command)) { - Ok(_) => (), - Err(_) => return Err(Error::Io(io::Error::new(io::ErrorKind::Other, "Failed to write"))), - }; + Ok(_) => Ok(()), + Err(_) => Err(Error::Io(io::Error::new(io::ErrorKind::Other, "Failed to write"))), + } + } - self.read_response() + pub fn run_command_and_read_response(&mut self, untagged_command: &str) -> Result> { + match self.run_command(untagged_command) { + Ok(_) => self.read_response(), + Err(e) => Err(e) + } } fn read_response(&mut self) -> Result> { @@ -265,8 +276,10 @@ impl Client { Ok(_) => {}, Err(_) => return Err(Error::Io(io::Error::new(io::ErrorKind::Other, "Failed to read line"))), } + print!("{}", String::from_utf8_lossy(byte_buffer)); line_buffer.push(byte_buffer[0]); } + println!("{}", String::from_utf8(line_buffer.clone()).unwrap()); Ok(line_buffer) } diff --git a/src/error.rs b/src/error.rs index 40a75a4..d9e8e84 100644 --- a/src/error.rs +++ b/src/error.rs @@ -71,7 +71,7 @@ pub enum ParseError { // Error parsing the cabability response. Capability(Vec), // Authentication errors. - Authentication(Vec) + Authentication(String) } impl fmt::Display for ParseError { diff --git a/src/parse.rs b/src/parse.rs index 38e2d34..b421e36 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -3,19 +3,15 @@ use regex::Regex; use super::mailbox::Mailbox; use super::error::{Error, ParseError, Result}; -pub fn parse_authenticate_response(lines: Vec) -> Result { - let authenticate_regex = Regex::new("^+ (.*)\r\n").unwrap(); - let last_line = match lines.last() { - Some(l) => l, - None => return Err(Error::Parse(ParseError::Authentication(lines.clone()))) - }; +pub fn parse_authenticate_response(line: String) -> Result { + let authenticate_regex = Regex::new("^+(.*)\r\n").unwrap(); - for cap in authenticate_regex.captures_iter(last_line) { + for cap in authenticate_regex.captures_iter(line.as_str()) { let data = cap.at(1).unwrap_or(""); return Ok(String::from(data)); } - Err(Error::Parse(ParseError::Authentication(lines.clone()))) + Err(Error::Parse(ParseError::Authentication(line))) } pub fn parse_capability(lines: Vec) -> Result> {