From 94d79862ff3d1661a12bd97e8331767c41f323b5 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Fri, 7 Nov 2014 13:48:40 +0100 Subject: [PATCH] Error handling --- examples/client.rs | 2 +- src/client/mod.rs | 120 +++++++++++++++++++++---------------------- src/client/stream.rs | 32 ++++++------ src/lib.rs | 13 +++-- src/response.rs | 15 ++++-- 5 files changed, 92 insertions(+), 90 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index 892a21b..cc4547c 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -23,7 +23,7 @@ fn sendmail(source_address: String, recipient_addresses: Vec, message: S server, port, my_hostname); - email_client.send_mail::( + let result = email_client.send_mail::( source_address, recipient_addresses, message diff --git a/src/client/mod.rs b/src/client/mod.rs index 8da2f36..beca02c 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -10,7 +10,6 @@ //! SMTP client use std::fmt::Show; -use std::result::Result; use std::string::String; use std::io::net::ip::Port; @@ -23,6 +22,7 @@ use command; use command::Command; use transaction; use transaction::TransactionState; +use error::{SmtpResult, SmtpError, ErrorKind}; use client::connecter::Connecter; use client::server_info::ServerInfo; use client::stream::ClientStream; @@ -66,10 +66,10 @@ impl Client { impl Client { /// TODO - fn smtp_fail_if_err(&mut self, response: Result) { + fn smtp_fail_if_err(&mut self, response: SmtpResult) { match response { Err(response) => { - self.smtp_fail::(response) + self.smtp_fail::(response) }, Ok(_) => {} } @@ -90,27 +90,24 @@ impl Client { /// Sends an email pub fn send_mail(&mut self, from_address: String, - to_addresses: Vec, message: String) { + to_addresses: Vec, message: String) -> SmtpResult { let my_hostname = self.my_hostname.clone(); - let mut smtp_result: Result; + let mut smtp_result: SmtpResult; - match self.connect() { - Ok(_) => {}, - Err(response) => panic!("Cannot connect to {}:{}. Server says: {}", - self.host, - self.port, response - ) - } + // Connect to the server + try!(self.connect()); // Extended Hello or Hello match self.ehlo::(my_hostname.clone().to_string()) { - Err(Response{code: 550, message: _}) => { - smtp_result = self.helo::(my_hostname.clone()); - self.smtp_fail_if_err::(smtp_result); - }, - Err(response) => { - self.smtp_fail::(response) - } + Err(error) => match error.kind { + ErrorKind::PermanentError(Response{code: 550, message: _}) => { + smtp_result = self.helo::(my_hostname.clone()); + self.smtp_fail_if_err::(smtp_result); + }, + _ => { + self.smtp_fail::(error) + } + }, _ => {} } @@ -139,7 +136,7 @@ impl Client { let sent = self.message::(message.as_slice()); if sent.clone().is_err() { - self.smtp_fail::(sent.clone().err().unwrap()) + self.smtp_fail::(sent.clone().err().unwrap()) } info!("to=<{}>, status=sent ({})", @@ -148,11 +145,12 @@ impl Client { // Quit smtp_result = self.quit::(); self.smtp_fail_if_err::(smtp_result); + return Ok(Response{code:100, message:None}); } /// Sends an SMTP command // TODO : ensure this is an ASCII string - fn send_command(&mut self, command: Command) -> Response { + fn send_command(&mut self, command: Command) -> SmtpResult { if !command.is_ascii() { panic!("Non-ASCII string: {}", command); } @@ -164,40 +162,29 @@ impl Client { } /// Sends the email content - fn send_message(&mut self, message: &str) -> Response { + fn send_message(&mut self, message: &str) -> SmtpResult { self.stream.clone().unwrap().send_and_get_response(format!("{}", message).as_slice(), format!("{}.{}", CRLF, CRLF).as_slice()) } /// Connects to the configured server - pub fn connect(&mut self) -> Result { + pub fn connect(&mut self) -> SmtpResult { // connect should not be called when the client is already connected if !self.stream.is_none() { panic!("The connection is already established"); } // Try to connect - self.stream = match Connecter::connect(self.host.clone().as_slice(), self.port) { - Ok(stream) => Some(stream), - Err(..) => panic!("Cannot connect to the server") - }; + self.stream = Some(try!(Connecter::connect(self.host.clone().as_slice(), self.port))); // Log the connection info!("Connection established to {}[{}]:{}", self.host, self.stream.clone().unwrap().peer_name().unwrap().ip, self.port); - match self.stream.clone().unwrap().get_reply() { - Some(response) => match response.with_code(vec!(220)) { - Ok(response) => { - self.state = transaction::Connected; - Ok(response) - }, - Err(response) => { - Err(response) - } - }, - None => panic!("No banner on {}", self.host) - } + let response = try!(self.stream.clone().unwrap().get_reply()); + let result = try!(response.with_code(vec!(220))); + self.state = transaction::Connected; + Ok(result) } /// Checks if the server is connected @@ -216,8 +203,9 @@ impl Client { } /// Send a HELO command - pub fn helo(&mut self, my_hostname: String) -> Result { - match self.send_command(command::Hello(my_hostname.clone())).with_code(vec!(250)) { + pub fn helo(&mut self, my_hostname: String) -> SmtpResult { + let res = try!(self.send_command(command::Hello(my_hostname.clone()))); + match res.with_code(vec!(250)) { Ok(response) => { self.server_info = Some( ServerInfo{ @@ -233,8 +221,9 @@ impl Client { } /// Sends a EHLO command - pub fn ehlo(&mut self, my_hostname: String) -> Result { - match self.send_command(command::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) { + pub fn ehlo(&mut self, my_hostname: String) -> SmtpResult { + let res = try!(self.send_command(command::ExtendedHello(my_hostname.clone()))); + match res.with_code(vec!(250)) { Ok(response) => { self.server_info = Some( ServerInfo{ @@ -253,10 +242,11 @@ impl Client { /// Sends a MAIL command pub fn mail(&mut self, from_address: String, - options: Option>) -> Result { - match self.send_command( + options: Option>) -> SmtpResult { + let res = try!(self.send_command( command::Mail(unquote_email_address(from_address.as_slice()).to_string(), options) - ).with_code(vec!(250)) { + )); + match res.with_code(vec!(250)) { Ok(response) => { self.state = transaction::MailSent; Ok(response) @@ -269,10 +259,11 @@ impl Client { /// Sends a RCPT command pub fn rcpt(&mut self, to_address: String, - options: Option>) -> Result { - match self.send_command( + options: Option>) -> SmtpResult { + let res = try!(self.send_command( command::Recipient(unquote_email_address(to_address.as_slice()).to_string(), options) - ).with_code(vec!(250)) { + )); + match res.with_code(vec!(250)) { Ok(response) => { self.state = transaction::RecipientSent; Ok(response) @@ -284,8 +275,9 @@ impl Client { } /// Sends a DATA command - pub fn data(&mut self) -> Result { - match self.send_command(command::Data).with_code(vec!(354)) { + pub fn data(&mut self) -> SmtpResult { + let res = try!(self.send_command(command::Data)); + match res.with_code(vec!(354)) { Ok(response) => { self.state = transaction::DataSent; Ok(response) @@ -297,7 +289,7 @@ impl Client { } /// Sends the message content - pub fn message(&mut self, message_content: &str) -> Result { + pub fn message(&mut self, message_content: &str) -> SmtpResult { let server_info = self.server_info.clone().expect("Bad command sequence"); // Get maximum message size if defined and compare to the message size match server_info.supports_feature(extension::Size(0)) { @@ -314,7 +306,8 @@ impl Client { } } - match self.send_message(message_content).with_code(vec!(250)) { + let res = try!(self.send_message(message_content)); + match res.with_code(vec!(250)) { Ok(response) => { self.state = transaction::HelloSent; Ok(response) @@ -326,8 +319,9 @@ impl Client { } /// Sends a QUIT command - pub fn quit(&mut self) -> Result { - match self.send_command(command::Quit).with_code(vec!(221)) { + pub fn quit(&mut self) -> SmtpResult { + let res = try!(self.send_command(command::Quit)); + match res.with_code(vec!(221)) { Ok(response) => { Ok(response) }, @@ -338,8 +332,9 @@ impl Client { } /// Sends a RSET command - pub fn rset(&mut self) -> Result { - match self.send_command(command::Reset).with_code(vec!(250)) { + pub fn rset(&mut self) -> SmtpResult { + let res = try!(self.send_command(command::Reset)); + match res.with_code(vec!(250)) { Ok(response) => { if vec!(transaction::MailSent, transaction::RecipientSent, transaction::DataSent).contains(&self.state) { @@ -354,12 +349,15 @@ impl Client { } /// Sends a NOOP commands - pub fn noop(&mut self) -> Result { - self.send_command(command::Noop).with_code(vec!(250)) + pub fn noop(&mut self) -> SmtpResult { + let res = try!(self.send_command(command::Noop)); + res.with_code(vec!(250)) } /// Sends a VRFY command - pub fn vrfy(&mut self, to_address: String) -> Result { - self.send_command(command::Verify(to_address, None)).with_code(vec!(250)) + pub fn vrfy(&mut self, to_address: String) -> SmtpResult { + let res = try!(self.send_command(command::Verify(to_address, None))); + res.with_code(vec!(250)) } + } diff --git a/src/client/stream.rs b/src/client/stream.rs index 3bf9bf0..c6e41e5 100644 --- a/src/client/stream.rs +++ b/src/client/stream.rs @@ -13,6 +13,8 @@ use std::io::net::tcp::TcpStream; use std::io::IoResult; use std::str::from_utf8; use std::vec::Vec; +use error::SmtpResult; +use std::error::FromError; use response::Response; use common::{escape_crlf, escape_dot}; @@ -22,25 +24,21 @@ static BUFFER_SIZE: uint = 1024; /// TODO pub trait ClientStream { /// TODO - fn send_and_get_response(&mut self, string: &str, end: &str) -> Response; + fn send_and_get_response(&mut self, string: &str, end: &str) -> SmtpResult; /// TODO - fn get_reply(&mut self) -> Option; + fn get_reply(&mut self) -> SmtpResult; /// TODO fn read_into_string(&mut self) -> IoResult; } impl ClientStream for TcpStream { /// Sends a complete message or a command to the server and get the response - fn send_and_get_response(&mut self, string: &str, end: &str) -> Response { - match self.write_str(format!("{}{}", escape_dot(string), end).as_slice()) { - Ok(..) => debug!("Wrote: {}", escape_crlf(escape_dot(string).as_slice())), - Err(..) => panic!("Could not write to stream") - } + fn send_and_get_response(&mut self, string: &str, end: &str) -> SmtpResult { + try!(self.write_str(format!("{}{}", escape_dot(string), end).as_slice())); - match self.get_reply() { - Some(response) => response, - None => panic!("No answer") - } + debug!("Wrote: {}", escape_crlf(escape_dot(string).as_slice())); + + self.get_reply() } /// Reads on the stream into a string @@ -71,11 +69,11 @@ impl ClientStream for TcpStream { } /// Gets the SMTP response - fn get_reply(&mut self) -> Option { - let response = match self.read_into_string() { - Ok(string) => string, - Err(..) => panic!("No answer") - }; - from_str::(response.as_slice()) + fn get_reply(&mut self) -> SmtpResult { + let response = try!(self.read_into_string()); + match from_str::(response.as_slice()) { + Some(response) => Ok(response), + None => Err(FromError::from_error("Could not parse response")) + } } } diff --git a/src/lib.rs b/src/lib.rs index 2265509..2d73e40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,15 +27,13 @@ //! //! ## Usage //! -//! ```ignore -//! extern crate smtp; +//! ```rust,no_run //! use std::io::net::tcp::TcpStream; -//! use smtp::smtpc::client::SmtpClient; -//! use std::string::String; +//! use smtp::client::Client; //! -//! let mut email_client: SmtpClient = -//! SmtpClient::new("localhost".to_string(), None, None); -//! email_client.send_mail::( +//! let mut email_client: Client = +//! Client::new("localhost".to_string(), None, None); +//! let result = email_client.send_mail::( //! "user@example.com".to_string(), //! vec!("user@example.org".to_string()), //! "Test email".to_string() @@ -61,3 +59,4 @@ pub mod extension; pub mod response; pub mod transaction; pub mod common; +pub mod error; diff --git a/src/response.rs b/src/response.rs index 84cb918..44b5f98 100644 --- a/src/response.rs +++ b/src/response.rs @@ -7,15 +7,17 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! SMTP responses, contaiing a mandatory return code, and an optional text message +//! SMTP responses, containing a mandatory return code, and an optional text message #![unstable] use std::from_str::FromStr; use std::fmt::{Show, Formatter, Result}; use std::result; +use std::error::FromError; use common::remove_trailing_crlf; +use error::SmtpError; /// Contains an SMTP reply, with separed code and message /// @@ -76,19 +78,24 @@ impl FromStr for Response { impl Response { /// Checks the presence of the response code in the array of expected codes. pub fn with_code(&self, - expected_codes: Vec) -> result::Result { + expected_codes: Vec) -> result::Result { let response = self.clone(); if expected_codes.contains(&self.code) { Ok(response) } else { - Err(response) + Err(FromError::from_error(response)) } } + + /// TODO + //pub fn error_kind(&self) -> Option(SmtpError) {} + } #[cfg(test)] mod test { use super::Response; + use std::error::FromError; #[test] fn test_fmt() { @@ -144,7 +151,7 @@ mod test { ); assert_eq!( Response{code: 400, message: Some("message".to_string())}.with_code(vec!(200)), - Err(Response{code: 400, message: Some("message".to_string())}) + Err(FromError::from_error(Response{code: 400, message: Some("message".to_string())})) ); assert_eq!( Response{