Add documentation

This commit is contained in:
Alexis Mousset
2014-11-14 01:32:18 +01:00
parent 1d87ea0537
commit 2a1961c659
12 changed files with 72 additions and 68 deletions

View File

@@ -24,16 +24,21 @@ Example
-------
There is an example command-line program included:
```
```sh
$ cargo test
$ env RUST_LOG=info ./target/examples/client -r sender@localhost recipient@localhost < email.txt
INFO:smtp::client: Connection established to localhost[127.0.0.1]:25
INFO:smtp::client: connection established to 127.0.0.1:25
INFO:smtp::client: from=<sender@localhost>, size=989, nrcpt=1
INFO:smtp::client: to=<recipient@localhost>, status=sent (250 2.0.0 Ok: queued as 9D28F1C0A51)
```
Run `./target/examples/client -h` to get a list of available options.
Tests
-----
You can build and run the tests with `cargo test`. The client does not have tests for now.
Documentation
-------------

View File

@@ -27,12 +27,12 @@ fn sendmail(source_address: &str, recipient_addresses: &[&str], message: &str,
let mut email_client: Client<TcpStream> =
Client::new(
(server, port),
Some(my_hostname)
Some(my_hostname),
);
email_client.send_mail::<TcpStream>(
source_address,
recipient_addresses,
message
message,
)
}
@@ -64,7 +64,7 @@ fn main() {
let matches = match getopts(args_string.tail(), opts) {
Ok(m) => m,
Err(f) => panic!("{}", f)
Err(f) => panic!("{}", f),
};
if matches.opt_present("h") {
@@ -110,16 +110,16 @@ fn main() {
// port
match matches.opt_str("p") {
Some(port) => from_str::<Port>(port.as_slice()).unwrap(),
None => 25
None => 25
},
// my hostname
match matches.opt_str("m") {
Some(ref my_hostname) => my_hostname.as_slice(),
None => "localhost"
}
},
)
{
Ok(..) => info!("Email sent successfully"),
Err(error) => error!("{}", error)
Err(error) => error!("{}", error),
}
}

View File

@@ -44,14 +44,14 @@ pub struct Client<S> {
/// Value is None before HELO/EHLO
server_info: Option<ServerInfo>,
/// Transaction state, to check the sequence of commands
state: TransactionState
state: TransactionState,
}
macro_rules! try_smtp (
($err: expr $client: ident) => ({
match $err {
Ok(val) => val,
Err(err) => fail_with_err!(err $client)
Err(err) => fail_with_err!(err $client),
}
})
)
@@ -81,7 +81,7 @@ impl<S> Client<S> {
server_addr: addr.to_socket_addr().unwrap(),
my_hostname: my_hostname.unwrap_or("localhost").to_string(),
server_info: None,
state: TransactionState::new()
state: TransactionState::new(),
}
}
}
@@ -112,7 +112,7 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
try_smtp!(Err(error) self)
},
},
_ => {}
_ => {},
}
// Print server information
@@ -229,7 +229,7 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
self.server_info = Some(
ServerInfo{
name: get_first_word(result.message.as_ref().unwrap().as_slice()).to_string(),
esmtp_features: None
esmtp_features: None,
}
);
Ok(result)
@@ -258,14 +258,14 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
None => fail_with_err!(Response{
code: 503,
message: Some("Bad sequence of commands".to_string())
} self)
} self),
};
// Checks message encoding according to the server's capability
// TODO : Add an encoding check.
let options = match server_info.supports_feature(extension::EightBitMime) {
Some(extension) => Some(vec![extension.client_mail_option().unwrap().to_string()]),
None => None
None => None,
};
self.send_command(

View File

@@ -25,7 +25,7 @@ pub struct ServerInfo {
///
/// It contains the features supported by the server and known by the `Extension` module.
/// The `None` value means the server does not support ESMTP.
pub esmtp_features: Option<Vec<Extension>>
pub esmtp_features: Option<Vec<Extension>>,
}
impl Show for ServerInfo {
@@ -35,7 +35,7 @@ impl Show for ServerInfo {
self.name,
match self.esmtp_features {
Some(ref features) => features.to_string(),
None => "no supported features".to_string()
None => "no supported features".to_string(),
}
).as_bytes()
)
@@ -54,7 +54,7 @@ impl ServerInfo {
}
None
},
None => None
None => None,
}
}
}

View File

@@ -21,8 +21,7 @@ use common::SP;
/// 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`
/// and must not be used by clients: `SEND`, `SOML`, `SAML`, `TURN`.
#[deriving(PartialEq,Eq,Clone)]
pub enum Command {
/// A fake command to represent the connection step
@@ -96,7 +95,7 @@ impl Command {
Verify(ref address) => address.is_ascii(),
Expand(ref address) => address.is_ascii(),
Help(Some(ref argument)) => argument.is_ascii(),
_ => true
_ => true,
}
}

