Add tests for Extension and Response
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
|
||||
name = "smtp"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
description = "Simple SMTP client"
|
||||
readme = "README.md"
|
||||
documentation = "http://amousset.github.io/rust-smtp/smtp/"
|
||||
|
||||
@@ -15,7 +15,7 @@ use crypto::hmac::Hmac;
|
||||
use crypto::md5::Md5;
|
||||
use crypto::mac::Mac;
|
||||
|
||||
use tools::NUL;
|
||||
use NUL;
|
||||
|
||||
/// Returns a PLAIN mecanism response
|
||||
pub fn plain(username: &str, password: &str) -> String {
|
||||
|
||||
@@ -15,16 +15,34 @@ use std::net::TcpStream;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::io::{BufRead, BufStream, Read, Write};
|
||||
|
||||
use tools::{CRLF, MESSAGE_ENDING};
|
||||
use tools::{escape_dot, escape_crlf};
|
||||
use response::{Response, Severity, Category};
|
||||
use error::SmtpResult;
|
||||
use client::connecter::Connecter;
|
||||
use client::authentication::{plain, cram_md5};
|
||||
use {CRLF, MESSAGE_ENDING};
|
||||
|
||||
pub mod connecter;
|
||||
mod authentication;
|
||||
|
||||
/// Returns the string after adding a dot at the beginning of each line starting with a dot
|
||||
///
|
||||
/// Reference : https://tools.ietf.org/html/rfc5321#page-62 (4.5.2. Transparency)
|
||||
#[inline]
|
||||
fn escape_dot(string: &str) -> String {
|
||||
if string.starts_with(".") {
|
||||
format!(".{}", string)
|
||||
} else {
|
||||
string.to_string()
|
||||
}.replace("\r.", "\r..")
|
||||
.replace("\n.", "\n..")
|
||||
}
|
||||
|
||||
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
||||
#[inline]
|
||||
fn escape_crlf(string: &str) -> String {
|
||||
string.replace(CRLF, "<CR><LF>")
|
||||
}
|
||||
|
||||
/// Structure that implements the SMTP client
|
||||
pub struct Client<S = TcpStream> {
|
||||
/// TCP stream between client and server
|
||||
@@ -210,3 +228,26 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{escape_dot, escape_crlf};
|
||||
|
||||
#[test]
|
||||
fn test_escape_dot() {
|
||||
assert_eq!(escape_dot(".test").as_slice(), "..test");
|
||||
assert_eq!(escape_dot("\r.\n.\r\n").as_slice(), "\r..\n..\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.test\r\n").as_slice(), "test\r\n..test\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.\r\ntest").as_slice(), "test\r\n..\r\ntest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_crlf() {
|
||||
assert_eq!(escape_crlf("\r\n").as_slice(), "<CR><LF>");
|
||||
assert_eq!(escape_crlf("EHLO my_name\r\n").as_slice(), "EHLO my_name<CR><LF>");
|
||||
assert_eq!(
|
||||
escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(),
|
||||
"EHLO my_name<CR><LF>SIZE 42<CR><LF>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,3 +83,8 @@ impl FromError<&'static str> for SmtpError {
|
||||
|
||||
/// SMTP result type
|
||||
pub type SmtpResult = Result<Response, SmtpError>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ impl Extension {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Extension;
|
||||
use response::{Severity, Category, Response};
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
@@ -89,17 +90,19 @@ mod test {
|
||||
assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication]));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_parse_esmtp_response() {
|
||||
// assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 SIZE 42"),
|
||||
// vec![Extension::EightBitMime]);
|
||||
// assert_eq!(Extension::parse_esmtp_response("me\r\n250-8BITMIME\r\n250 AUTH PLAIN CRAM-MD5\r\n250 UNKNON 42"),
|
||||
// vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]);
|
||||
// assert_eq!(Extension::parse_esmtp_response("me\r\n250-9BITMIME\r\n250 SIZE a"),
|
||||
// vec![]);
|
||||
// assert_eq!(Extension::parse_esmtp_response("me\r\n250-SIZE 42\r\n250 SIZE 43"),
|
||||
// vec![]);
|
||||
// assert_eq!(Extension::parse_esmtp_response(""),
|
||||
// vec![]);
|
||||
// }
|
||||
#[test]
|
||||
fn test_parse_esmtp_response() {
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"2".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
)), vec![Extension::EightBitMime]);
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"3".parse::<Category>().unwrap(),
|
||||
3,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "AUTH PLAIN CRAM-MD5".to_string()]
|
||||
)), vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/lib.rs
18
src/lib.rs
@@ -149,7 +149,6 @@ extern crate crypto;
|
||||
extern crate time;
|
||||
extern crate uuid;
|
||||
|
||||
mod tools;
|
||||
mod extension;
|
||||
pub mod client;
|
||||
pub mod sender;
|
||||
@@ -169,3 +168,20 @@ pub static SMTPS_PORT: u16 = 465;
|
||||
|
||||
/// Default submission port
|
||||
pub static SUBMISSION_PORT: u16 = 587;
|
||||
|
||||
// Useful strings and characters
|
||||
|
||||
/// The word separator for SMTP transactions
|
||||
pub static SP: &'static str = " ";
|
||||
|
||||
/// The line ending for SMTP transactions (carriage return + line feed)
|
||||
pub static CRLF: &'static str = "\r\n";
|
||||
|
||||
/// Colon
|
||||
pub static COLON: &'static str = ":";
|
||||
|
||||
/// The ending of message content
|
||||
pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n";
|
||||
|
||||
/// NUL unicode character
|
||||
pub static NUL: &'static str = "\0";
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
use tools::SP;
|
||||
use SP;
|
||||
|
||||
/// Converts an adress or an address with an alias to an `Address`
|
||||
pub trait ToAddress {
|
||||
|
||||
@@ -13,8 +13,8 @@ use time::Tm;
|
||||
|
||||
use std::fmt::{Display, Formatter, Result};
|
||||
|
||||
use tools::{SP, COLON};
|
||||
use mailer::address::Address;
|
||||
use {COLON, SP};
|
||||
|
||||
/// Converts to an `Header`
|
||||
pub trait ToHeader {
|
||||
|
||||
@@ -15,8 +15,8 @@ use time::{now, Tm};
|
||||
|
||||
use mailer::header::{ToHeader, Header};
|
||||
use mailer::address::ToAddress;
|
||||
use tools::CRLF;
|
||||
use sendable_email::SendableEmail;
|
||||
use CRLF;
|
||||
|
||||
pub mod header;
|
||||
pub mod address;
|
||||
|
||||
119
src/response.rs
119
src/response.rs
@@ -222,4 +222,123 @@ mod test {
|
||||
fn test_category_fmt() {
|
||||
assert_eq!(format!("{}", Category::Unspecified4).as_slice(), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_new() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
), Response {
|
||||
severity: Severity::PositiveCompletion,
|
||||
category: Category::Unspecified4,
|
||||
detail: 1,
|
||||
message: vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()],
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_is_positive() {
|
||||
assert!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).is_positive());
|
||||
assert!(! Response::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).is_positive());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_message() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).message(), vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_severity() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).severity(), Severity::PositiveCompletion);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_category() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).category(), Category::Unspecified4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_detail() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).detail(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_code() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).code(), "241");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_has_code() {
|
||||
assert!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).has_code(241));
|
||||
assert!(! Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).has_code(251));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_first_word() {
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).first_word(), Some("me".to_string()));
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec!["me mo".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
).first_word(), Some("me".to_string()));
|
||||
assert_eq!(Response::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"4".parse::<Category>().unwrap(),
|
||||
1,
|
||||
vec![]
|
||||
).first_word(), None);
|
||||
}
|
||||
}
|
||||
|
||||
102
src/tools.rs
102
src/tools.rs
@@ -1,102 +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.
|
||||
|
||||
//! Tools for common string manipulations
|
||||
|
||||
use std::string::String;
|
||||
|
||||
/// The word separator for SMTP transactions
|
||||
pub static SP: &'static str = " ";
|
||||
|
||||
/// The line ending for SMTP transactions (carriage return + line feed)
|
||||
pub static CRLF: &'static str = "\r\n";
|
||||
|
||||
/// Carriage return
|
||||
pub static CR: &'static str = "\r";
|
||||
|
||||
/// Line feed
|
||||
pub static LF: &'static str = "\n";
|
||||
|
||||
/// Colon
|
||||
pub static COLON: &'static str = ":";
|
||||
|
||||
/// The ending of message content
|
||||
pub static MESSAGE_ENDING: &'static str = "\r\n.\r\n";
|
||||
|
||||
/// NUL unicode character
|
||||
pub static NUL: &'static str = "\0";
|
||||
|
||||
/// Returns the first word of a string, or the string if it contains no space
|
||||
#[inline]
|
||||
pub fn get_first_word(string: &str) -> &str {
|
||||
match string.lines_any().next() {
|
||||
Some(line) => match line.words().next() {
|
||||
Some(word) => word,
|
||||
None => "",
|
||||
},
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
||||
#[inline]
|
||||
pub fn escape_crlf(string: &str) -> String {
|
||||
string.replace(CRLF, "<CR><LF>")
|
||||
}
|
||||
|
||||
/// Returns the string after adding a dot at the beginning of each line starting with a dot
|
||||
///
|
||||
/// Reference : https://tools.ietf.org/html/rfc5321#page-62 (4.5.2. Transparency)
|
||||
#[inline]
|
||||
pub fn escape_dot(string: &str) -> String {
|
||||
if string.starts_with(".") {
|
||||
format!(".{}", string)
|
||||
} else {
|
||||
string.to_string()
|
||||
}.replace(format!("{}.", CR).as_slice(), format!("{}..", CR).as_slice())
|
||||
.replace(format!("{}.", LF).as_slice(), format!("{}..", LF).as_slice())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_first_word() {
|
||||
assert_eq!(get_first_word("first word"), "first");
|
||||
assert_eq!(get_first_word("first word\r\ntest"), "first");
|
||||
assert_eq!(get_first_word("first"), "first");
|
||||
assert_eq!(get_first_word(""), "");
|
||||
assert_eq!(get_first_word("\r\n"), "");
|
||||
assert_eq!(get_first_word("a\r\n"), "a");
|
||||
// Manage cases of empty line, spaces at the beginning
|
||||
//assert_eq!(get_first_word(" a"), "a");
|
||||
//assert_eq!(get_first_word("\r\n a"), "a");
|
||||
assert_eq!(get_first_word(" \r\n"), "");
|
||||
assert_eq!(get_first_word("\r\n "), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_crlf() {
|
||||
assert_eq!(escape_crlf("\r\n").as_slice(), "<CR><LF>");
|
||||
assert_eq!(escape_crlf("EHLO my_name\r\n").as_slice(), "EHLO my_name<CR><LF>");
|
||||
assert_eq!(
|
||||
escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(),
|
||||
"EHLO my_name<CR><LF>SIZE 42<CR><LF>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_dot() {
|
||||
assert_eq!(escape_dot(".test").as_slice(), "..test");
|
||||
assert_eq!(escape_dot("\r.\n.\r\n").as_slice(), "\r..\n..\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.test\r\n").as_slice(), "test\r\n..test\r\n");
|
||||
assert_eq!(escape_dot("test\r\n.\r\ntest").as_slice(), "test\r\n..\r\ntest");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user