diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b850440..ac4628e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,8 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features=native-tls + args: --no-default-features --features=native-tls,builder,r2d2,smtp-transport,file-transport,sendmail-transport + - run: rm target/debug/deps/liblettre-* - uses: actions-rs/cargo@v1 with: command: test @@ -68,7 +69,7 @@ jobs: with: command: clippy args: -- -D warnings - + coverage: name: Coverage runs-on: ubuntu-latest @@ -85,8 +86,8 @@ jobs: command: test args: --no-fail-fast env: - CARGO_INCREMENTAL: '0' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zno-landing-pads' + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zno-landing-pads" - id: coverage uses: actions-rs/grcov@v0.1 - name: Coveralls upload diff --git a/Cargo.toml b/Cargo.toml index 09dee61..d859d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ webpki = { version = "^0.21", optional = true } criterion = "^0.3" env_logger = "^0.7" glob = "^0.3" +walkdir = "^2" [[bench]] harness = false @@ -46,12 +47,11 @@ name = "transport_smtp" [features] builder = ["email", "mime", "time", "base64", "uuid"] connection-pool = ["r2d2"] -default = ["file-transport", "smtp-transport", "sendmail-transport", "ssl-rustls", "builder"] +default = ["file-transport", "smtp-transport", "sendmail-transport", "rustls-tls", "builder"] file-transport = ["serde", "serde_json"] +rustls-tls = ["webpki", "rustls"] sendmail-transport = [] smtp-transport = ["bufstream", "base64", "nom", "hostname"] -ssl-native = ["native-tls"] -ssl-rustls = ["rustls", "webpki"] unstable = [] [[example]] diff --git a/README.md b/README.md index 6de8775..8f17ea0 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ lettre = "0.9" ```rust,no_run extern crate lettre; -use lettre::{SmtpClient, Transport, Email, mime::TEXT_PLAIN}; +use lettre::{SmtpClient, Transport, Email, builder::mime::TEXT_PLAIN}; use std::path::Path; fn main() { diff --git a/src/builder/mod.rs b/src/builder/mod.rs index 73b58b8..a1643e8 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -1,6 +1,7 @@ use crate::{error::Error as LettreError, EmailAddress, Envelope, SendableEmail}; pub use email::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; use error::Error; +pub use mime; use mime::Mime; use std::ffi::OsStr; use std::fs; diff --git a/src/lib.rs b/src/lib.rs index 482b17d..e8b2d5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,8 @@ pub mod sendmail; pub mod smtp; pub mod stub; +#[cfg(feature = "builder")] +pub use crate::builder::Email; use crate::error::EmailResult; use crate::error::Error; #[cfg(feature = "file-transport")] diff --git a/src/smtp/client/net.rs b/src/smtp/client/net.rs index 7d151b8..7f89475 100644 --- a/src/smtp/client/net.rs +++ b/src/smtp/client/net.rs @@ -3,13 +3,14 @@ use crate::smtp::client::mock::MockStream; use crate::smtp::error::Error; #[cfg(feature = "native-tls")] -use native_tls::{Protocol, TlsConnector, TlsStream}; +use native_tls::{TlsConnector, TlsStream}; #[cfg(feature = "rustls")] use rustls::{ClientConfig, ClientSession}; #[cfg(feature = "native-tls")] use std::io::ErrorKind; use std::io::{self, Read, Write}; use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream}; +#[cfg(feature = "rustls")] use std::sync::Arc; use std::time::Duration; @@ -45,12 +46,6 @@ impl ClientTlsParameters { } } -/// Accepted protocols by default. -/// This removes TLS 1.0 and 1.1 compared to tls-native defaults. -/// This is also rustls' default behavior -#[cfg(feature = "native-tls")] -const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12; - /// Represents the different types of underlying network streams pub enum NetworkStream { /// Plain TCP stream @@ -161,7 +156,7 @@ impl Connector for NetworkStream { .connector .connect(context.domain.as_ref(), tcp_stream) .map(|tls| NetworkStream::Tls(Box::new(tls))) - .map_err(|e| io::Error::new(ErrorKind::Other, e)), + .map_err(|e| Error::Io(io::Error::new(ErrorKind::Other, e))), #[cfg(feature = "rustls")] Some(context) => { let domain = webpki::DNSNameRef::try_from_ascii_str(&context.domain)?; @@ -183,7 +178,7 @@ impl Connector for NetworkStream { .connect(tls_parameters.domain.as_ref(), stream.try_clone().unwrap()) { Ok(tls_stream) => NetworkStream::Tls(Box::new(tls_stream)), - Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), + Err(err) => return Err(Error::Io(io::Error::new(ErrorKind::Other, err))), }, #[cfg(feature = "rustls")] NetworkStream::Tcp(ref mut stream) => { diff --git a/src/smtp/error.rs b/src/smtp/error.rs index 9c47ba7..0c7565d 100644 --- a/src/smtp/error.rs +++ b/src/smtp/error.rs @@ -41,6 +41,7 @@ pub enum Error { /// Parsing error Parsing(nom::error::ErrorKind), /// Invalid hostname + #[cfg(feature = "rustls-tls")] InvalidDNSName(webpki::InvalidDNSNameError), } @@ -73,6 +74,7 @@ impl StdError for Error { #[cfg(feature = "native-tls")] Tls(ref err) => err.description(), Parsing(ref err) => err.description(), + #[cfg(feature = "rustls-tls")] InvalidDNSName(ref err) => err.description(), } } @@ -124,6 +126,7 @@ impl From for Error { } } +#[cfg(feature = "rustls-tls")] impl From for Error { fn from(err: webpki::InvalidDNSNameError) -> Error { InvalidDNSName(err) diff --git a/src/smtp/mod.rs b/src/smtp/mod.rs index 0e46d7b..4b7999d 100644 --- a/src/smtp/mod.rs +++ b/src/smtp/mod.rs @@ -17,8 +17,6 @@ use crate::smtp::authentication::{ Credentials, Mechanism, DEFAULT_ENCRYPTED_MECHANISMS, DEFAULT_UNENCRYPTED_MECHANISMS, }; use crate::smtp::client::net::ClientTlsParameters; -#[cfg(feature = "native-tls")] -use crate::smtp::client::net::DEFAULT_TLS_MIN_PROTOCOL; use crate::smtp::client::InnerClient; use crate::smtp::commands::*; use crate::smtp::error::{Error, SmtpResult}; @@ -26,7 +24,9 @@ use crate::smtp::extension::{ClientId, Extension, MailBodyParameter, MailParamet use crate::{SendableEmail, Transport}; use log::{debug, info}; #[cfg(feature = "native-tls")] -use native_tls::TlsConnector; +use native_tls::{Protocol, TlsConnector}; +#[cfg(feature = "rustls")] +use rustls::ClientConfig; use std::net::{SocketAddr, ToSocketAddrs}; use std::time::Duration; @@ -51,6 +51,12 @@ pub const SUBMISSION_PORT: u16 = 587; /// Default submission over TLS port pub const SUBMISSIONS_PORT: u16 = 465; +/// Accepted protocols by default. +/// This removes TLS 1.0 and 1.1 compared to tls-native defaults. +/// This is also rustls' default behavior +#[cfg(feature = "native-tls")] +const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12; + /// How to apply TLS to a client connection #[derive(Clone)] #[allow(missing_debug_implementations)] @@ -150,6 +156,16 @@ impl SmtpClient { ) } + #[cfg(feature = "rustls")] + pub fn new_simple(domain: &str) -> Result { + let tls_parameters = ClientTlsParameters::new(domain.to_string(), ClientConfig::new()); + + SmtpClient::new( + (domain, SUBMISSIONS_PORT), + ClientSecurity::Wrapper(tls_parameters), + ) + } + /// Creates a new local SMTP client to port 25 pub fn new_unencrypted_localhost() -> Result { SmtpClient::new(("localhost", SMTP_PORT), ClientSecurity::None) diff --git a/tests/skeptic.rs b/tests/skeptic.rs index 32ce188..5920a2f 100644 --- a/tests/skeptic.rs +++ b/tests/skeptic.rs @@ -1,23 +1,21 @@ -use glob::glob; -use std::env; -use std::env::consts::EXE_EXTENSION; +use std::env::{self, consts::EXE_EXTENSION}; use std::path::Path; use std::process::Command; +use walkdir::WalkDir; #[test] fn book_test() { - let mut book_path = env::current_dir().unwrap(); - book_path.push( - Path::new(file!()) - .parent() - .unwrap() - .parent() - .unwrap() - .join("../website/content/sending-messages"), - ); // For some reasons, calling .parent() once more gives us None... + skeptic_test(Path::new("README.md")); - for md in glob(&format!("{}/*.md", book_path.to_str().unwrap())).unwrap() { - skeptic_test(&md.unwrap()); + for entry in WalkDir::new("website").into_iter().filter(|e| { + e.as_ref() + .unwrap() + .path() + .extension() + .map(|ex| ex == "md") + .unwrap_or(false) + }) { + skeptic_test(entry.unwrap().path()); } } diff --git a/website/src/creating-messages/email.md b/website/src/creating-messages/email.md index 449f587..698850e 100644 --- a/website/src/creating-messages/email.md +++ b/website/src/creating-messages/email.md @@ -8,9 +8,11 @@ The `email` part builds email messages. For now, it does not support attachments An email is built using an `EmailBuilder`. The simplest email could be: ```rust -extern crate lettre_email; +# #[cfg(feature = "builder")] +# { +extern crate lettre; -use lettre_email::Email; +use lettre::Email; fn main() { // Create an email @@ -22,9 +24,10 @@ fn main() { .subject("Hi, Hello world") .alternative("

