Refactoring to create an SMTP library
This commit is contained in:
@@ -17,10 +17,14 @@ use std::strbuf::StrBuf;
|
||||
use std::io::{IoResult, Reader, Writer};
|
||||
use std::io::net::ip::{SocketAddr, Port};
|
||||
use std::io::net::tcp::TcpStream;
|
||||
use std::io::net::addrinfo::get_host_addresses;
|
||||
use common::{CRLF, get_first_word, unquote_email_address};
|
||||
use commands;
|
||||
use commands::{SMTP_PORT, SmtpCommand, EsmtpParameter, SmtpResponse};
|
||||
|
||||
use common::{resolve_host, get_first_word, unquote_email_address};
|
||||
use smtp::smtp_response::SmtpResponse;
|
||||
use smtp::esmtp_parameter;
|
||||
use smtp::esmtp_parameter::EsmtpParameter;
|
||||
use smtp::smtp_command;
|
||||
use smtp::smtp_command::SmtpCommand;
|
||||
use smtp::{SMTP_PORT, CRLF};
|
||||
|
||||
/// Information about an SMTP server
|
||||
#[deriving(Clone)]
|
||||
@@ -131,6 +135,7 @@ macro_rules! smtp_fail_if_err(
|
||||
/// Structure that implements the SMTP client
|
||||
pub struct SmtpClient<T, S> {
|
||||
/// TCP stream between client and server
|
||||
/// Value is None before connection
|
||||
stream: Option<S>,
|
||||
/// Host we are connecting to
|
||||
host: T,
|
||||
@@ -141,7 +146,7 @@ pub struct SmtpClient<T, S> {
|
||||
/// Information about the server
|
||||
/// Value is None before HELO/EHLO
|
||||
server_info: Option<SmtpServerInfo<T>>,
|
||||
/// Transaction state, permits to check order againt RFCs
|
||||
/// Transaction state, to check the sequence of commands
|
||||
state: SmtpClientState
|
||||
}
|
||||
|
||||
@@ -165,9 +170,9 @@ impl SmtpClient<StrBuf, TcpStream> {
|
||||
if !self.stream.is_none() {
|
||||
fail!("The connection is already established");
|
||||
}
|
||||
let ip = match get_host_addresses(self.host.clone().into_owned()) {
|
||||
Ok(ip_vector) => *ip_vector.get(0), // TODO : select a random ip
|
||||
Err(..) => fail!("Cannot resolve {:s}", self.host)
|
||||
let ip = match resolve_host(self.host.clone().into_owned()) {
|
||||
Ok(ip) => ip,
|
||||
Err(..) => fail!("Cannot resolve {:s}", self.host)
|
||||
};
|
||||
self.stream = match TcpStream::connect(SocketAddr{ip: ip, port: self.port}) {
|
||||
Ok(stream) => Some(stream),
|
||||
@@ -219,7 +224,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).is_ok() {
|
||||
if ! self.server_info.clone().unwrap().supports_feature(esmtp_parameter::EightBitMime).is_ok() {
|
||||
if ! message.clone().into_owned().is_ascii() {
|
||||
self.smtp_fail("Server does not accepts UTF-8 strings");
|
||||
}
|
||||
@@ -310,14 +315,19 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
|
||||
|
||||
/// Closes the TCP stream
|
||||
pub fn close(&mut self) {
|
||||
// Close the TCP connection
|
||||
drop(self.stream.clone().unwrap());
|
||||
// Reset client state
|
||||
self.stream = None;
|
||||
self.state = Unconnected;
|
||||
self.server_info = None;
|
||||
}
|
||||
|
||||
/// Send a HELO command
|
||||
pub fn helo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
|
||||
check_state_in!(vec!(Connected));
|
||||
|
||||
match self.send_command(commands::Hello(my_hostname.clone())).with_code(vec!(250)) {
|
||||
match self.send_command(smtp_command::Hello(my_hostname.clone())).with_code(vec!(250)) {
|
||||
Ok(response) => {
|
||||
self.server_info = Some(
|
||||
SmtpServerInfo{
|
||||
@@ -336,7 +346,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
|
||||
pub fn ehlo(&mut self, my_hostname: StrBuf) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
|
||||
check_state_not_in!(vec!(Unconnected));
|
||||
|
||||
match self.send_command(commands::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
|
||||
match self.send_command(smtp_command::ExtendedHello(my_hostname.clone())).with_code(vec!(250)) {
|
||||
Ok(response) => {
|
||||
self.server_info = Some(
|
||||
SmtpServerInfo{
|
||||
@@ -355,7 +365,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
|
||||
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(StrBuf::from_str(unquote_email_address(from_address.to_owned())), options)).with_code(vec!(250)) {
|
||||
match self.send_command(smtp_command::Mail(StrBuf::from_str(unquote_email_address(from_address.to_owned())), options)).with_code(vec!(250)) {
|
||||
Ok(response) => {
|
||||
self.state = MailSent;
|
||||
Ok(response)
|
||||
@@ -370,7 +380,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
|
||||
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(StrBuf::from_str(unquote_email_address(to_address.to_owned())), options)).with_code(vec!(250)) {
|
||||
match self.send_command(smtp_command::Recipient(StrBuf::from_str(unquote_email_address(to_address.to_owned())), options)).with_code(vec!(250)) {
|
||||
Ok(response) => {
|
||||
self.state = RcptSent;
|
||||
Ok(response)
|
||||
@@ -385,7 +395,7 @@ impl<S: Writer + Reader + Clone> SmtpClient<StrBuf, S> {
|
||||
pub fn data(&mut self) -> Result<SmtpResponse<StrBuf>, SmtpResponse<StrBuf>> {
|
||||
check_state_in!(vec!(RcptSent));
|
||||
|
||||
match self.send_command(commands::Data).with_code(vec!(354)) {
|
||||
match self.send_command(smtp_command::Data).with_code(vec!(354)) {
|
||||
Ok(response) => {
|
||||
self.state = DataSent;
|
||||
Ok(response)
|
||||
@@ -414,9 +424,8 @@ 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!(vec!(Unconnected));
|
||||
match self.send_command(commands::Quit).with_code(vec!(221)) {
|
||||
match self.send_command(smtp_command::Quit).with_code(vec!(221)) {
|
||||
Ok(response) => {
|
||||
self.close();
|
||||
Ok(response)
|
||||
},
|
||||
Err(response) => {
|
||||
@@ -428,7 +437,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!(vec!(Unconnected));
|
||||
match self.send_command(commands::Reset).with_code(vec!(250)) {
|
||||
match self.send_command(smtp_command::Reset).with_code(vec!(250)) {
|
||||
Ok(response) => {
|
||||
if vec!(MailSent, RcptSent, DataSent).contains(&self.state) {
|
||||
self.state = HeloSent;
|
||||
@@ -444,13 +453,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!(vec!(Unconnected));
|
||||
self.send_command(commands::Noop).with_code(vec!(250))
|
||||
self.send_command(smtp_command::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!(vec!(Unconnected));
|
||||
self.send_command(commands::Verify(to_address)).with_code(vec!(250))
|
||||
self.send_command(smtp_command::Verify(to_address)).with_code(vec!(250))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,17 +498,17 @@ impl<T, S: Writer + Clone> Writer for SmtpClient<T, S> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::SmtpServerInfo;
|
||||
use commands;
|
||||
use smtp::esmtp_parameter;
|
||||
|
||||
#[test]
|
||||
fn test_smtp_server_info_fmt() {
|
||||
assert_eq!(format!("{}", SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::EightBitMime))
|
||||
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
|
||||
}), "name with [8BITMIME]".to_owned());
|
||||
assert_eq!(format!("{}", SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::EightBitMime, commands::Size(42)))
|
||||
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime, esmtp_parameter::Size(42)))
|
||||
}), "name with [8BITMIME, SIZE=42]".to_owned());
|
||||
assert_eq!(format!("{}", SmtpServerInfo{
|
||||
name: "name",
|
||||
@@ -510,13 +519,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_smtp_server_info_parse_esmtp_response() {
|
||||
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 SIZE 42"),
|
||||
Some(vec!(commands::EightBitMime, commands::Size(42))));
|
||||
Some(vec!(esmtp_parameter::EightBitMime, esmtp_parameter::Size(42))));
|
||||
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 UNKNON 42"),
|
||||
Some(vec!(commands::EightBitMime)));
|
||||
Some(vec!(esmtp_parameter::EightBitMime)));
|
||||
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-9BITMIME\r\n250 SIZE a"),
|
||||
None);
|
||||
assert_eq!(SmtpServerInfo::parse_esmtp_response("me\r\n250-SIZE 42\r\n250 SIZE 43"),
|
||||
Some(vec!(commands::Size(42), commands::Size(43))));
|
||||
Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::Size(43))));
|
||||
assert_eq!(SmtpServerInfo::parse_esmtp_response(""),
|
||||
None);
|
||||
}
|
||||
@@ -525,19 +534,19 @@ mod test {
|
||||
fn test_smtp_server_info_supports_feature() {
|
||||
assert_eq!(SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::EightBitMime))
|
||||
}.supports_feature(commands::EightBitMime), Ok(commands::EightBitMime));
|
||||
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
|
||||
}.supports_feature(esmtp_parameter::EightBitMime), Ok(esmtp_parameter::EightBitMime));
|
||||
assert_eq!(SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::Size(42), commands::EightBitMime))
|
||||
}.supports_feature(commands::EightBitMime), Ok(commands::EightBitMime));
|
||||
esmtp_features: Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::EightBitMime))
|
||||
}.supports_feature(esmtp_parameter::EightBitMime), Ok(esmtp_parameter::EightBitMime));
|
||||
assert_eq!(SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::Size(42), commands::EightBitMime))
|
||||
}.supports_feature(commands::Size(0)), Ok(commands::Size(42)));
|
||||
esmtp_features: Some(vec!(esmtp_parameter::Size(42), esmtp_parameter::EightBitMime))
|
||||
}.supports_feature(esmtp_parameter::Size(0)), Ok(esmtp_parameter::Size(42)));
|
||||
assert!(SmtpServerInfo{
|
||||
name: "name",
|
||||
esmtp_features: Some(vec!(commands::EightBitMime))
|
||||
}.supports_feature(commands::Size(42)).is_err());
|
||||
esmtp_features: Some(vec!(esmtp_parameter::EightBitMime))
|
||||
}.supports_feature(esmtp_parameter::Size(42)).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
300
src/commands.rs
300
src/commands.rs
@@ -1,300 +0,0 @@
|
||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! SMTP commands and ESMTP features library
|
||||
|
||||
use std::fmt::{Show, Formatter, Result};
|
||||
use std::result;
|
||||
use std::io::net::ip::Port;
|
||||
use std::from_str::FromStr;
|
||||
|
||||
use common::remove_trailing_crlf;
|
||||
|
||||
/// Default SMTP port
|
||||
pub static SMTP_PORT: Port = 25;
|
||||
//pub static SMTPS_PORT: Port = 465;
|
||||
//pub static SUBMISSION_PORT: Port = 587;
|
||||
|
||||
/// Supported SMTP commands
|
||||
///
|
||||
/// We do not implement the following SMTP commands, as they were deprecated in RFC 5321
|
||||
/// and must not be used by clients:
|
||||
/// SEND, SOML, SAML, TURN
|
||||
#[deriving(Eq,Clone)]
|
||||
pub enum SmtpCommand<T> {
|
||||
/// Extended Hello command
|
||||
ExtendedHello(T),
|
||||
/// Hello command
|
||||
Hello(T),
|
||||
/// Mail command, takes optionnal options
|
||||
Mail(T, Option<Vec<T>>),
|
||||
/// Recipient command, takes optionnal options
|
||||
Recipient(T, Option<Vec<T>>),
|
||||
/// Data command
|
||||
Data,
|
||||
/// Reset command
|
||||
Reset,
|
||||
/// Verify command
|
||||
Verify(T),
|
||||
/// Expand command
|
||||
Expand(T),
|
||||
/// Help command, takes optionnal options
|
||||
Help(Option<T>),
|
||||
/// Noop command
|
||||
Noop,
|
||||
/// Quit command
|
||||
Quit,
|
||||
|
||||
}
|
||||
|
||||
impl<T: Show + Str> Show for SmtpCommand<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
f.buf.write(match *self {
|
||||
ExtendedHello(ref my_hostname) =>
|
||||
format!("EHLO {}", my_hostname.clone()),
|
||||
Hello(ref my_hostname) =>
|
||||
format!("HELO {}", my_hostname.clone()),
|
||||
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.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.connect(" ")),
|
||||
Data => "DATA".to_owned(),
|
||||
Reset => "RSET".to_owned(),
|
||||
Verify(ref address) =>
|
||||
format!("VRFY {}", address.clone()),
|
||||
Expand(ref address) =>
|
||||
format!("EXPN {}", address.clone()),
|
||||
Help(None) => "HELP".to_owned(),
|
||||
Help(Some(ref argument)) =>
|
||||
format!("HELP {}", argument.clone()),
|
||||
Noop => "NOOP".to_owned(),
|
||||
Quit => "QUIT".to_owned(),
|
||||
}.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Supported ESMTP keywords
|
||||
#[deriving(Eq,Clone)]
|
||||
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".to_owned(),
|
||||
&Size(ref size) => format!("SIZE={}", size)
|
||||
}.as_bytes()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EsmtpParameter {
|
||||
fn from_str(s: &str) -> Option<EsmtpParameter> {
|
||||
let splitted : Vec<&str> = s.splitn(' ', 1).collect();
|
||||
match splitted.len() {
|
||||
1 => match *splitted.get(0) {
|
||||
"8BITMIME" => Some(EightBitMime),
|
||||
_ => None
|
||||
},
|
||||
2 => match (*splitted.get(0), from_str::<uint>(*splitted.get(1))) {
|
||||
("SIZE", Some(size)) => Some(Size(size)),
|
||||
_ => None
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EsmtpParameter {
|
||||
/// Checks if the ESMTP keyword is the same
|
||||
pub fn same_keyword_as(&self, other: EsmtpParameter) -> bool {
|
||||
if *self == other {
|
||||
return true;
|
||||
}
|
||||
match (*self, other) {
|
||||
(Size(_), Size(_)) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains an SMTP reply, with separed code and message
|
||||
///
|
||||
/// We do accept messages containing only a code, to comply with RFC5321
|
||||
#[deriving(Clone, Eq)]
|
||||
pub struct SmtpResponse<T> {
|
||||
/// Server response code
|
||||
pub code: u16,
|
||||
/// Server response string
|
||||
pub message: Option<T>
|
||||
}
|
||||
|
||||
impl<T: Show + Clone> Show for SmtpResponse<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
f.buf.write(
|
||||
match self.clone().message {
|
||||
Some(message) => format!("{} {}", self.code.to_str(), message),
|
||||
None => self.code.to_str()
|
||||
}.as_bytes()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FromStr ?
|
||||
impl FromStr for SmtpResponse<StrBuf> {
|
||||
fn from_str(s: &str) -> Option<SmtpResponse<StrBuf>> {
|
||||
// If the string is too short to be a response code
|
||||
if s.len() < 3 {
|
||||
None
|
||||
// If we have only a code, with or without a trailing space
|
||||
} else if s.len() == 3 || (s.len() == 4 && s.slice(3,4) == " ") {
|
||||
match from_str::<u16>(s.slice_to(3)) {
|
||||
Some(code) => Some(SmtpResponse{
|
||||
code: code,
|
||||
message: None
|
||||
}),
|
||||
None => None
|
||||
|
||||
}
|
||||
// If we have a code and a message
|
||||
} else {
|
||||
match (
|
||||
from_str::<u16>(s.slice_to(3)),
|
||||
vec!(" ", "-").contains(&s.slice(3,4)),
|
||||
StrBuf::from_str(remove_trailing_crlf(s.slice_from(4).to_owned()))
|
||||
) {
|
||||
(Some(code), true, message) => Some(SmtpResponse{
|
||||
code: code,
|
||||
message: Some(message)
|
||||
}),
|
||||
_ => None
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> SmtpResponse<T> {
|
||||
/// Checks the presence of the response code in the array of expected codes.
|
||||
pub fn with_code(&self, expected_codes: Vec<u16>) -> result::Result<SmtpResponse<T>,SmtpResponse<T>> {
|
||||
let response = self.clone();
|
||||
if expected_codes.contains(&self.code) {
|
||||
Ok(response)
|
||||
} else {
|
||||
Err(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{SmtpCommand, EsmtpParameter, SmtpResponse};
|
||||
|
||||
#[test]
|
||||
fn test_command_fmt() {
|
||||
let noop: SmtpCommand<StrBuf> = super::Noop;
|
||||
assert_eq!(format!("{}", noop), "NOOP".to_owned());
|
||||
assert_eq!(format!("{}", super::ExtendedHello("me")), "EHLO me".to_owned());
|
||||
assert_eq!(format!("{}",
|
||||
super::Mail("test", Some(vec!("option")))), "MAIL FROM:<test> option".to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_esmtp_parameter_same_keyword_as() {
|
||||
assert_eq!(super::EightBitMime.same_keyword_as(super::EightBitMime), true);
|
||||
assert_eq!(super::Size(42).same_keyword_as(super::Size(42)), true);
|
||||
assert_eq!(super::Size(42).same_keyword_as(super::Size(43)), true);
|
||||
assert_eq!(super::Size(42).same_keyword_as(super::EightBitMime), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_esmtp_parameter_fmt() {
|
||||
assert_eq!(format!("{}", super::EightBitMime), "8BITMIME".to_owned());
|
||||
assert_eq!(format!("{}", super::Size(42)), "SIZE=42".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_esmtp_parameter_from_str() {
|
||||
assert_eq!(from_str::<EsmtpParameter>("8BITMIME"), Some(super::EightBitMime));
|
||||
assert_eq!(from_str::<EsmtpParameter>("SIZE 42"), Some(super::Size(42)));
|
||||
assert_eq!(from_str::<EsmtpParameter>("SIZ 42"), None);
|
||||
assert_eq!(from_str::<EsmtpParameter>("SIZE 4a2"), None);
|
||||
// TODO: accept trailing spaces ?
|
||||
assert_eq!(from_str::<EsmtpParameter>("SIZE 42 "), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smtp_response_fmt() {
|
||||
assert_eq!(format!("{}", SmtpResponse{code: 200, message: Some("message")}), "200 message".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smtp_response_from_str() {
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200 response message"),
|
||||
Some(SmtpResponse{
|
||||
code: 200,
|
||||
message: Some(StrBuf::from_str("response message"))
|
||||
})
|
||||
);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200-response message"),
|
||||
Some(SmtpResponse{
|
||||
code: 200,
|
||||
message: Some(StrBuf::from_str("response message"))
|
||||
})
|
||||
);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200"),
|
||||
Some(SmtpResponse{
|
||||
code: 200,
|
||||
message: None
|
||||
})
|
||||
);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200 "),
|
||||
Some(SmtpResponse{
|
||||
code: 200,
|
||||
message: None
|
||||
})
|
||||
);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("200-response\r\nmessage"),
|
||||
Some(SmtpResponse{
|
||||
code: 200,
|
||||
message: Some(StrBuf::from_str("response\r\nmessage"))
|
||||
})
|
||||
);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("2000response message"), None);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("20a response message"), None);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("20 "), None);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("20"), None);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>("2"), None);
|
||||
assert_eq!(from_str::<SmtpResponse<StrBuf>>(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smtp_response_with_code() {
|
||||
assert_eq!(SmtpResponse{code: 200, message: Some("message")}.with_code(vec!(200)),
|
||||
Ok(SmtpResponse{code: 200, message: Some("message")}));
|
||||
assert_eq!(SmtpResponse{code: 400, message: Some("message")}.with_code(vec!(200)),
|
||||
Err(SmtpResponse{code: 400, message: Some("message")}));
|
||||
assert_eq!(SmtpResponse{code: 200, message: Some("message")}.with_code(vec!(200, 300)),
|
||||
Ok(SmtpResponse{code: 200, message: Some("message")}));
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,18 @@
|
||||
//!
|
||||
//! Needs to be organized later
|
||||
|
||||
pub static SP: &'static str = " ";
|
||||
pub static CRLF: &'static str = "\r\n";
|
||||
use std::io::net::addrinfo::get_host_addresses;
|
||||
use std::io::net::ip::IpAddr;
|
||||
|
||||
use smtp::CRLF;
|
||||
|
||||
/// Resolves an hostname and returns a random IP
|
||||
pub fn resolve_host(hostname: ~str) -> Result<IpAddr, ()> {
|
||||
match get_host_addresses(hostname) {
|
||||
Ok(ip_vector) => Ok(*ip_vector.get(ip_vector.len() - 1)),
|
||||
Err(..) => Err({})
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds quotes to emails if needed
|
||||
pub fn quote_email_address(address: ~str) -> ~str {
|
||||
@@ -54,6 +64,13 @@ pub fn get_first_word(string: ~str) -> ~str {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::io::net::ip::IpAddr;
|
||||
|
||||
#[test]
|
||||
fn test_resolve_host() {
|
||||
assert_eq!(super::resolve_host("localhost".to_owned()), Ok(from_str::<IpAddr>("127.0.0.1").unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_email_address() {
|
||||
assert_eq!(super::quote_email_address("address".to_owned()), "<address>".to_owned());
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! # Rust SMTP client
|
||||
//! # Rust SMTP library
|
||||
//!
|
||||
//! The client does its best to follow RFC 5321 (https://tools.ietf.org/html/rfc5321).
|
||||
//!
|
||||
@@ -63,6 +63,6 @@
|
||||
|
||||
#![feature(phase)] #[phase(syntax, link)] extern crate log;
|
||||
|
||||
pub mod commands;
|
||||
pub mod smtp;
|
||||
pub mod common;
|
||||
pub mod client;
|
||||
|
||||
Reference in New Issue
Block a user