View File

@@ -9,6 +9,8 @@
//! Constants defined in SMTP RFCs
#![unstable]
use std::io::net::ip::Port;
/// Default smtp port
@@ -26,9 +28,13 @@ pub static SUBMISSION_PORT: Port = 587;
/// The word separator for SMTP transactions
pub static SP: &'static str = " ";
/// The line ending for SMTP transactions
/// 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";
/// The ending of message content

View File

@@ -9,6 +9,8 @@
//! Error and result type for SMTP clients
#![unstable]
use std::error::Error;
use std::io::IoError;
use std::error::FromError;
@@ -48,7 +50,7 @@ impl FromError<IoError> for SmtpError {
SmtpError {
kind: InternalIoError(err),
desc: "An internal IO error ocurred.",
detail: None
detail: None,
}
}
}
@@ -79,7 +81,7 @@ impl FromError<Response> for SmtpError {
SmtpError {
kind: kind,
desc: desc,
detail: None
detail: None,
}
}
}
@@ -89,7 +91,7 @@ impl FromError<&'static str> for SmtpError {
SmtpError {
kind: UnknownError(string.to_string()),
desc: "an unknown error occured during the SMTP transaction",
detail: None
detail: None,
}
}
}
@@ -119,5 +121,5 @@ impl Error for SmtpError {
}
}
/// smtp result type
/// SMTP result type
pub type SmtpResult = Result<Response, SmtpError>;

View File

@@ -35,16 +35,16 @@ pub enum Extension {
/// SIZE keyword
///
/// RFC 1427 : https://tools.ietf.org/html/rfc1427
Size(uint)
Size(uint),
}
impl Show for Extension {
fn fmt(&self, f: &mut Formatter) -> Result {
f.write(
match self {
&EightBitMime => "8BITMIME".to_string(),
&SmtpUtfEight => "SMTPUTF8".to_string(),
&StartTls => "STARTTLS".to_string(),
&EightBitMime => "8BITMIME".to_string(),
&SmtpUtfEight => "SMTPUTF8".to_string(),
&StartTls => "STARTTLS".to_string(),
&Size(ref size) => format!("SIZE={}", size)
}.as_bytes()
)
@@ -60,13 +60,13 @@ impl FromStr for Extension {
"8BITMIME" => Some(EightBitMime),
"SMTPUTF8" => Some(SmtpUtfEight),
"STARTTLS" => Some(StartTls),
_ => None
_ => None,
},
2 => match (splitted[0], from_str::<uint>(splitted[1])) {
("SIZE", Some(size)) => Some(Size(size)),
_ => None
_ => None,
},
_ => None
_ => None,
}
}
}
@@ -79,7 +79,7 @@ impl Extension {
}
match (*self, other) {
(Size(_), Size(_)) => true,
_ => false
_ => false,
}
}
@@ -91,10 +91,10 @@ impl Extension {
Some(Response{code: 250, message}) => {
match from_str::<Extension>(message.unwrap().as_slice()) {
Some(keyword) => esmtp_features.push(keyword),
None => ()
None => (),
}
},
_ => ()
_ => (),
}
}
Some(esmtp_features)
@@ -104,7 +104,7 @@ impl Extension {
pub fn client_mail_option(&self) -> Option<&str> {
match *self {
EightBitMime => Some("BODY=8BITMIME"),
_ => None
_ => None,
}
}
}

View File