Hi, Hello world.

", "Hi, Hello world.") .build(); - + assert!(email.is_ok()); } +# } ``` When the `build` method is called, the `EmailBuilder` will add the missing headers (like diff --git a/website/src/sending-messages/file.md b/website/src/sending-messages/file.md index a7dac05..2570702 100644 --- a/website/src/sending-messages/file.md +++ b/website/src/sending-messages/file.md @@ -5,6 +5,8 @@ The file transport writes the emails to the given directory. The name of the fil It can be useful for testing purposes, or if you want to keep track of sent messages. ```rust +# #[cfg(feature = "file-transport")] +# { extern crate lettre; use std::env::temp_dir; @@ -23,10 +25,11 @@ fn main() { "id".to_string(), "Hello world".to_string().into_bytes(), ); - + let result = sender.send(email); assert!(result.is_ok()); } +# } ``` Example result in `/tmp/b7c211bc-9811-45ce-8cd9-68eab575d695.txt`: diff --git a/website/src/sending-messages/sendmail.md b/website/src/sending-messages/sendmail.md index 5800c2a..95c991b 100644 --- a/website/src/sending-messages/sendmail.md +++ b/website/src/sending-messages/sendmail.md @@ -3,6 +3,8 @@ The sendmail transport sends the email using the local sendmail command. ```rust,no_run +# #[cfg(feature = "sendmail-transport")] +# { extern crate lettre; use lettre::sendmail::SendmailTransport; @@ -17,9 +19,10 @@ fn main() { "id".to_string(), "Hello world".to_string().into_bytes(), ); - + let mut sender = SendmailTransport::new(); let result = sender.send(email); assert!(result.is_ok()); } +# } ``` diff --git a/website/src/sending-messages/smtp.md b/website/src/sending-messages/smtp.md index c10e50b..61a730a 100644 --- a/website/src/sending-messages/smtp.md +++ b/website/src/sending-messages/smtp.md @@ -18,6 +18,8 @@ The relay server can be the local email server, a specific host or a third-party This is the most basic example of usage: ```rust,no_run +# #[cfg(feature = "smtp-transport")] +# { extern crate lettre; use lettre::{SendableEmail, EmailAddress, Transport, Envelope, SmtpClient}; @@ -31,20 +33,23 @@ fn main() { "id".to_string(), "Hello world".to_string().into_bytes(), ); - + // Open a local connection on port 25 let mut mailer = SmtpClient::new_unencrypted_localhost().unwrap().transport(); // Send the email let result = mailer.send(email); - + assert!(result.is_ok()); } +# } ``` #### Complete example ```rust,no_run +# #[cfg(feature = "smtp-transport")] +# { extern crate lettre; use lettre::smtp::authentication::{Credentials, Mechanism}; @@ -61,7 +66,7 @@ fn main() { "id1".to_string(), "Hello world".to_string().into_bytes(), ); - + let email_2 = SendableEmail::new( Envelope::new( Some(EmailAddress::new("user@localhost".to_string()).unwrap()), @@ -70,7 +75,7 @@ fn main() { "id2".to_string(), "Hello world a second time".to_string().into_bytes(), ); - + // Connect to a remote server on a custom port let mut mailer = SmtpClient::new_simple("server.tld").unwrap() // Set the name sent during EHLO/HELO, default is `localhost` @@ -83,22 +88,25 @@ fn main() { .authentication_mechanism(Mechanism::Plain) // Enable connection reuse .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).transport(); - + let result_1 = mailer.send(email_1); assert!(result_1.is_ok()); - + // The second email will use the same connection let result_2 = mailer.send(email_2); assert!(result_2.is_ok()); - + // Explicitly close the SMTP transaction as we enabled connection reuse mailer.close(); } +# } ``` You can specify custom TLS settings: ```rust,no_run +# #[cfg(feature = "native-tls")] +# { extern crate native_tls; extern crate lettre; @@ -144,6 +152,7 @@ fn main() { mailer.close(); } +# } ``` #### Lower level @@ -152,6 +161,8 @@ You can also send commands, here is a simple email transaction without error handling: ```rust,no_run +# #[cfg(feature = "smtp-transport")] +# { extern crate lettre; use lettre::EmailAddress; @@ -175,5 +186,5 @@ fn main() { let _ = email_client.message(Box::new("Test email".as_bytes())); let _ = email_client.command(QuitCommand); } +# } ``` - diff --git a/website/src/sending-messages/stub.md b/website/src/sending-messages/stub.md index 2d13a5b..b7b9c48 100644 --- a/website/src/sending-messages/stub.md +++ b/website/src/sending-messages/stub.md @@ -18,7 +18,7 @@ fn main() { "id".to_string(), "Hello world".to_string().into_bytes(), ); - + let mut sender = StubTransport::new_positive(); let result = sender.send(email); assert!(result.is_ok());