Add documentation
This commit is contained in:
@@ -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
|
||||
-------------
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
10
src/error.rs
10
src/error.rs
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
src/lib.rs
26
src/lib.rs
@@ -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>();
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user