Compare commits

..

3 Commits

Author SHA1 Message Date
Alexis Mousset
02a9093bad test beta on travis and bump to 0.0.11 2015-04-04 23:47:21 +02:00
Alexis Mousset
d59672ef26 Get ready for rust 1.0.0-beta 2015-04-04 23:32:14 +02:00
Alexis Mousset
7f9b2c5150 Fix for latest nightly 2015-04-04 22:34:01 +02:00
12 changed files with 56 additions and 187 deletions

View File

@@ -1,5 +1,9 @@
language: rust
rust:
- nightly
- 1.0.0-beta
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&

View File

@@ -1,7 +1,7 @@
[package]
name = "smtp"
version = "0.0.9"
version = "0.0.11"
description = "Simple SMTP client"
readme = "README.md"
documentation = "http://amousset.github.io/rust-smtp/smtp/"
@@ -15,6 +15,8 @@ keywords = ["email", "smtp", "mailer"]
time = "*"
uuid = "*"
log = "*"
env_logger = "*"
rustc-serialize = "*"
rust-crypto = "*"
[dev-dependencies]
env_logger = "*"

View File

@@ -4,11 +4,6 @@ rust-smtp [![Build Status](https://travis-ci.org/amousset/rust-smtp.svg?branch=m
This library implements a simple SMTP client.
See the [documentation](http://amousset.github.io/rust-smtp/smtp/) for more information.
Rust versions
-------------
This library is designed for Rust 1.0.0-nightly (master).
Install
-------
@@ -19,24 +14,6 @@ To use this library, add the following to your `Cargo.toml`:
smtp = "*"
```
Otherwise, you can clone this repository and run `cargo build`.
Example
-------
There is an example command-line program included:
```sh
$ cargo test
$ env RUST_LOG=info cargo run --example client -- -s "My subject" -r sender@localhost recipient@localhost < email.txt
INFO:smtp::sender: connection established to 127.0.0.1:25
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: from=<sender@localhost>
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: to=<recipient@localhost>
INFO:smtp::sender: 1d0467fb21b2454f90a85dd1e0eda839: conn_use=1, size=1889, status=sent (2.0.0 Ok: queued as BAA9C1C0055)
INFO:client: Email sent successfully
```
Run `cargo run --example client -- -h` to get a list of available options.
Tests
-----

View File

@@ -7,150 +7,38 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(core, old_io, rustc_private, collections)]
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate smtp;
extern crate getopts;
use std::old_io::stdin;
use std::string::String;
use std::env;
use getopts::{optopt, optflag, getopts, OptGroup, usage};
use std::net::TcpStream;
use smtp::sender::{Sender, SenderBuilder};
use smtp::error::SmtpResult;
use smtp::mailer::EmailBuilder;
fn sendmail(source_address: String, recipient_addresses: Vec<String>, message: String, subject: String,
server: String, port: u16, my_hostname: String, number: u16) -> SmtpResult {
fn main() {
env_logger::init().unwrap();
let mut email_builder = EmailBuilder::new();
for destination in recipient_addresses.iter() {
email_builder = email_builder.to(destination.as_slice());
}
let email = email_builder.from(source_address.as_slice())
.body(message.as_slice())
.subject(subject.as_slice())
email_builder = email_builder.to("user@localhost");
let email = email_builder.from("user@localhost")
.body("Hello World!")
.subject("Hello")
.build();
let mut sender: Sender<TcpStream> = SenderBuilder::new((server.as_slice(), port)).hello_name(my_hostname.as_slice())
let mut sender: Sender<TcpStream> = SenderBuilder::localhost().hello_name("localhost")
.enable_connection_reuse(true).build();
for _ in (1..number) {
for _ in (1..5) {
let _ = sender.send(email.clone());
}
let result = sender.send(email);
sender.close();
result
}
fn print_usage(description: String, _opts: &[OptGroup]) {
println!("{}", usage(description.as_slice(), _opts));
}
fn main() {
env_logger::init().unwrap();
let args = env::args();
let mut args_string = Vec::new();
for arg in args {
args_string.push(arg.clone());
};
let program = args_string[0].clone();
let description = format!("Usage: {0} [options...] recipients\n\n\
This program reads a message on standard input until it reaches\
EOF, then tries to send it using the given paramters.\n\n\
Example: {0} -r user@example.org user@example.com < message.txt",
program);
let opts = [
optopt("n", "number", "set the number of emails to send", "NUMBER"),
optopt("s", "subject", "set the email subject", "SUBJECT"),
optopt("r", "reverse-path", "set the sender address", "FROM_ADDRESS"),
optopt("p", "port", "set the port to use, default is 25", "PORT"),
optopt("a", "server", "set the server to use, default is localhost", "SERVER"),
optopt("m", "my-hostname", "set the hostname used by the client", "MY_HOSTNAME"),
optflag("h", "help", "print this help menu"),
];
let matches = match getopts(args_string.tail(), &opts) {
Ok(m) => m,
Err(f) => panic!("{}", f),
};
if matches.opt_present("h") {
print_usage(description, &opts);
return;
}
if !matches.opt_present("r") {
println!("The sender option is required");
print_usage(program, &opts);
return;
}
let recipients_str: &str = if !matches.free.is_empty() {
matches.free[0].as_slice()
} else {
print_usage(description, &opts);
return;
};
let mut recipients = Vec::new();
for recipient in recipients_str.split(' ') {
recipients.push(recipient.to_string());
}
let mut message = String::new();
let mut line = stdin().read_line();
while line.is_ok() {
message.push_str(line.unwrap().as_slice());
line = stdin().read_line();
}
match sendmail(
// sender
matches.opt_str("r").unwrap().clone(),
// recipients
recipients,
// message content
message,
// subject
match matches.opt_str("s") {
Some(ref subject) => subject.clone(),
None => "(empty subject)".to_string(),
},
// server
match matches.opt_str("a") {
Some(ref server) => server.clone(),
None => "localhost".to_string(),
},
// port
match matches.opt_str("p") {
Some(port) => port.as_slice().parse::<u16>().unwrap(),
None => 25,
},
// my hostname
match matches.opt_str("m") {
Some(ref my_hostname) => my_hostname.clone(),
None => "localhost".to_string(),
},
// number of copies
match matches.opt_str("n") {
Some(ref n) => n.as_slice().parse::<u16>().unwrap(),
None => 1,
},
)
{
match result {
Ok(..) => info!("Email sent successfully"),
Err(error) => error!("{}", error),
Err(error) => error!("{:?}", error),
}
}

View File

@@ -39,13 +39,13 @@ mod test {
#[test]
fn test_plain() {
assert_eq!(plain("username", "password").as_slice(), "AHVzZXJuYW1lAHBhc3N3b3Jk");
assert_eq!(plain("username", "password"), "AHVzZXJuYW1lAHBhc3N3b3Jk");
}
#[test]
fn test_cram_md5() {
assert_eq!(cram_md5("alice", "wonderland",
"PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==").as_slice(),
"PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="),
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
}
}

View File

@@ -10,7 +10,6 @@
//! SMTP client
use std::string::String;
use std::error::FromError;
use std::net::TcpStream;
use std::net::{SocketAddr, ToSocketAddrs};
use std::io::{BufRead, BufStream, Read, Write};
@@ -44,7 +43,7 @@ fn escape_crlf(string: &str) -> String {
}
/// Structure that implements the SMTP client
pub struct Client<S = TcpStream> {
pub struct Client<S: Write + Read = TcpStream> {
/// TCP stream between client and server
/// Value is None before connection
stream: Option<BufStream<S>>,
@@ -55,11 +54,11 @@ pub struct Client<S = TcpStream> {
macro_rules! return_err (
($err: expr, $client: ident) => ({
return Err(FromError::from_error($err))
return Err(From::from($err))
})
);
impl<S = TcpStream> Client<S> {
impl<S: Write + Read = TcpStream> Client<S> {
/// Creates a new SMTP client
///
/// It does not connects to the server, but only creates the `Client`
@@ -75,6 +74,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
/// Closes the SMTP transaction if possible
pub fn close(&mut self) {
let _ = self.quit();
self.stream = None;
}
/// Connects to the configured server
@@ -180,7 +180,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
/// Sends a string to the server and gets the response
fn send_server(&mut self, string: &str, end: &str) -> SmtpResult {
if self.stream.is_none() {
return Err(FromError::from_error("Connection closed"));
return Err(From::from("Connection closed"));
}
try!(write!(self.stream.as_mut().unwrap(), "{}{}", string, end));
@@ -198,12 +198,12 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
// If the string is too short to be a response code
if line.len() < 3 {
return Err(FromError::from_error("Could not parse reply code, line too short"));
return Err(From::from("Could not parse reply code, line too short"));
}
let (severity, category, detail) = match (line[0..1].parse::<Severity>(), line[1..2].parse::<Category>(), line[2..3].parse::<u8>()) {
(Ok(severity), Ok(category), Ok(detail)) => (severity, category, detail),
_ => return Err(FromError::from_error("Could not parse reply code")),
_ => return Err(From::from("Could not parse reply code")),
};
let mut message = Vec::new();
@@ -224,7 +224,7 @@ impl<S: Connecter + Write + Read = TcpStream> Client<S> {
match response.is_positive() {
true => Ok(response),
false => Err(FromError::from_error(response)),
false => Err(From::from(response)),
}
}
}
@@ -235,18 +235,18 @@ mod test {
#[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");
assert_eq!(escape_dot(".test"), "..test");
assert_eq!(escape_dot("\r.\n.\r\n"), "\r..\n..\r\n");
assert_eq!(escape_dot("test\r\n.test\r\n"), "test\r\n..test\r\n");
assert_eq!(escape_dot("test\r\n.\r\ntest"), "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("\r\n"), "<CR><LF>");
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CR><LF>");
assert_eq!(
escape_crlf("EHLO my_name\r\nSIZE 42\r\n").as_slice(),
escape_crlf("EHLO my_name\r\nSIZE 42\r\n"),
"EHLO my_name<CR><LF>SIZE 42<CR><LF>"
);
}

View File

@@ -11,7 +11,6 @@
use std::error::Error;
use std::io;
use std::error::FromError;
use std::fmt::{Display, Formatter};
use std::fmt;
@@ -19,7 +18,7 @@ use response::{Severity, Response};
use self::SmtpError::*;
/// An enum of all error kinds.
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(Debug)]
pub enum SmtpError {
/// Transient error, 4xx reply code
///
@@ -59,14 +58,14 @@ impl Error for SmtpError {
}
}
impl FromError<io::Error> for SmtpError {
fn from_error(err: io::Error) -> SmtpError {
impl From<io::Error> for SmtpError {
fn from(err: io::Error) -> SmtpError {
IoError(err)
}
}
impl FromError<Response> for SmtpError {
fn from_error(response: Response) -> SmtpError {
impl From<Response> for SmtpError {
fn from(response: Response) -> SmtpError {
match response.severity() {
Severity::TransientNegativeCompletion => TransientError(response),
Severity::PermanentNegativeCompletion => PermanentError(response),
@@ -75,8 +74,8 @@ impl FromError<Response> for SmtpError {
}
}
impl FromError<&'static str> for SmtpError {
fn from_error(string: &'static str) -> SmtpError {
impl From<&'static str> for SmtpError {
fn from(string: &'static str) -> SmtpError {
ClientError(string.to_string())
}
}

View File

@@ -68,7 +68,9 @@ impl Extension {
for line in response.message() {
if let Ok(keywords) = Extension::from_str(&line) {
esmtp_features.push_all(&keywords);
for keyword in keywords {
esmtp_features.push(keyword);
}
};
}

View File

@@ -140,11 +140,10 @@
//! let _ = email_client.quit();
//! ```
#![feature(plugin, core, collections, str_words)]
#![deny(missing_docs)]
#[macro_use] extern crate log;
extern crate "rustc-serialize" as serialize;
extern crate rustc_serialize as serialize;
extern crate crypto;
extern crate time;
extern crate uuid;

View File

@@ -76,7 +76,7 @@ impl Display for Header {
Header::MimeVersion => "MIME-Version",
Header::ContentType(_) => "Content-Type",
Header::MessageId(_) => "Message-Id",
Header::Other(ref name, _) => name.as_slice(),
Header::Other(ref name, _) => name.as_ref(),
},
COLON, SP,
match *self {

View File

@@ -189,7 +189,7 @@ impl Response {
pub fn first_word(&self) -> Option<String> {
match self.message.is_empty() {
true => None,
false => Some(self.message[0].words().next().unwrap().to_string())
false => Some(self.message[0].split(" ").next().unwrap().to_string()),
}
}
@@ -208,7 +208,7 @@ mod test {
#[test]
fn test_severity_fmt() {
assert_eq!(format!("{}", Severity::PositiveCompletion).as_slice(), "2");
assert_eq!(format!("{}", Severity::PositiveCompletion), "2");
}
#[test]
@@ -220,7 +220,7 @@ mod test {
#[test]
fn test_category_fmt() {
assert_eq!(format!("{}", Category::Unspecified4).as_slice(), "4");
assert_eq!(format!("{}", Category::Unspecified4), "4");
}
#[test]

View File

@@ -102,7 +102,7 @@ struct State {
}
/// Structure that implements the high level SMTP client
pub struct Sender<S = TcpStream> {
pub struct Sender<S: Write + Read = TcpStream> {
/// Information about the server
/// Value is None before HELO/EHLO
server_info: Option<ServerInfo>,
@@ -121,7 +121,7 @@ macro_rules! try_smtp (
Err(err) => {
if !$client.state.panic {
$client.state.panic = true;
$client.client.close();
$client.reset();
}
return Err(err)
},
@@ -129,7 +129,7 @@ macro_rules! try_smtp (
})
);
impl<S = TcpStream> Sender<S> {
impl<S: Write + Read = TcpStream> Sender<S> {
/// Creates a new SMTP client
///
/// It does not connects to the server, but only creates the `Sender`
@@ -265,10 +265,8 @@ impl<S: Connecter + Write + Read = TcpStream> Sender<S> {
// Log the message
info!("{}: conn_use={}, size={}, status=sent ({})", current_message,
self.state.connection_reuse_count, message.len(), match result.as_ref().ok().unwrap().message().as_slice() {
[ref line, ..] => line.as_slice(),
[] => "no response",
}
self.state.connection_reuse_count, message.len(),
result.as_ref().ok().unwrap().message().iter().next().unwrap_or(&"no response".to_string())
);
}