Add ClientBuilder helper to make setting up TLS connections easy. (#197)
Also replaces connect() and connect_starttls() with ClientBuilder.
This commit is contained in:
parent
c443a3ab5d
commit
7204697dd9
19 changed files with 314 additions and 237 deletions
10
.cirrus.yml
10
.cirrus.yml
|
|
@ -11,12 +11,12 @@ task:
|
||||||
- . $HOME/.cargo/env
|
- . $HOME/.cargo/env
|
||||||
check_script:
|
check_script:
|
||||||
- . $HOME/.cargo/env
|
- . $HOME/.cargo/env
|
||||||
- cargo check --all-targets
|
- cargo check --all-targets --all-features
|
||||||
build_script:
|
build_script:
|
||||||
- . $HOME/.cargo/env
|
- . $HOME/.cargo/env
|
||||||
- cargo build --all-targets --verbose
|
- cargo build --all-targets --verbose --all-features
|
||||||
test_script:
|
test_script:
|
||||||
- . $HOME/.cargo/env
|
- . $HOME/.cargo/env
|
||||||
- cargo test --examples
|
- cargo test --examples --all-features
|
||||||
- cargo test --doc
|
- cargo test --doc --all-features
|
||||||
- cargo test --lib
|
- cargo test --lib --all-features
|
||||||
|
|
|
||||||
14
Cargo.toml
14
Cargo.toml
|
|
@ -15,10 +15,12 @@ categories = ["email", "network-programming"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tls = ["native-tls"]
|
tls = ["native-tls"]
|
||||||
|
rustls-tls = ["rustls-connector"]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
native-tls = { version = "0.2.2", optional = true }
|
native-tls = { version = "0.2.2", optional = true }
|
||||||
|
rustls-connector = { version = "0.13.1", optional = true }
|
||||||
regex = "1.0"
|
regex = "1.0"
|
||||||
bufstream = "0.1"
|
bufstream = "0.1"
|
||||||
imap-proto = "0.14.1"
|
imap-proto = "0.14.1"
|
||||||
|
|
@ -45,6 +47,18 @@ required-features = ["default"]
|
||||||
name = "idle"
|
name = "idle"
|
||||||
required-features = ["default"]
|
required-features = ["default"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "rustls"
|
||||||
|
required-features = ["rustls-tls"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "starttls"
|
||||||
|
required-features = ["default"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "timeout"
|
||||||
|
required-features = ["default"]
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "imap_integration"
|
name = "imap_integration"
|
||||||
required-features = ["default"]
|
required-features = ["default"]
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -22,7 +22,7 @@ results](https://dev.azure.com/jonhoo/jonhoo/_build/latest?definitionId=11&branc
|
||||||
|
|
||||||
[@jonhoo]: https://thesquareplanet.com/
|
[@jonhoo]: https://thesquareplanet.com/
|
||||||
|
|
||||||
To connect, use the [`connect`] function. This gives you an unauthenticated [`Client`]. You can
|
To connect, use the [`ClientBuilder`]. This gives you an unauthenticated [`Client`]. You can
|
||||||
then use [`Client::login`] or [`Client::authenticate`] to perform username/password or
|
then use [`Client::login`] or [`Client::authenticate`] to perform username/password or
|
||||||
challenge/response authentication respectively. This in turn gives you an authenticated
|
challenge/response authentication respectively. This in turn gives you an authenticated
|
||||||
[`Session`], which lets you access the mailboxes at the server.
|
[`Session`], which lets you access the mailboxes at the server.
|
||||||
|
|
@ -34,16 +34,9 @@ in the documentation for the various types and methods and read the raw text the
|
||||||
Below is a basic client example. See the `examples/` directory for more.
|
Below is a basic client example. See the `examples/` directory for more.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate imap;
|
|
||||||
extern crate native_tls;
|
|
||||||
|
|
||||||
fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
||||||
let domain = "imap.example.com";
|
|
||||||
let tls = native_tls::TlsConnector::builder().build().unwrap();
|
|
||||||
|
|
||||||
// we pass in the domain twice to check that the server's TLS
|
let client = imap::ClientBuilder::new("imap.example.com", 993).native_tls()?;
|
||||||
// certificate is valid for the domain we're connecting to.
|
|
||||||
let client = imap::connect((domain, 993), domain, &tls).unwrap();
|
|
||||||
|
|
||||||
// the client we have here is unauthenticated.
|
// the client we have here is unauthenticated.
|
||||||
// to do anything useful with the e-mails, we need to log in
|
// to do anything useful with the e-mails, we need to log in
|
||||||
|
|
@ -90,7 +83,8 @@ default-features = false
|
||||||
```
|
```
|
||||||
|
|
||||||
Even without `native_tls`, you can still use TLS by leveraging the pure Rust `rustls`
|
Even without `native_tls`, you can still use TLS by leveraging the pure Rust `rustls`
|
||||||
crate. See the example/rustls.rs file for a working example.
|
crate, which is enabled with the `rustls-tls` feature. See the example/rustls.rs file
|
||||||
|
for a working example.
|
||||||
|
|
||||||
## Running the test suite
|
## Running the test suite
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ jobs:
|
||||||
- template: install-rust.yml@templates
|
- template: install-rust.yml@templates
|
||||||
parameters:
|
parameters:
|
||||||
rust: $(rust)
|
rust: $(rust)
|
||||||
- script: cargo check --all-targets
|
- script: cargo check --all-targets --all-features
|
||||||
displayName: cargo check
|
displayName: cargo check
|
||||||
- script: cargo test --examples
|
- script: cargo test --examples --all-features
|
||||||
displayName: cargo test --examples
|
displayName: cargo test --examples
|
||||||
- script: cargo test --doc
|
- script: cargo test --doc --all-features
|
||||||
displayName: cargo test --doc
|
displayName: cargo test --doc
|
||||||
- script: cargo test --lib
|
- script: cargo test --lib --all-features
|
||||||
displayName: cargo test --lib
|
displayName: cargo test --lib
|
||||||
- script: |
|
- script: |
|
||||||
set -e
|
set -e
|
||||||
|
|
@ -75,6 +75,18 @@ jobs:
|
||||||
greenmail: greenmail
|
greenmail: greenmail
|
||||||
env:
|
env:
|
||||||
TEST_HOST: greenmail
|
TEST_HOST: greenmail
|
||||||
|
- job: features
|
||||||
|
displayName: "Check feature combinations"
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- template: install-rust.yml@templates
|
||||||
|
parameters:
|
||||||
|
rust: stable
|
||||||
|
- script: cargo install cargo-hack
|
||||||
|
displayName: install cargo-hack
|
||||||
|
- script: cargo hack --feature-powerset check --all-targets
|
||||||
|
displayName: cargo hack
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
repositories:
|
repositories:
|
||||||
|
|
|
||||||
17
codecov.yml
Normal file
17
codecov.yml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
coverage:
|
||||||
|
range: 70..100
|
||||||
|
round: down
|
||||||
|
precision: 2
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
threshold: 2%
|
||||||
|
|
||||||
|
# Tests aren't important for coverage
|
||||||
|
ignore:
|
||||||
|
- "tests"
|
||||||
|
|
||||||
|
# Make less noisy comments
|
||||||
|
comment:
|
||||||
|
layout: "files"
|
||||||
|
require_changes: yes
|
||||||
|
|
@ -6,5 +6,7 @@ This directory contains examples of working with the IMAP client.
|
||||||
Examples:
|
Examples:
|
||||||
* basic - This is a very basic example of using the client.
|
* basic - This is a very basic example of using the client.
|
||||||
* gmail_oauth2 - This is an example using oauth2 for logging into gmail as a secure appplication.
|
* gmail_oauth2 - This is an example using oauth2 for logging into gmail as a secure appplication.
|
||||||
|
* idle - This is an example showing how to use IDLE to monitor a mailbox.
|
||||||
* rustls - This demonstrates how to use Rustls instead of Openssl for secure connections (helpful for cross compilation).
|
* rustls - This demonstrates how to use Rustls instead of Openssl for secure connections (helpful for cross compilation).
|
||||||
* timeout - This demonstrates how to use timeouts while connecting to an IMAP server.
|
* starttls - This is an example showing how to use STARTTLS after connecting over plaintext.
|
||||||
|
* timeout - This demonstrates how to use timeouts while connecting to an IMAP server by using a custom TCP/TLS stream initialization and creating a `Client` directly instead of using the `ClientBuilder`.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
extern crate imap;
|
extern crate imap;
|
||||||
extern crate native_tls;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// To connect to the gmail IMAP server with this you will need to allow unsecure apps access.
|
// To connect to the gmail IMAP server with this you will need to allow unsecure apps access.
|
||||||
|
|
@ -9,12 +8,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
||||||
let domain = "imap.example.com";
|
let client = imap::ClientBuilder::new("imap.example.com", 993).native_tls()?;
|
||||||
let tls = native_tls::TlsConnector::builder().build().unwrap();
|
|
||||||
|
|
||||||
// we pass in the domain twice to check that the server's TLS
|
|
||||||
// certificate is valid for the domain we're connecting to.
|
|
||||||
let client = imap::connect((domain, 993), domain, &tls).unwrap();
|
|
||||||
|
|
||||||
// the client we have here is unauthenticated.
|
// the client we have here is unauthenticated.
|
||||||
// to do anything useful with the e-mails, we need to log in
|
// to do anything useful with the e-mails, we need to log in
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate imap;
|
extern crate imap;
|
||||||
extern crate native_tls;
|
|
||||||
|
|
||||||
use native_tls::TlsConnector;
|
|
||||||
|
|
||||||
struct GmailOAuth2 {
|
struct GmailOAuth2 {
|
||||||
user: String,
|
user: String,
|
||||||
|
|
@ -25,11 +22,10 @@ fn main() {
|
||||||
user: String::from("sombody@gmail.com"),
|
user: String::from("sombody@gmail.com"),
|
||||||
access_token: String::from("<access_token>"),
|
access_token: String::from("<access_token>"),
|
||||||
};
|
};
|
||||||
let domain = "imap.gmail.com";
|
|
||||||
let port = 993;
|
let client = imap::ClientBuilder::new("imap.gmail.com", 993)
|
||||||
let socket_addr = (domain, port);
|
.native_tls()
|
||||||
let ssl_connector = TlsConnector::builder().build().unwrap();
|
.expect("Could not connect to imap.gmail.com");
|
||||||
let client = imap::connect(socket_addr, domain, &ssl_connector).unwrap();
|
|
||||||
|
|
||||||
let mut imap_session = match client.authenticate("XOAUTH2", &gmail_auth) {
|
let mut imap_session = match client.authenticate("XOAUTH2", &gmail_auth) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use native_tls::TlsConnector;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
|
|
@ -39,9 +38,10 @@ struct Opt {
|
||||||
fn main() {
|
fn main() {
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
let ssl_conn = TlsConnector::builder().build().unwrap();
|
let client = imap::ClientBuilder::new(opt.server.clone(), opt.port)
|
||||||
let client = imap::connect((opt.server.clone(), opt.port), opt.server, &ssl_conn)
|
.native_tls()
|
||||||
.expect("Could not connect to imap server");
|
.expect("Could not connect to imap server");
|
||||||
|
|
||||||
let mut imap = client
|
let mut imap = client
|
||||||
.login(opt.username, opt.password)
|
.login(opt.username, opt.password)
|
||||||
.expect("Could not authenticate");
|
.expect("Could not authenticate");
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
extern crate imap;
|
extern crate imap;
|
||||||
extern crate rustls_connector;
|
|
||||||
|
|
||||||
use std::{env, error::Error, net::TcpStream};
|
use std::{env, error::Error};
|
||||||
|
|
||||||
use rustls_connector::RustlsConnector;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// Read config from environment or .env file
|
// Read config from environment or .env file
|
||||||
|
|
@ -25,14 +22,7 @@ fn fetch_inbox_top(
|
||||||
password: String,
|
password: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> Result<Option<String>, Box<dyn Error>> {
|
) -> Result<Option<String>, Box<dyn Error>> {
|
||||||
// Setup Rustls TcpStream
|
let client = imap::ClientBuilder::new(&host, port).rustls()?;
|
||||||
let stream = TcpStream::connect((host.as_ref(), port))?;
|
|
||||||
let tls = RustlsConnector::default();
|
|
||||||
let tlsstream = tls.connect(&host, stream)?;
|
|
||||||
|
|
||||||
// we pass in the domain twice to check that the server's TLS
|
|
||||||
// certificate is valid for the domain we're connecting to.
|
|
||||||
let client = imap::Client::new(tlsstream);
|
|
||||||
|
|
||||||
// the client we have here is unauthenticated.
|
// the client we have here is unauthenticated.
|
||||||
// to do anything useful with the e-mails, we need to log in
|
// to do anything useful with the e-mails, we need to log in
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* Here's an example showing how to connect to the IMAP server with STARTTLS.
|
* Here's an example showing how to connect to the IMAP server with STARTTLS.
|
||||||
* The only difference with the `basic.rs` example is when using `imap::connect_starttls()` method
|
*
|
||||||
* instead of `imap::connect()` (l. 52), and so you can connect on port 143 instead of 993
|
* The only difference is calling `starttls()` on the `ClientBuilder` before
|
||||||
* as you have to when using TLS the entire way.
|
* initiating the secure connection with `native_tls()` or `rustls()`, so you
|
||||||
|
* can connect on port 143 instead of 993.
|
||||||
*
|
*
|
||||||
* The following env vars are expected to be set:
|
* The following env vars are expected to be set:
|
||||||
* - IMAP_HOST
|
* - IMAP_HOST
|
||||||
|
|
@ -11,9 +12,7 @@
|
||||||
* - IMAP_PORT (supposed to be 143)
|
* - IMAP_PORT (supposed to be 143)
|
||||||
*/
|
*/
|
||||||
extern crate imap;
|
extern crate imap;
|
||||||
extern crate native_tls;
|
|
||||||
|
|
||||||
use native_tls::TlsConnector;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
|
@ -42,13 +41,10 @@ fn fetch_inbox_top(
|
||||||
password: String,
|
password: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
) -> Result<Option<String>, Box<dyn Error>> {
|
) -> Result<Option<String>, Box<dyn Error>> {
|
||||||
let domain: &str = host.as_str();
|
let client = imap::ClientBuilder::new(&host, port)
|
||||||
|
.starttls()
|
||||||
let tls = TlsConnector::builder().build().unwrap();
|
.native_tls()
|
||||||
|
.expect("Could not connect to server");
|
||||||
// we pass in the domain twice to check that the server's TLS
|
|
||||||
// certificate is valid for the domain we're connecting to.
|
|
||||||
let client = imap::connect_starttls((domain, port), domain, &tls).unwrap();
|
|
||||||
|
|
||||||
// the client we have here is unauthenticated.
|
// the client we have here is unauthenticated.
|
||||||
// to do anything useful with the e-mails, we need to log in
|
// to do anything useful with the e-mails, we need to log in
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,7 @@ fn connect_timeout<S: AsRef<str>>(
|
||||||
Ok(client)
|
Ok(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve address and try to connect to all in order; note that this function is required to fully
|
// resolve address and try to connect to all in order
|
||||||
// mimic `imap::connect` with the usage of `ToSocketAddrs`
|
|
||||||
fn connect_all_timeout<A: ToSocketAddrs, S: AsRef<str>>(
|
fn connect_all_timeout<A: ToSocketAddrs, S: AsRef<str>>(
|
||||||
addr: A,
|
addr: A,
|
||||||
domain: S,
|
domain: S,
|
||||||
|
|
|
||||||
159
src/client.rs
159
src/client.rs
|
|
@ -1,11 +1,8 @@
|
||||||
use bufstream::BufStream;
|
use bufstream::BufStream;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use imap_proto::Response;
|
use imap_proto::Response;
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
use native_tls::{TlsConnector, TlsStream};
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::{TcpStream, ToSocketAddrs};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
@ -253,109 +250,6 @@ impl<T: Read + Write> DerefMut for Session<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to a server using a TLS-encrypted connection.
|
|
||||||
///
|
|
||||||
/// The returned [`Client`] is unauthenticated; to access session-related methods (through
|
|
||||||
/// [`Session`]), use [`Client::login`] or [`Client::authenticate`].
|
|
||||||
///
|
|
||||||
/// The domain must be passed in separately from the `TlsConnector` so that the certificate of the
|
|
||||||
/// IMAP server can be validated.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # extern crate native_tls;
|
|
||||||
/// # extern crate imap;
|
|
||||||
/// # use std::io;
|
|
||||||
/// # use native_tls::TlsConnector;
|
|
||||||
/// # fn main() {
|
|
||||||
/// let tls = TlsConnector::builder().build().unwrap();
|
|
||||||
/// let client = imap::connect(("imap.example.org", 993), "imap.example.org", &tls).unwrap();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
pub fn connect<A: ToSocketAddrs, S: AsRef<str>>(
|
|
||||||
addr: A,
|
|
||||||
domain: S,
|
|
||||||
ssl_connector: &TlsConnector,
|
|
||||||
) -> Result<Client<TlsStream<TcpStream>>> {
|
|
||||||
match TcpStream::connect(addr) {
|
|
||||||
Ok(stream) => {
|
|
||||||
let ssl_stream = match TlsConnector::connect(ssl_connector, domain.as_ref(), stream) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(Error::TlsHandshake(e)),
|
|
||||||
};
|
|
||||||
let mut socket = Client::new(ssl_stream);
|
|
||||||
|
|
||||||
socket.read_greeting()?;
|
|
||||||
Ok(socket)
|
|
||||||
}
|
|
||||||
Err(e) => Err(Error::Io(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connect to a server and upgrade to a TLS-encrypted connection.
|
|
||||||
///
|
|
||||||
/// This is the [STARTTLS](https://tools.ietf.org/html/rfc2595) equivalent to [`connect`]. All
|
|
||||||
/// notes there also apply here.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// # extern crate native_tls;
|
|
||||||
/// # extern crate imap;
|
|
||||||
/// # use std::io;
|
|
||||||
/// # use native_tls::TlsConnector;
|
|
||||||
/// # fn main() {
|
|
||||||
/// let tls = TlsConnector::builder().build().unwrap();
|
|
||||||
/// let client = imap::connect_starttls(("imap.example.org", 143), "imap.example.org", &tls).unwrap();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
pub fn connect_starttls<A: ToSocketAddrs, S: AsRef<str>>(
|
|
||||||
addr: A,
|
|
||||||
domain: S,
|
|
||||||
ssl_connector: &TlsConnector,
|
|
||||||
) -> Result<Client<TlsStream<TcpStream>>> {
|
|
||||||
match TcpStream::connect(addr) {
|
|
||||||
Ok(stream) => {
|
|
||||||
let mut socket = Client::new(stream);
|
|
||||||
socket.read_greeting()?;
|
|
||||||
socket.run_command_and_check_ok("STARTTLS")?;
|
|
||||||
TlsConnector::connect(
|
|
||||||
ssl_connector,
|
|
||||||
domain.as_ref(),
|
|
||||||
socket.conn.stream.into_inner()?,
|
|
||||||
)
|
|
||||||
.map(Client::new)
|
|
||||||
.map_err(Error::TlsHandshake)
|
|
||||||
}
|
|
||||||
Err(e) => Err(Error::Io(e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client<TcpStream> {
|
|
||||||
/// This will upgrade an IMAP client from using a regular TCP connection to use TLS.
|
|
||||||
///
|
|
||||||
/// The domain parameter is required to perform hostname verification.
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
pub fn secure<S: AsRef<str>>(
|
|
||||||
mut self,
|
|
||||||
domain: S,
|
|
||||||
ssl_connector: &TlsConnector,
|
|
||||||
) -> Result<Client<TlsStream<TcpStream>>> {
|
|
||||||
// TODO This needs to be tested
|
|
||||||
self.run_command_and_check_ok("STARTTLS")?;
|
|
||||||
TlsConnector::connect(
|
|
||||||
ssl_connector,
|
|
||||||
domain.as_ref(),
|
|
||||||
self.conn.stream.into_inner()?,
|
|
||||||
)
|
|
||||||
.map(Client::new)
|
|
||||||
.map_err(Error::TlsHandshake)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// As the pattern of returning the unauthenticated `Client` (a.k.a. `self`) back with a login error
|
// As the pattern of returning the unauthenticated `Client` (a.k.a. `self`) back with a login error
|
||||||
// is relatively common, it's abstacted away into a macro here.
|
// is relatively common, it's abstacted away into a macro here.
|
||||||
//
|
//
|
||||||
|
|
@ -375,27 +269,28 @@ macro_rules! ok_or_unauth_client_err {
|
||||||
impl<T: Read + Write> Client<T> {
|
impl<T: Read + Write> Client<T> {
|
||||||
/// Creates a new client over the given stream.
|
/// Creates a new client over the given stream.
|
||||||
///
|
///
|
||||||
/// For an example of how to use this method to provide a pure-Rust TLS integration, see the
|
/// This method primarily exists for writing tests that mock the underlying transport,
|
||||||
/// rustls.rs in the examples/ directory.
|
/// but can also be used to support IMAP over custom tunnels. If you do not need to do
|
||||||
|
/// that, then it is simpler to use the [`ClientBuilder`](crate::ClientBuilder) to get
|
||||||
|
/// a new client.
|
||||||
///
|
///
|
||||||
/// This method primarily exists for writing tests that mock the underlying transport, but can
|
/// For an example, see `examples/timeout.rs` which uses a custom timeout on the
|
||||||
/// also be used to support IMAP over custom tunnels.
|
/// tcp stream.
|
||||||
///
|
///
|
||||||
/// **Note:** In case you do need to use `Client::new` over `imap::connect`, you will need to
|
/// **Note:** In case you do need to use `Client::new` instead of the `ClientBuilder`
|
||||||
/// listen for the IMAP protocol server greeting before authenticating:
|
/// you will need to listen for the IMAP protocol server greeting before authenticating:
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # extern crate imap;
|
|
||||||
/// # extern crate native_tls;
|
|
||||||
/// # use imap::Client;
|
/// # use imap::Client;
|
||||||
/// # use native_tls::TlsConnector;
|
|
||||||
/// # use std::io;
|
/// # use std::io;
|
||||||
/// # use std::net::TcpStream;
|
/// # use std::net::TcpStream;
|
||||||
|
/// # {} #[cfg(feature = "tls")]
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// # let server = "imap.example.com";
|
/// # let server = "imap.example.com";
|
||||||
/// # let username = "";
|
/// # let username = "";
|
||||||
/// # let password = "";
|
/// # let password = "";
|
||||||
/// # let tcp = TcpStream::connect((server, 993)).unwrap();
|
/// # let tcp = TcpStream::connect((server, 993)).unwrap();
|
||||||
|
/// # use native_tls::TlsConnector;
|
||||||
/// # let ssl_connector = TlsConnector::builder().build().unwrap();
|
/// # let ssl_connector = TlsConnector::builder().build().unwrap();
|
||||||
/// # let tls = TlsConnector::connect(&ssl_connector, server.as_ref(), tcp).unwrap();
|
/// # let tls = TlsConnector::connect(&ssl_connector, server.as_ref(), tcp).unwrap();
|
||||||
/// let mut client = Client::new(tls);
|
/// let mut client = Client::new(tls);
|
||||||
|
|
@ -414,6 +309,15 @@ impl<T: Read + Write> Client<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Yield the underlying connection for this Client.
|
||||||
|
///
|
||||||
|
/// This consumes `self` since the Client is not much use without
|
||||||
|
/// an underlying transport.
|
||||||
|
pub(crate) fn into_inner(self) -> Result<T> {
|
||||||
|
let res = self.conn.stream.into_inner()?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
/// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
|
/// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
|
||||||
/// returned; on error the original `Client` instance is returned in addition to the error.
|
/// returned; on error the original `Client` instance is returned in addition to the error.
|
||||||
/// This is because `login` takes ownership of `self`, so in order to try again (e.g. after
|
/// This is because `login` takes ownership of `self`, so in order to try again (e.g. after
|
||||||
|
|
@ -421,16 +325,10 @@ impl<T: Read + Write> Client<T> {
|
||||||
/// transferred back to the caller.
|
/// transferred back to the caller.
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # extern crate imap;
|
/// # {} #[cfg(feature = "tls")]
|
||||||
/// # extern crate native_tls;
|
|
||||||
/// # use std::io;
|
|
||||||
/// # use native_tls::TlsConnector;
|
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// # let tls_connector = TlsConnector::builder().build().unwrap();
|
/// let client = imap::ClientBuilder::new("imap.example.org", 993)
|
||||||
/// let client = imap::connect(
|
/// .native_tls().unwrap();
|
||||||
/// ("imap.example.org", 993),
|
|
||||||
/// "imap.example.org",
|
|
||||||
/// &tls_connector).unwrap();
|
|
||||||
///
|
///
|
||||||
/// match client.login("user", "pass") {
|
/// match client.login("user", "pass") {
|
||||||
/// Ok(s) => {
|
/// Ok(s) => {
|
||||||
|
|
@ -463,10 +361,6 @@ impl<T: Read + Write> Client<T> {
|
||||||
/// challenge.
|
/// challenge.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// extern crate imap;
|
|
||||||
/// extern crate native_tls;
|
|
||||||
/// use native_tls::TlsConnector;
|
|
||||||
///
|
|
||||||
/// struct OAuth2 {
|
/// struct OAuth2 {
|
||||||
/// user: String,
|
/// user: String,
|
||||||
/// access_token: String,
|
/// access_token: String,
|
||||||
|
|
@ -482,14 +376,15 @@ impl<T: Read + Write> Client<T> {
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
/// # {} #[cfg(feature = "tls")]
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let auth = OAuth2 {
|
/// let auth = OAuth2 {
|
||||||
/// user: String::from("me@example.com"),
|
/// user: String::from("me@example.com"),
|
||||||
/// access_token: String::from("<access_token>"),
|
/// access_token: String::from("<access_token>"),
|
||||||
/// };
|
/// };
|
||||||
/// let domain = "imap.example.com";
|
/// let client = imap::ClientBuilder::new("imap.example.com", 993).native_tls()
|
||||||
/// let tls = TlsConnector::builder().build().unwrap();
|
/// .expect("Could not connect to server");
|
||||||
/// let client = imap::connect((domain, 993), domain, &tls).unwrap();
|
///
|
||||||
/// match client.authenticate("XOAUTH2", &auth) {
|
/// match client.authenticate("XOAUTH2", &auth) {
|
||||||
/// Ok(session) => {
|
/// Ok(session) => {
|
||||||
/// // you are successfully authenticated!
|
/// // you are successfully authenticated!
|
||||||
|
|
@ -1379,7 +1274,7 @@ impl<T: Read + Write> Connection<T> {
|
||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_command_and_check_ok(&mut self, command: &str) -> Result<()> {
|
pub(crate) fn run_command_and_check_ok(&mut self, command: &str) -> Result<()> {
|
||||||
self.run_command_and_read_response(command).map(|_| ())
|
self.run_command_and_read_response(command).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
144
src/client_builder.rs
Normal file
144
src/client_builder.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
use crate::{Client, Result};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
use native_tls::{TlsConnector, TlsStream};
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
use rustls_connector::{RustlsConnector, TlsStream as RustlsStream};
|
||||||
|
|
||||||
|
/// A convenience builder for [`Client`] structs over various encrypted transports.
|
||||||
|
///
|
||||||
|
/// Creating a [`Client`] using `native-tls` transport is straightforward:
|
||||||
|
/// ```no_run
|
||||||
|
/// # use imap::ClientBuilder;
|
||||||
|
/// # {} #[cfg(feature = "tls")]
|
||||||
|
/// # fn main() -> Result<(), imap::Error> {
|
||||||
|
/// let client = ClientBuilder::new("imap.example.com", 993).native_tls()?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Similarly, if using the `rustls-tls` feature you can create a [`Client`] using rustls:
|
||||||
|
/// ```no_run
|
||||||
|
/// # use imap::ClientBuilder;
|
||||||
|
/// # {} #[cfg(feature = "rustls-tls")]
|
||||||
|
/// # fn main() -> Result<(), imap::Error> {
|
||||||
|
/// let client = ClientBuilder::new("imap.example.com", 993).rustls()?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// To use `STARTTLS`, just call `starttls()` before one of the [`Client`]-yielding
|
||||||
|
/// functions:
|
||||||
|
/// ```no_run
|
||||||
|
/// # use imap::ClientBuilder;
|
||||||
|
/// # {} #[cfg(feature = "rustls-tls")]
|
||||||
|
/// # fn main() -> Result<(), imap::Error> {
|
||||||
|
/// let client = ClientBuilder::new("imap.example.com", 993)
|
||||||
|
/// .starttls()
|
||||||
|
/// .rustls()?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
/// The returned [`Client`] is unauthenticated; to access session-related methods (through
|
||||||
|
/// [`Session`](crate::Session)), use [`Client::login`] or [`Client::authenticate`].
|
||||||
|
pub struct ClientBuilder<D>
|
||||||
|
where
|
||||||
|
D: AsRef<str>,
|
||||||
|
{
|
||||||
|
domain: D,
|
||||||
|
port: u16,
|
||||||
|
starttls: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> ClientBuilder<D>
|
||||||
|
where
|
||||||
|
D: AsRef<str>,
|
||||||
|
{
|
||||||
|
/// Make a new `ClientBuilder` using the given domain and port.
|
||||||
|
pub fn new(domain: D, port: u16) -> Self {
|
||||||
|
ClientBuilder {
|
||||||
|
domain,
|
||||||
|
port,
|
||||||
|
starttls: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use [`STARTTLS`](https://tools.ietf.org/html/rfc2595) for this connection.
|
||||||
|
#[cfg(any(feature = "tls", feature = "rustls-tls"))]
|
||||||
|
pub fn starttls(&mut self) -> &mut Self {
|
||||||
|
self.starttls = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a new [`Client`] using a `native-tls` transport.
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
|
||||||
|
pub fn native_tls(&mut self) -> Result<Client<TlsStream<TcpStream>>> {
|
||||||
|
self.connect(|domain, tcp| {
|
||||||
|
let ssl_conn = TlsConnector::builder().build()?;
|
||||||
|
Ok(TlsConnector::connect(&ssl_conn, domain, tcp)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a new [`Client`] using `rustls` transport.
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
|
||||||
|
pub fn rustls(&mut self) -> Result<Client<RustlsStream<TcpStream>>> {
|
||||||
|
self.connect(|domain, tcp| {
|
||||||
|
let ssl_conn = RustlsConnector::new_with_native_certs()?;
|
||||||
|
Ok(ssl_conn.connect(domain, tcp)?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a [`Client`] using a custom TLS initialization. This function is intended
|
||||||
|
/// to be used if your TLS setup requires custom work such as adding private CAs
|
||||||
|
/// or other specific TLS parameters.
|
||||||
|
///
|
||||||
|
/// The `handshake` argument should accept two parameters:
|
||||||
|
///
|
||||||
|
/// - domain: [`&str`]
|
||||||
|
/// - tcp: [`TcpStream`]
|
||||||
|
///
|
||||||
|
/// and yield a `Result<C>` where `C` is `Read + Write`. It should only perform
|
||||||
|
/// TLS initialization over the given `tcp` socket and return the encrypted stream
|
||||||
|
/// object, such as a [`native_tls::TlsStream`] or a [`rustls_connector::TlsStream`].
|
||||||
|
///
|
||||||
|
/// If the caller is using `STARTTLS` and previously called [`starttls`](Self::starttls)
|
||||||
|
/// then the `tcp` socket given to the `handshake` function will be connected and will
|
||||||
|
/// have initiated the `STARTTLS` handshake.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use imap::ClientBuilder;
|
||||||
|
/// # use rustls_connector::RustlsConnector;
|
||||||
|
/// # {} #[cfg(feature = "rustls-tls")]
|
||||||
|
/// # fn main() -> Result<(), imap::Error> {
|
||||||
|
/// let client = ClientBuilder::new("imap.example.com", 993)
|
||||||
|
/// .starttls()
|
||||||
|
/// .connect(|domain, tcp| {
|
||||||
|
/// let ssl_conn = RustlsConnector::new_with_native_certs()?;
|
||||||
|
/// Ok(ssl_conn.connect(domain, tcp)?)
|
||||||
|
/// })?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn connect<F, C>(&mut self, handshake: F) -> Result<Client<C>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&str, TcpStream) -> Result<C>,
|
||||||
|
C: Read + Write,
|
||||||
|
{
|
||||||
|
let tcp = if self.starttls {
|
||||||
|
let tcp = TcpStream::connect((self.domain.as_ref(), self.port))?;
|
||||||
|
let mut client = Client::new(tcp);
|
||||||
|
client.read_greeting()?;
|
||||||
|
client.run_command_and_check_ok("STARTTLS")?;
|
||||||
|
client.into_inner()?
|
||||||
|
} else {
|
||||||
|
TcpStream::connect((self.domain.as_ref(), self.port))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let tls = handshake(self.domain.as_ref(), tcp)?;
|
||||||
|
Ok(Client::new(tls))
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/error.rs
19
src/error.rs
|
|
@ -3,7 +3,6 @@
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
|
@ -15,6 +14,8 @@ use imap_proto::{types::ResponseCode, Response};
|
||||||
use native_tls::Error as TlsError;
|
use native_tls::Error as TlsError;
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use native_tls::HandshakeError as TlsHandshakeError;
|
use native_tls::HandshakeError as TlsHandshakeError;
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
use rustls_connector::HandshakeError as RustlsHandshakeError;
|
||||||
|
|
||||||
/// A convenience wrapper around `Result` for `imap::Error`.
|
/// A convenience wrapper around `Result` for `imap::Error`.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
@ -57,6 +58,9 @@ impl fmt::Display for No {
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
|
/// An error from the `rustls` library during the TLS handshake.
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
RustlsHandshake(RustlsHandshakeError<TcpStream>),
|
||||||
/// An error from the `native_tls` library during the TLS handshake.
|
/// An error from the `native_tls` library during the TLS handshake.
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
TlsHandshake(TlsHandshakeError<TcpStream>),
|
TlsHandshake(TlsHandshakeError<TcpStream>),
|
||||||
|
|
@ -100,6 +104,13 @@ impl<T> From<BufError<T>> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
impl From<RustlsHandshakeError<TcpStream>> for Error {
|
||||||
|
fn from(err: RustlsHandshakeError<TcpStream>) -> Error {
|
||||||
|
Error::RustlsHandshake(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
impl From<TlsHandshakeError<TcpStream>> for Error {
|
impl From<TlsHandshakeError<TcpStream>> for Error {
|
||||||
fn from(err: TlsHandshakeError<TcpStream>) -> Error {
|
fn from(err: TlsHandshakeError<TcpStream>) -> Error {
|
||||||
|
|
@ -124,6 +135,8 @@ impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
Error::Io(ref e) => fmt::Display::fmt(e, f),
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
Error::RustlsHandshake(ref e) => fmt::Display::fmt(e, f),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
Error::Tls(ref e) => fmt::Display::fmt(e, f),
|
Error::Tls(ref e) => fmt::Display::fmt(e, f),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
|
|
@ -144,6 +157,8 @@ impl StdError for Error {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => e.description(),
|
Error::Io(ref e) => e.description(),
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
Error::RustlsHandshake(ref e) => e.description(),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
Error::Tls(ref e) => e.description(),
|
Error::Tls(ref e) => e.description(),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
|
|
@ -161,6 +176,8 @@ impl StdError for Error {
|
||||||
fn cause(&self) -> Option<&dyn StdError> {
|
fn cause(&self) -> Option<&dyn StdError> {
|
||||||
match *self {
|
match *self {
|
||||||
Error::Io(ref e) => Some(e),
|
Error::Io(ref e) => Some(e),
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
Error::RustlsHandshake(ref e) => Some(e),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
Error::Tls(ref e) => Some(e),
|
Error::Tls(ref e) => Some(e),
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use crate::parse::parse_idle;
|
||||||
use crate::types::UnsolicitedResponse;
|
use crate::types::UnsolicitedResponse;
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
use native_tls::TlsStream;
|
use native_tls::TlsStream;
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
use rustls_connector::TlsStream as RustlsStream;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -25,10 +27,10 @@ use std::time::Duration;
|
||||||
/// a convenience callback function [`stop_on_any`] is provided.
|
/// a convenience callback function [`stop_on_any`] is provided.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # use native_tls::TlsConnector;
|
|
||||||
/// use imap::extensions::idle;
|
/// use imap::extensions::idle;
|
||||||
/// let ssl_conn = TlsConnector::builder().build().unwrap();
|
/// # #[cfg(feature = "tls")]
|
||||||
/// let client = imap::connect(("example.com", 993), "example.com", &ssl_conn)
|
/// # {
|
||||||
|
/// let client = imap::ClientBuilder::new("example.com", 993).native_tls()
|
||||||
/// .expect("Could not connect to imap server");
|
/// .expect("Could not connect to imap server");
|
||||||
/// let mut imap = client.login("user@example.com", "password")
|
/// let mut imap = client.login("user@example.com", "password")
|
||||||
/// .expect("Could not authenticate");
|
/// .expect("Could not authenticate");
|
||||||
|
|
@ -39,6 +41,7 @@ use std::time::Duration;
|
||||||
///
|
///
|
||||||
/// // Exit on any mailbox change
|
/// // Exit on any mailbox change
|
||||||
/// let result = idle.wait_keepalive_while(idle::stop_on_any);
|
/// let result = idle.wait_keepalive_while(idle::stop_on_any);
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
|
/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
|
||||||
|
|
@ -284,3 +287,10 @@ impl<'a> SetReadTimeout for TlsStream<TcpStream> {
|
||||||
self.get_ref().set_read_timeout(timeout).map_err(Error::Io)
|
self.get_ref().set_read_timeout(timeout).map_err(Error::Io)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
impl<'a> SetReadTimeout for RustlsStream<TcpStream> {
|
||||||
|
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()> {
|
||||||
|
self.get_ref().set_read_timeout(timeout).map_err(Error::Io)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -9,7 +9,7 @@
|
||||||
//!
|
//!
|
||||||
//! [@jonhoo]: https://thesquareplanet.com/
|
//! [@jonhoo]: https://thesquareplanet.com/
|
||||||
//!
|
//!
|
||||||
//! To connect, use the [`connect`] function. This gives you an unauthenticated [`Client`]. You can
|
//! To connect, use the [`ClientBuilder`]. This gives you an unauthenticated [`Client`]. You can
|
||||||
//! then use [`Client::login`] or [`Client::authenticate`] to perform username/password or
|
//! then use [`Client::login`] or [`Client::authenticate`] to perform username/password or
|
||||||
//! challenge/response authentication respectively. This in turn gives you an authenticated
|
//! challenge/response authentication respectively. This in turn gives you an authenticated
|
||||||
//! [`Session`], which lets you access the mailboxes at the server.
|
//! [`Session`], which lets you access the mailboxes at the server.
|
||||||
|
|
@ -21,16 +21,10 @@
|
||||||
//! Below is a basic client example. See the `examples/` directory for more.
|
//! Below is a basic client example. See the `examples/` directory for more.
|
||||||
//!
|
//!
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! extern crate imap;
|
//! # #[cfg(feature = "tls")]
|
||||||
//! extern crate native_tls;
|
|
||||||
//!
|
|
||||||
//! fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
//! fn fetch_inbox_top() -> imap::error::Result<Option<String>> {
|
||||||
//! let domain = "imap.example.com";
|
|
||||||
//! let tls = native_tls::TlsConnector::builder().build().unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! // we pass in the domain twice to check that the server's TLS
|
//! let client = imap::ClientBuilder::new("imap.example.com", 993).native_tls()?;
|
||||||
//! // certificate is valid for the domain we're connecting to.
|
|
||||||
//! let client = imap::connect((domain, 993), domain, &tls).unwrap();
|
|
||||||
//!
|
//!
|
||||||
//! // the client we have here is unauthenticated.
|
//! // the client we have here is unauthenticated.
|
||||||
//! // to do anything useful with the e-mails, we need to log in
|
//! // to do anything useful with the e-mails, we need to log in
|
||||||
|
|
@ -77,7 +71,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Even without `native_tls`, you can still use TLS by leveraging the pure Rust `rustls`
|
//! Even without `native_tls`, you can still use TLS by leveraging the pure Rust `rustls`
|
||||||
//! crate. See the example/rustls.rs file for a working example.
|
//! crate, which is enabled with the `rustls-tls` feature. See the example/rustls.rs file
|
||||||
|
//! for a working example.
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
|
|
@ -90,6 +85,8 @@ pub use crate::authenticator::Authenticator;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
pub use crate::client::*;
|
pub use crate::client::*;
|
||||||
|
mod client_builder;
|
||||||
|
pub use crate::client_builder::ClientBuilder;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,10 @@ use std::ops::RangeInclusive;
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # let domain = "imap.example.com";
|
/// # {} #[cfg(feature = "tls")]
|
||||||
/// # let tls = native_tls::TlsConnector::builder().build().unwrap();
|
/// # fn main() {
|
||||||
/// # let client = imap::connect((domain, 993), domain, &tls).unwrap();
|
/// # let client = imap::ClientBuilder::new("imap.example.com", 993)
|
||||||
|
/// .native_tls().unwrap();
|
||||||
/// # let mut session = client.login("name", "pw").unwrap();
|
/// # let mut session = client.login("name", "pw").unwrap();
|
||||||
/// // Iterate over whatever is returned
|
/// // Iterate over whatever is returned
|
||||||
/// if let Ok(deleted) = session.expunge() {
|
/// if let Ok(deleted) = session.expunge() {
|
||||||
|
|
@ -35,6 +36,7 @@ use std::ops::RangeInclusive;
|
||||||
/// // Do something with uid
|
/// // Do something with uid
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Deleted {
|
pub enum Deleted {
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,15 @@ fn tls() -> native_tls::TlsConnector {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn session(user: &str) -> imap::Session<native_tls::TlsStream<TcpStream>> {
|
fn session(user: &str) -> imap::Session<native_tls::TlsStream<TcpStream>> {
|
||||||
let mut s = imap::connect(
|
let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string());
|
||||||
&format!(
|
let mut s = imap::ClientBuilder::new(&host, 3993)
|
||||||
"{}:3993",
|
.connect(|domain, tcp| {
|
||||||
std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
|
let ssl_conn = tls();
|
||||||
),
|
Ok(native_tls::TlsConnector::connect(&ssl_conn, domain, tcp).unwrap())
|
||||||
"imap.example.com",
|
})
|
||||||
&tls(),
|
.unwrap()
|
||||||
)
|
.login(user, user)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.login(user, user)
|
|
||||||
.unwrap();
|
|
||||||
s.debug = true;
|
s.debug = true;
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
@ -55,25 +53,25 @@ fn smtp(user: &str) -> lettre::SmtpTransport {
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn connect_insecure_then_secure() {
|
fn connect_insecure_then_secure() {
|
||||||
let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string());
|
let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string());
|
||||||
let stream = TcpStream::connect((host.as_ref(), 3143)).unwrap();
|
|
||||||
|
|
||||||
// ignored because of https://github.com/greenmail-mail-test/greenmail/issues/135
|
// ignored because of https://github.com/greenmail-mail-test/greenmail/issues/135
|
||||||
imap::Client::new(stream)
|
imap::ClientBuilder::new(&host, 3143)
|
||||||
.secure("imap.example.com", &tls())
|
.starttls()
|
||||||
|
.connect(|domain, tcp| {
|
||||||
|
let ssl_conn = tls();
|
||||||
|
Ok(native_tls::TlsConnector::connect(&ssl_conn, domain, tcp).unwrap())
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn connect_secure() {
|
fn connect_secure() {
|
||||||
imap::connect(
|
let host = std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string());
|
||||||
&format!(
|
imap::ClientBuilder::new(&host, 3993)
|
||||||
"{}:3993",
|
.connect(|domain, tcp| {
|
||||||
std::env::var("TEST_HOST").unwrap_or("127.0.0.1".to_string())
|
let ssl_conn = tls();
|
||||||
),
|
Ok(native_tls::TlsConnector::connect(&ssl_conn, domain, tcp).unwrap())
|
||||||
"imap.example.com",
|
})
|
||||||
&tls(),
|
.unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue