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