Improvements on SMTP error handling

This commit is contained in:
Alexis Mousset
2014-12-15 14:51:21 +01:00
parent 74156cb099
commit 1f9f376441
3 changed files with 62 additions and 42 deletions

View File

@@ -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]) {

View File

@@ -44,20 +44,25 @@ pub struct Client<S = TcpStream> {
server_info: Option<ServerInfo>,
/// 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<S = TcpStream> Client<S> {
my_hostname: my_hostname.unwrap_or("localhost").to_string(),
server_info: None,
state: TransactionState::new(),
panic: false,
}
}
@@ -95,20 +101,20 @@ impl<S = TcpStream> Client<S> {
}
impl<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
/// 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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
// 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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
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<S: Connecter + ClientStream + Clone = TcpStream> Client<S> {
// 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);

View File

@@ -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: