Little improvements
This commit is contained in:
@@ -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];
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/")]
|
||||
|
||||
Reference in New Issue
Block a user