Follow master

This commit is contained in:
Alexis Mousset
2014-03-30 16:20:58 +02:00
parent 1cc1a462ae
commit 0d9937cf91
3 changed files with 121 additions and 120 deletions

View File

@@ -11,14 +11,16 @@ email_client.send_mail("user@example.org", [&"user@localhost"], "Message content
# TODO
Think about RFC compliance in the SMTP library.
Support ESMTP : Parse server answer, and manage mail and rcpt options.
* Client options: `mail_options` and `rcpt_options` lists
* Server options: helo/ehlo, parse and store ehlo response
Manage errors
Support SSL/TLS
*/
@@ -32,15 +34,9 @@ use std::io::net::tcp::TcpStream;
use std::io::net::addrinfo::get_host_addresses;
use common::{SMTP_PORT, CRLF};
use commands;
use commands::{Command, SmtpCommand};
// // Define smtp_fail! and smtp_success!
// macro_rules! smtp_fail(
// ($command:expr $code:ident $message:expr) => (
// fail!("{} failed: {:u} {:s}", $command, $code, $message);
// );
// )
use commands::{Command, SmtpCommand, EhloKeyword};
// Define smtp_fail! and smtp_success!
/// Contains an SMTP reply, with separed code and message
#[deriving(Eq,Clone)]
@@ -81,7 +77,7 @@ pub struct SmtpServerInfo {
/// Does the server supports ESMTP
does_esmtp: bool,
/// ESMTP features supported by the server
esmtp_features: Option<~[~str]>
esmtp_features: Option<~[EhloKeyword]>
}
@@ -112,16 +108,7 @@ impl<S> SmtpClient<S> {
}
// use std::io::{stdin, BufferedReader};
// fn main() {
// let mut stdin = BufferedReader::new(stdin());
// for line in stdin.lines() {
// println!("{}", line);
// }
// }
// pub fn does_esmtp_feature(keyword: EhloKeyword)
// fn parse_ehello_or_hello_response(response: &str) {
// // split
// }
@@ -195,9 +182,14 @@ impl SmtpClient<TcpStream> {
}
}
/// Print an SMTP response as info
fn smtp_success(&mut self, response: SmtpResponse) {
info!("{:u} {:s}", response.code, response.message);
}
/// Send a QUIT command and end the program
fn smtp_fail(&mut self, command: ~str, response: SmtpResponse) {
self.send_command(commands::Quit, None);
self.send_command(commands::QUIT, None);
fail!("{} failed: {:u} {:s}", command, response.code, response.message);
}
@@ -207,21 +199,22 @@ impl SmtpClient<TcpStream> {
// Connect
match self.connect().with_code([220]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"CONNECT", response)
}
// Ehello or Hello
match self.send_command(commands::Ehello, Some(my_hostname.clone())).with_code([250, 500]) {
// Extended Hello or Hello
match self.send_command(commands::EHLO, Some(my_hostname.clone())).with_code([250, 500]) {
Ok(SmtpResponse{code: 250, message: message}) => {
self.server_info = Some(SmtpServerInfo{name: message.clone(), does_esmtp: true, esmtp_features: None});
info!("{:u} {:s}", 250u, message);
self.smtp_success(SmtpResponse{code: 250u, message: message});
},
Ok(SmtpResponse{code: code, message: message}) => {
self.server_info = Some(SmtpServerInfo{name: message.clone(), does_esmtp: false, esmtp_features: None});
info!("{:u} {:s}", code, message);
match self.send_command(commands::Ehello, Some(my_hostname.clone())).with_code([250]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
Ok(..) => {
match self.send_command(commands::HELO, Some(my_hostname.clone())).with_code([250]) {
Ok(response) => {
self.server_info = Some(SmtpServerInfo{name: response.message.clone(), does_esmtp: false, esmtp_features: None});
self.smtp_success(response);
},
Err(response) => self.smtp_fail(~"HELO", response)
}
},
@@ -229,34 +222,34 @@ impl SmtpClient<TcpStream> {
}
// Mail
match self.send_command(commands::Mail, Some(from_addr.to_owned())).with_code([250]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
match self.send_command(commands::MAIL, Some(from_addr.to_owned())).with_code([250]) {
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"MAIL", response)
}
// Recipient
for &to_addr in to_addrs.iter() {
match self.send_command(commands::Recipient, Some(to_addr.to_owned())).with_code([250]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
match self.send_command(commands::RCPT, Some(to_addr.to_owned())).with_code([250]) {
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"RCPT", response)
}
}
// Data
match self.send_command(commands::Data, None).with_code([354]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
match self.send_command(commands::DATA, None).with_code([354]) {
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"DATA", response)
}
// Message content
match self.send_message(message.to_owned()).with_code([250]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"MESSAGE", response)
}
// Quit
match self.send_command(commands::Quit, None).with_code([221]) {
Ok(response) => info!("{:u} {:s}", response.code, response.message),
match self.send_command(commands::QUIT, None).with_code([221]) {
Ok(response) => self.smtp_success(response),
Err(response) => self.smtp_fail(~"DATA", response)
}
}

View File

@@ -5,8 +5,6 @@
*/
use std::fmt;
// use std::from_str::FromStr;
// use std::to_str::ToStr;
use std::io;
/*
@@ -30,77 +28,77 @@ use std::io;
#[deriving(Eq,Clone)]
pub enum Command {
/// Hello command
Hello,
/// Ehello command
Ehello,
HELO,
/// Extended Hello command
EHLO,
/// Mail command
Mail,
MAIL,
/// Recipient command
Recipient,
RCPT,
/// Data command
Data,
DATA,
/// Reset command
Reset,
/// SendMail command
SendMail,
/// SendOrMail command
SendOrMail,
/// SendAndMail command
SendAndMail,
RSET,
/// Send command, deprecated in RFC 5321
SEND,
/// Send Or Mail command, deprecated in RFC 5321
SOML,
/// Send And Mail command, deprecated in RFC 5321
SAML,
/// Verify command
Verify,
VRFY,
/// Expand command
Expand,
EXPN,
/// Help command
Help,
HELP,
/// Noop command
Noop,
/// Quit commandopenclassroom vim
Quit,
NOOP,
/// Quit command
QUIT,
/// Turn command, deprecated in RFC 5321
Turn,
TURN,
}
impl Command {
/// Tell if the command accetps an string argument.
pub fn takes_argument(&self) -> bool{
match *self {
Ehello => true,
Hello => true,
Mail => true,
Recipient => true,
Data => false,
Reset => false,
SendMail => true,
SendOrMail => true,
SendAndMail => true,
Verify => true,
Expand => true,
Help => true,
Noop => false,
Quit => false,
Turn => false,
EHLO => true,
HELO => true,
MAIL => true,
RCPT => true,
DATA => false,
RSET => false,
SEND => true,
SOML => true,
SAML => true,
VRFY => true,
EXPN => true,
HELP => true,
NOOP => false,
QUIT => false,
TURN => false,
}
}
/// Tell if an argument is needed by the command.
pub fn needs_argument(&self) -> bool {
match *self {
Ehello => true,
Hello => true,
Mail => true,
Recipient => true,
Data => false,
Reset => false,
SendMail => true,
SendOrMail => true,
SendAndMail => true,
Verify => true,
Expand => true,
Help => false,
Noop => false,
Quit => false,
Turn => false,
EHLO => true,
HELO => true,
MAIL => true,
RCPT => true,
DATA => false,
RSET => false,
SEND => true,
SOML => true,
SAML => true,
VRFY => true,
EXPN => true,
HELP => false,
NOOP => false,
QUIT => false,
TURN => false,
}
}
}
@@ -109,22 +107,22 @@ impl fmt::Show for Command {
/// Format SMTP command display
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), io::IoError> {
f.buf.write(match *self {
Ehello => "EHLO".as_bytes(),
Hello => "HELO".as_bytes(),
Mail => "MAIL FROM:".as_bytes(),
Recipient => "RCPT TO:".as_bytes(),
Data => "DATA".as_bytes(),
Reset => "RSET".as_bytes(),
SendMail => "SEND TO:".as_bytes(),
SendOrMail => "SOML TO:".as_bytes(),
SendAndMail => "SAML TO:".as_bytes(),
Verify => "VRFY".as_bytes(),
Expand => "EXPN".as_bytes(),
Help => "HELP".as_bytes(),
Noop => "NOOP".as_bytes(),
Quit => "QUIT".as_bytes(),
Turn => "TURN".as_bytes()
})
EHLO => "EHLO",
HELO => "HELO",
MAIL => "MAIL FROM:",
RCPT => "RCPT TO:",
DATA => "DATA",
RSET => "RSET",
SEND => "SEND TO:",
SOML => "SOML TO:",
SAML => "SAML TO:",
VRFY => "VRFY",
EXPN => "EXPN",
HELP => "HELP",
NOOP => "NOOP",
QUIT => "QUIT",
TURN => "TURN"
}.as_bytes())
}
}
@@ -160,39 +158,46 @@ impl fmt::Show for SmtpCommand {
}
}
/// Supported ESMTP keywords
#[deriving(Eq,Clone)]
pub enum EhloKeyword {
/// STARTTLS keyword
STARTTLS,
/// 8BITMIME keyword
BITMIME,
/// SMTP authentification
AUTH
}
#[cfg(test)]
mod test {
use super::{Command, SmtpCommand};
#[test]
fn test_command_parameters() {
assert!((super::Help).takes_argument() == true);
assert!((super::Reset).takes_argument() == false);
assert!((super::Hello).needs_argument() == true);
assert!((super::HELP).takes_argument() == true);
assert!((super::RSET).takes_argument() == false);
assert!((super::HELO).needs_argument() == true);
}
#[test]
fn test_to_str() {
assert!(super::Turn.to_str() == ~"TURN");
assert!(super::TURN.to_str() == ~"TURN");
}
// #[test]
// fn test_from_str() {
// assert!(from_str == ~"TURN");
// }
#[test]
fn test_fmt() {
assert!(format!("{}", super::Turn) == ~"TURN");
assert!(format!("{}", super::TURN) == ~"TURN");
}
#[test]
fn test_get_simple_command() {
assert!(SmtpCommand::new(super::Turn, None).to_str() == ~"TURN");
assert!(SmtpCommand::new(super::TURN, None).to_str() == ~"TURN");
}
#[test]
fn test_get_argument_command() {
assert!(SmtpCommand::new(super::Ehello, Some(~"example.example")).to_str() == ~"EHLO example.example");
assert!(SmtpCommand::new(super::EHLO, Some(~"example.example")).to_str() == ~"EHLO example.example");
}
}

View File

@@ -16,6 +16,9 @@
#[deny(non_camel_case_types)];
#[deny(missing_doc)];
#[feature(phase)];
#[phase(syntax, link)] extern crate log;
pub mod commands;
pub mod common;
pub mod client;