@@ -9,21 +9,15 @@
//! # Rust SMTP library
//!
//! The client does its best to follow RFC 5321 (https://tools.ietf.org/html/rfc5321).
//! The client does its best to follow [RFC 5321](https://tools.ietf.org/html/rfc5321), but is still
//! a work in progress.
//!
//! It also implements the following extensions :
//! It will eventually implement the following extensions :
//!
//! * 8BITMIME (RFC 6152 : https://tools.ietf.org/html/rfc6152)
//! * SIZE (RFC 1427 : https://tools.ietf.org/html/rfc1427)
//!
//! ## What this client is NOT made for
//!
//! Send emails to public email servers. It is not designed to smartly handle servers responses,
//! to rate-limit emails, to make retries, and all that complicated stuff needed to politely
//! talk to public servers.
//!
//! What this client does is basically try once to send the email, and say if it worked.
//! It should only be used to transfer emails to a relay server.
//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531))
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954))
//!
//! ## Usage
//!
@@ -39,12 +33,12 @@
//! let mut email_client: Client<TcpStream> =
//! Client::new(
//! ("localhost", SMTP_PORT), // server socket
//! Some("myhost") // my hostname (default is localhost)
//! Some("myhost"), // my hostname (default is localhost)
//! );
//! let result = email_client.send_mail::<TcpStream>(
//! "user@example.com", // sender (reverse-path)
//! ["user@example.org"], // recipient list
//! "Test email" // email content
//! "Test email", // email content
//! );
//! ```
//!
@@ -60,7 +54,7 @@
//! let mut email_client: Client<TcpStream> =
//! Client::new(
//! ("localhost", SMTP_PORT), // server socket
//! Some("myhost") // my hostname (default is localhost)
//! Some("myhost"), // my hostname (default is localhost)
//! );
//! let _ = email_client.connect();
//! let _ = email_client.ehlo::<TcpStream>();

View File

@@ -32,7 +32,7 @@ impl Show for Response {
f.write(
match self.clone().message {
Some(message) => format!("{} {}", self.code, message),
None => format!("{}", self.code)
None => format!("{}", self.code),
}.as_bytes()
)
}
@@ -50,8 +50,7 @@ impl FromStr for Response {
code: code,
message: None
}),
None => None
None => None,
}
// If we have a code and a message
} else {
@@ -64,8 +63,7 @@ impl FromStr for Response {
code: code,
message: Some(message.to_string())
}),
_ => None
_ => None,
}
}
}

View File

@@ -22,7 +22,7 @@ pub fn quote_email_address(address: &str) -> String {
_ => match (address.slice_to(1),
address.slice_from(address.len() - 1)) {
("<", ">") => address.to_string(),
_ => format!("<{}>", address)
_ => format!("<{}>", address),
}
}
}
@@ -35,7 +35,7 @@ pub fn unquote_email_address(address: &str) -> &str {
_ => match (address.slice_to(1),
address.slice_from(address.len() - 1)) {
("<", ">") => address.slice(1, address.len() - 1),
_ => address
_ => address,
}
}
}
@@ -58,7 +58,7 @@ pub fn get_first_word(string: &str) -> &str {
string.split_str(CRLF).next().unwrap().splitn(1, ' ').next().unwrap()
}
/// Returns the string replacing all the CRLF with "<CRLF>"
/// Returns the string replacing all the CRLF with "\<CRLF\>"
#[inline]
pub fn escape_crlf(string: &str) -> String {
replace(string, CRLF, "<CR><LF>")

View File

@@ -38,12 +38,12 @@ impl Show for TransactionState {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write(
match *self {
Unconnected => "Unconnected",
Connected => "Connected",
HelloSent => "HelloSent",
MailSent => "MailSent",
Unconnected => "Unconnected",
Connected => "Connected",
HelloSent => "HelloSent",
MailSent => "MailSent",
RecipientSent => "RecipientSent",
DataSent => "DataSent"
DataSent => "DataSent",
}.as_bytes()
)
}
@@ -78,7 +78,7 @@ impl TransactionState {
(RecipientSent, &command::Recipient(_, _)) => true,
(RecipientSent, &command::Data) => true,
// Everything else
(_, _) => false
(_, _) => false,
}
}
@@ -107,7 +107,7 @@ impl TransactionState {
(RecipientSent, &command::Recipient(_, _)) => Some(RecipientSent),
(RecipientSent, &command::Data) => Some(DataSent),
// Everything else
(_, _) => None
(_, _) => None,
}
}
}