Improvements on SMTP error handling
This commit is contained in:
@@ -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]) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
17
src/lib.rs
17
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:
|
||||
|
||||
Reference in New Issue
Block a user