Little improvements

This commit is contained in:
Alexis Mousset
2014-04-30 08:54:07 +02:00
parent 19e85d7cf3
commit d89b637d79
3 changed files with 67 additions and 38 deletions

View File

@@ -24,7 +24,7 @@ use commands;
use commands::{SMTP_PORT, SmtpCommand, EsmtpParameter};
/// Contains an SMTP reply, with separed code and message
#[deriving(Eq,Clone)]
#[deriving(Clone)]
pub struct SmtpResponse<T> {
/// Server response code
code: uint,
@@ -71,8 +71,8 @@ impl<T: Clone> SmtpResponse<T> {
}
/// Information about an SMTP server
#[deriving(Eq,Clone)]
pub struct SmtpServerInfo<T> {
#[deriving(Clone)]
struct SmtpServerInfo<T> {
/// Server name
name: T,
/// ESMTP features supported by the server
@@ -89,6 +89,7 @@ impl<T: Show> Show for SmtpServerInfo<T>{
impl<T: Str> SmtpServerInfo<T> {
/// Parses supported ESMTP features
/// TODO: Improve parsing
fn parse_esmtp_response(message: T) -> Option<Vec<EsmtpParameter>> {
let mut esmtp_features = Vec::new();
for line in message.into_owned().split_str(CRLF) {
@@ -121,7 +122,7 @@ impl<T: Str> SmtpServerInfo<T> {
/// Contains the state of the current transaction
#[deriving(Eq,Clone)]
pub enum SmtpClientState {
enum SmtpClientState {
/// The server is unconnected
Unconnected,
/// The connection and banner were successful
@@ -139,7 +140,7 @@ pub enum SmtpClientState {
macro_rules! check_state_in(
($expected_states:expr) => (
if ! $expected_states.contains(&self.state) {
fail!("Wrong transaction state for this command.");
fail!("Bad sequence of commands.");
}
);
)
@@ -147,7 +148,7 @@ macro_rules! check_state_in(
macro_rules! check_state_not_in(
($expected_states:expr) => (
if $expected_states.contains(&self.state) {
fail!("Wrong transaction state for this command.");
fail!("Bad sequence of commands.");
}
);
)
@@ -249,7 +250,7 @@ impl SmtpClient<StrBuf, TcpStream> {
// Checks message encoding according to the server's capability
// TODO : Add an encoding check.
if ! self.server_info.clone().unwrap().supports_feature(commands::EightBitMime) {
if false {
if message.clone().into_owned().is_ascii() {
self.smtp_fail("Server does not accepts UTF-8 strings");
}
}
@@ -259,6 +260,7 @@ impl SmtpClient<StrBuf, TcpStream> {
// Recipient
// TODO Return rejected addresses
// TODO Manage the number of recipients
for to_address in to_addresses.iter() {
smtp_fail_if_err!(self.rcpt(to_address.clone(), None));
}
@@ -276,6 +278,7 @@ impl SmtpClient<StrBuf, TcpStream> {
impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends an SMTP command
// TODO : ensure this is an ASCII string
pub fn send_command(&mut self, command: SmtpCommand<StrBuf>) -> SmtpResponse<StrBuf> {
self.send_and_get_response(format!("{}", command))
}
@@ -333,7 +336,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Send a HELO command
pub fn helo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!([Connected]);
check_state_in!(vec!(Connected));
match self.send_command(commands::Hello(my_hostname.clone())).with_code(vec!(250)) {
Ok(response) => {
@@ -353,7 +356,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a EHLO command
pub fn ehlo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!([Unconnected]);
check_state_not_in!(vec!(Unconnected));
match self.send_command(commands::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
Ok(response) => {
@@ -371,8 +374,8 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
}
/// Sends a MAIL command
pub fn mail(&mut self, from_address: StrBuf, options: Option<StrBuf>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!([HeloSent]);
pub fn mail(&mut self, from_address: StrBuf, options: Option<Vec<StrBuf>>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(HeloSent));
match self.send_command(commands::Mail(from_address, options)).with_code(vec!(250)) {
Ok(response) => {
@@ -386,8 +389,8 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
}
/// Sends a RCPT command
pub fn rcpt(&mut self, to_address: StrBuf, options: Option<StrBuf>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!([MailSent, RcptSent]);
pub fn rcpt(&mut self, to_address: StrBuf, options: Option<Vec<StrBuf>>) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!(vec!(MailSent, RcptSent));
match self.send_command(commands::Recipient(to_address, options)).with_code(vec!(250)) {
Ok(response) => {
@@ -402,7 +405,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a DATA command
pub fn data(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!([RcptSent]);
check_state_in!(vec!(RcptSent));
match self.send_command(commands::Data).with_code(vec!(354)) {
Ok(response) => {
@@ -417,7 +420,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends the message content
pub fn message(&mut self, message_content: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_in!([DataSent]);
check_state_in!(vec!(DataSent));
match self.send_message(message_content).with_code(vec!(250)) {
Ok(response) => {
@@ -433,7 +436,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a QUIT command
pub fn quit(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!([Unconnected]);
check_state_not_in!(vec!(Unconnected));
match self.send_command(commands::Quit).with_code(vec!(221)) {
Ok(response) => {
self.close();
@@ -447,7 +450,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a RSET command
pub fn rset(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!([Unconnected]);
check_state_not_in!(vec!(Unconnected));
match self.send_command(commands::Reset).with_code(vec!(250)) {
Ok(response) => {
if vec!(MailSent, RcptSent, DataSent).contains(&self.state) {
@@ -463,13 +466,13 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
/// Sends a NOOP commands
pub fn noop(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!([Unconnected]);
check_state_not_in!(vec!(Unconnected));
self.send_command(commands::Noop).with_code(vec!(250))
}
/// Sends a VRFY command
pub fn vrfy(&mut self, to_address: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
check_state_not_in!([Unconnected]);
check_state_not_in!(vec!(Unconnected));
self.send_command(commands::Verify(to_address)).with_code(vec!(250))
}
}
@@ -481,6 +484,7 @@ impl<T, S: Reader + Clone> Reader for SmtpClient<T, S> {
}
/// Reads a string from the client socket
// TODO: Manage long messages.
fn read_to_str(&mut self) -> IoResult<~str> {
let mut buf = [0u8, ..1000];

View File

@@ -34,9 +34,9 @@ pub enum SmtpCommand<T> {
/// Hello command
Hello(T),
/// Mail command, takes optionnal options
Mail(T, Option<T>),
Mail(T, Option<Vec<T>>),
/// Recipient command, takes optionnal options
Recipient(T, Option<T>),
Recipient(T, Option<Vec<T>>),
/// Data command
Data,
/// Reset command
@@ -54,7 +54,7 @@ pub enum SmtpCommand<T> {
}
impl<T: Show> Show for SmtpCommand<T> {
impl<T: Show + Str> Show for SmtpCommand<T> {
fn fmt(&self, f: &mut Formatter) -> Result {
f.buf.write(match *self {
ExtendedHello(ref my_hostname) =>
@@ -64,11 +64,11 @@ impl<T: Show> Show for SmtpCommand<T> {
Mail(ref from_address, None) =>
format!("MAIL FROM:{}", from_address.clone()),
Mail(ref from_address, Some(ref options)) =>
format!("MAIL FROM:{} {}", from_address.clone(), options.clone()),
format!("MAIL FROM:{} {}", from_address.clone(), options.connect(" ")),
Recipient(ref to_address, None) =>
format!("RCPT TO:{}", to_address.clone()),
Recipient(ref to_address, Some(ref options)) =>
format!("RCPT TO:{} {}", to_address.clone(), options.clone()),
format!("RCPT TO:{} {}", to_address.clone(), options.connect(" ")),
Data => ~"DATA",
Reset => ~"RSET",
Verify(ref address) =>
@@ -90,22 +90,34 @@ pub enum EsmtpParameter {
/// 8BITMIME keyword
/// RFC 6152 : https://tools.ietf.org/html/rfc6152
EightBitMime,
/// SIZE keyword
/// RFC 1427 : https://tools.ietf.org/html/rfc1427
Size(uint)
}
impl Show for EsmtpParameter {
fn fmt(&self, f: &mut Formatter) -> Result {
f.buf.write(
match self {
&EightBitMime => "8BITMIME".as_bytes()
}
&EightBitMime => ~"8BITMIME",
&Size(ref size) => format!("SIZE={}", size)
}.as_bytes()
)
}
}
impl FromStr for EsmtpParameter {
fn from_str(s: &str) -> Option<EsmtpParameter> {
match s.as_slice() {
"8BITMIME" => Some(EightBitMime),
let splitted : ~[&str] = s.splitn(' ', 1).collect();
match splitted.len() {
1 => match splitted[0] {
"8BITMIME" => Some(EightBitMime),
_ => None
},
2 => match (splitted[0], splitted[1]) {
("SIZE", size) => Some(Size(from_str::<uint>(size).unwrap())),
_ => None
},
_ => None
}
}
@@ -119,16 +131,18 @@ mod test {
fn test_command_fmt() {
//assert!(format!("{}", super::Noop) == ~"NOOP");
assert!(format!("{}", super::ExtendedHello("me")) == ~"EHLO me");
assert!(format!("{}", super::Mail("test", Some("option"))) == ~"MAIL FROM:test option");
assert!(format!("{}", super::Mail("test", Some(vec!("option")))) == ~"MAIL FROM:test option");
}
#[test]
fn test_esmtp_parameter_fmt() {
assert!(format!("{}", super::EightBitMime) == ~"8BITMIME");
assert!(format!("{}", super::Size(42)) == ~"SIZE=42");
}
#[test]
fn test_ehlokeyword_from_str() {
fn test_esmtp_parameter_from_str() {
assert!(from_str::<EsmtpParameter>("8BITMIME") == Some(super::EightBitMime));
assert!(from_str::<EsmtpParameter>("SIZE 42") == Some(super::Size(42)));
}
}

View File

@@ -10,12 +10,23 @@
/*! SMTP library
This library implements a simple SMTP client.
RFC 5321 : https://tools.ietf.org/html/rfc5321#section-4.1
It does NOT manages email content.
# What this client is NOT made for
It also implements the following extesnions
*Send emails to public email servers.* It is not designed to smartly handle servers responses,
to rate-limit emails, to make retries, and all that complicated stuff needed to politely talk to public
servers.
What this client does is basically try once to send the email, and say if it worked. It should be
used to transfer emails to a relay server,
The client tends to follow RFC 5321 (https://tools.ietf.org/html/rfc5321).
This is an SMTP client, and thus does NOT manages email content but only the enveloppe.
It also implements the following extensions :
8BITMIME (RFC 6152 : https://tools.ietf.org/html/rfc6152)
SIZE (RFC 1427 : https://tools.ietf.org/html/rfc1427)
# Usage
@@ -24,17 +35,17 @@ let mut email_client: SmtpClient<StrBuf, TcpStream> = SmtpClient::new(StrBuf::fr
email_client.send_mail(StrBuf::from_str("<user@example.com>"), vec!(StrBuf::from_str("<user@example.org>")), StrBuf::from_str("Test email"));
```
# TODO:
Add SSL/TLS
Add AUTH
# Next steps:
Add SSL/TLS support
Add AUTH support
*/
#![crate_id = "smtp#0.1-pre"]
#![desc = "Rust SMTP client"]
#![comment = "Simple SMTP client"]
#![license = "ASL2"]
#![comment = "Simple SMTP client, without AUTH or SSL/TLS for now"]
#![license = "MIT/ASL2"]
#![crate_type = "lib"]
#![doc(html_root_url = "http://www.rust-ci.org/amousset/rust-smtp/doc/")]