Error handling

This commit is contained in:
Alexis Mousset
2014-11-07 13:48:40 +01:00
parent 492d9bf0af
commit 94d79862ff
5 changed files with 92 additions and 90 deletions

View File

@@ -23,7 +23,7 @@ fn sendmail(source_address: String, recipient_addresses: Vec<String>, message: S
server,
port,
my_hostname);
email_client.send_mail::<TcpStream>(
let result = email_client.send_mail::<TcpStream>(
source_address,
recipient_addresses,
message

View File

@@ -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<S> Client<S> {
impl<S: Connecter + ClientStream + Clone> Client<S> {
/// TODO
fn smtp_fail_if_err<S>(&mut self, response: Result<Response, Response>) {
fn smtp_fail_if_err<S>(&mut self, response: SmtpResult<Response>) {
match response {
Err(response) => {
self.smtp_fail::<S, Response>(response)
self.smtp_fail::<S, SmtpError>(response)
},
Ok(_) => {}
}
@@ -90,27 +90,24 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
/// Sends an email
pub fn send_mail<S>(&mut self, from_address: String,
to_addresses: Vec<String>, message: String) {
to_addresses: Vec<String>, message: String) -> SmtpResult<Response> {
let my_hostname = self.my_hostname.clone();
let mut smtp_result: Result<Response, Response>;
let mut smtp_result: SmtpResult<Response>;
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::<S>(my_hostname.clone().to_string()) {
Err(Response{code: 550, message: _}) => {
smtp_result = self.helo::<S>(my_hostname.clone());
self.smtp_fail_if_err::<S>(smtp_result);
},
Err(response) => {
self.smtp_fail::<S, Response>(response)
}
Err(error) => match error.kind {
ErrorKind::PermanentError(Response{code: 550, message: _}) => {
smtp_result = self.helo::<S>(my_hostname.clone());
self.smtp_fail_if_err::<S>(smtp_result);
},
_ => {
self.smtp_fail::<S, SmtpError>(error)
}
},
_ => {}
}
@@ -139,7 +136,7 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
let sent = self.message::<S>(message.as_slice());
if sent.clone().is_err() {
self.smtp_fail::<S, Response>(sent.clone().err().unwrap())
self.smtp_fail::<S, SmtpError>(sent.clone().err().unwrap())
}
info!("to=<{}>, status=sent ({})",
@@ -148,11 +145,12 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
// Quit
smtp_result = self.quit::<S>();
self.smtp_fail_if_err::<S>(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<Response> {
if !command.is_ascii() {
panic!("Non-ASCII string: {}", command);
}
@@ -164,40 +162,29 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends the email content
fn send_message(&mut self, message: &str) -> Response {
fn send_message(&mut self, message: &str) -> SmtpResult<Response> {
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<Response, Response> {
pub fn connect(&mut self) -> SmtpResult<Response> {
// 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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Send a HELO command
pub fn helo<S>(&mut self, my_hostname: String) -> Result<Response, Response> {
match self.send_command(command::Hello(my_hostname.clone())).with_code(vec!(250)) {
pub fn helo<S>(&mut self, my_hostname: String) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends a EHLO command
pub fn ehlo<S>(&mut self, my_hostname: String) -> Result<Response, Response> {
match self.send_command(command::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
pub fn ehlo<S>(&mut self, my_hostname: String) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
/// Sends a MAIL command
pub fn mail<S>(&mut self, from_address: String,
options: Option<Vec<String>>) -> Result<Response, Response> {
match self.send_command(
options: Option<Vec<String>>) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
/// Sends a RCPT command
pub fn rcpt<S>(&mut self, to_address: String,
options: Option<Vec<String>>) -> Result<Response, Response> {
match self.send_command(
options: Option<Vec<String>>) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends a DATA command
pub fn data<S>(&mut self) -> Result<Response, Response> {
match self.send_command(command::Data).with_code(vec!(354)) {
pub fn data<S>(&mut self) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends the message content
pub fn message<S>(&mut self, message_content: &str) -> Result<Response, Response> {
pub fn message<S>(&mut self, message_content: &str) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
}
}
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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends a QUIT command
pub fn quit<S>(&mut self) -> Result<Response, Response> {
match self.send_command(command::Quit).with_code(vec!(221)) {
pub fn quit<S>(&mut self) -> SmtpResult<Response> {
let res = try!(self.send_command(command::Quit));
match res.with_code(vec!(221)) {
Ok(response) => {
Ok(response)
},
@@ -338,8 +332,9 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends a RSET command
pub fn rset<S>(&mut self) -> Result<Response, Response> {
match self.send_command(command::Reset).with_code(vec!(250)) {
pub fn rset<S>(&mut self) -> SmtpResult<Response> {
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<S: Connecter + ClientStream + Clone> Client<S> {
}
/// Sends a NOOP commands
pub fn noop<S>(&mut self) -> Result<Response, Response> {
self.send_command(command::Noop).with_code(vec!(250))
pub fn noop<S>(&mut self) -> SmtpResult<Response> {
let res = try!(self.send_command(command::Noop));
res.with_code(vec!(250))
}
/// Sends a VRFY command
pub fn vrfy<S, T>(&mut self, to_address: String) -> Result<Response, Response> {
self.send_command(command::Verify(to_address, None)).with_code(vec!(250))
pub fn vrfy<S, T>(&mut self, to_address: String) -> SmtpResult<Response> {
let res = try!(self.send_command(command::Verify(to_address, None)));
res.with_code(vec!(250))
}
}

View File

@@ -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<Response>;
/// TODO
fn get_reply(&mut self) -> Option<Response>;
fn get_reply(&mut self) -> SmtpResult<Response>;
/// TODO
fn read_into_string(&mut self) -> IoResult<String>;
}
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<Response> {
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<Response> {
let response = match self.read_into_string() {
Ok(string) => string,
Err(..) => panic!("No answer")
};
from_str::<Response>(response.as_slice())
fn get_reply(&mut self) -> SmtpResult<Response> {
let response = try!(self.read_into_string());
match from_str::<Response>(response.as_slice()) {
Some(response) => Ok(response),
None => Err(FromError::from_error("Could not parse response"))
}
}
}

View File

@@ -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<TcpStream> =
//! SmtpClient::new("localhost".to_string(), None, None);
//! email_client.send_mail::<TcpStream>(
//! let mut email_client: Client<TcpStream> =
//! Client::new("localhost".to_string(), None, None);
//! let result = email_client.send_mail::<TcpStream>(
//! "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;

View File

@@ -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<u16>) -> result::Result<Response,Response> {
expected_codes: Vec<u16>) -> result::Result<Response, SmtpError> {
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{