From 1f9f37644169c473609fe233db6b7a39108fb94c Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Mon, 15 Dec 2014 14:51:21 +0100 Subject: [PATCH] Improvements on SMTP error handling --- examples/client.rs | 7 ++-- src/client/mod.rs | 80 +++++++++++++++++++++++++--------------------- src/lib.rs | 17 ++++++++-- 3 files changed, 62 insertions(+), 42 deletions(-) diff --git a/examples/client.rs b/examples/client.rs index a2186c1..862f037 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -25,6 +25,7 @@ use smtp::mailer::Email; fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str, subject: &str, server: &str, port: Port, my_hostname: &str) -> SmtpResult { + let mut email = Email::new(); for destination in recipient_addresses.iter() { email.to(*destination); @@ -34,14 +35,12 @@ fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str, s email.subject(subject); email.date_now(); - let mut email_client = + let mut client = Client::new( (server, port), Some(my_hostname), ); - email_client.send( - email - ) + client.send(email) } fn print_usage(description: String, _opts: &[OptGroup]) { diff --git a/src/client/mod.rs b/src/client/mod.rs index 4115ac4..44fd55a 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -44,20 +44,25 @@ pub struct Client { server_info: Option, /// Transaction state, to check the sequence of commands state: TransactionState, + /// Panic state + panic : bool, } macro_rules! try_smtp ( ($err: expr $client: ident) => ({ match $err { Ok(val) => val, - Err(err) => fail_with_err!(err $client), + Err(err) => close_and_return_err!(err $client), } }) ) -macro_rules! fail_with_err ( +macro_rules! close_and_return_err ( ($err: expr $client: ident) => ({ - $client.close(); + if !$client.panic { + $client.panic = true; + $client.close(); + } return Err(FromError::from_error($err)) }) ) @@ -65,7 +70,7 @@ macro_rules! fail_with_err ( macro_rules! check_command_sequence ( ($command: ident $client: ident) => ({ if !$client.state.is_allowed(&$command) { - fail_with_err!( + close_and_return_err!( Response{code: 503, message: Some("Bad sequence of commands".to_string())} $client ); } @@ -83,6 +88,7 @@ impl Client { my_hostname: my_hostname.unwrap_or("localhost").to_string(), server_info: None, state: TransactionState::new(), + panic: false, } } @@ -95,20 +101,20 @@ impl Client { } impl Client { - /// Closes the TCP stream + /// Closes the SMTP transaction if possible pub fn close(&mut self) { - if self.stream.is_some() { - if self.is_connected() { - let _ = self.quit(); - } - // Close the TCP connection - drop(self.stream.as_mut().unwrap()); - } + let _ = self.quit(); + } + + /// Reset the client state + pub fn reset(&mut self) { + // Close the SMTP transaction if needed + self.close(); - // Reset client state self.stream = None; self.state = TransactionState::new(); self.server_info = None; + self.panic = false; } /// Sends an email @@ -120,25 +126,27 @@ impl Client { let to_addresses = email.to_addresses(); let message = email.message(); - // Connect to the server + // Connect to the server if needed if self.noop().is_err() { + self.reset(); + try!(self.connect()); - } - // Extended Hello or Hello - if let Err(error) = self.ehlo() { - match error.kind { - ErrorKind::PermanentError(Response{code: 550, message: _}) => { - try_smtp!(self.helo() self); - }, - _ => { - try_smtp!(Err(error) self) - }, - }; - } + // Extended Hello or Hello if needed + if let Err(error) = self.ehlo() { + match error.kind { + ErrorKind::PermanentError(Response{code: 550, message: _}) => { + try_smtp!(self.helo() self); + }, + _ => { + try_smtp!(Err(error) self) + }, + }; + } - // Print server information - debug!("server {}", self.server_info.as_ref().unwrap()); + // Print server information + debug!("server {}", self.server_info.as_ref().unwrap()); + } // Mail try_smtp!(self.mail(from_address.as_slice()) self); @@ -148,7 +156,7 @@ impl Client { // Recipient // TODO Return rejected addresses - // TODO Manage the number of recipients + // TODO Limit the number of recipients for to_address in to_addresses.iter() { try_smtp!(self.rcpt(to_address.as_slice()) self); } @@ -173,8 +181,8 @@ impl Client { check_command_sequence!(command self); // Connect should not be called when the client is already connected - if !self.stream.is_none() { - fail_with_err!("The connection is already established" self); + if self.stream.is_some() { + close_and_return_err!("The connection is already established" self); } // Try to connect @@ -195,7 +203,7 @@ impl Client { pub fn send_command(&mut self, command: Command) -> SmtpResult { // for now we do not support SMTPUTF8 if !command.is_ascii() { - fail_with_err!("Non-ASCII string" self); + close_and_return_err!("Non-ASCII string" self); } self.send_server(command, None) @@ -266,7 +274,7 @@ impl Client { let server_info = match self.server_info.clone() { Some(info) => info, - None => fail_with_err!(Response{ + None => close_and_return_err!(Response{ code: 503, message: Some("Bad sequence of commands".to_string()), } self), @@ -301,7 +309,7 @@ impl Client { let server_info = match self.server_info.clone() { Some(info) => info, - None => fail_with_err!(Response{ + None => close_and_return_err!(Response{ code: 503, message: Some("Bad sequence of commands".to_string()), } self) @@ -310,14 +318,14 @@ impl Client { // Check message encoding if !server_info.supports_feature(Extension::EightBitMime).is_some() { if !message_content.is_ascii() { - fail_with_err!("Server does not accepts UTF-8 strings" self); + close_and_return_err!("Server does not accepts UTF-8 strings" self); } } // Get maximum message size if defined and compare to the message size if let Some(Extension::Size(max)) = server_info.supports_feature(Extension::Size(0)) { if message_content.len() > max { - fail_with_err!(Response{ + close_and_return_err!(Response{ code: 552, message: Some("Message exceeds fixed maximum message size".to_string()), } self); diff --git a/src/lib.rs b/src/lib.rs index 24113fb..18d68e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,25 +21,36 @@ //! //! ## Usage //! -//! ### Simple usage +//! ### Simple example +//! +//! This is the most basic example of usage: //! //! ```rust,no_run //! #![feature(default_type_params)] //! use smtp::client::Client; //! use smtp::mailer::Email; //! +//! // Create an email //! let mut email = Email::new(); +//! // Addresses can be specified by the couple (email, alias) //! email.to(("user@example.org", "Firstname Lastname")); +//! // ... or by an address only //! email.from("user@example.com"); //! email.subject("Hello world"); //! email.body("Hi, Hello world."); //! email.date_now(); //! +//! // Open a local connection on port 25 //! let mut client = Client::localhost(); +//! // Send the email //! let result = client.send(email); +//! //! assert!(result.is_ok()); //! ``` //! +//! You can send multiple emails using the same connection by using `send` several times on the +//! same client. If the connection was closed, it will be re-opened. +//! //! ### Complete example //! //! ```rust,no_run @@ -51,6 +62,8 @@ //! email.to(("user@example.org", "Alias name")); //! email.cc(("user@example.net", "Alias name")); //! email.from("no-reply@example.com"); +//! email.from("no-reply@example.eu"); +//! email.sender("no-reply@example.com"); //! email.subject("Hello world"); //! email.body("Hi, Hello world."); //! email.reply_to("contact@example.com"); @@ -64,7 +77,7 @@ //! let result = client.send(email); //! assert!(result.is_ok()); //! ``` - +//! //! ### Using the client directly //! //! If you just want to send an email without using `Email` to provide headers: