Add support for alternate TLs implementations.
This commit is contained in:
committed by
Martin Algesten
parent
1c1dfaa691
commit
56276c3742
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -6,7 +6,7 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: doc
|
command: doc
|
||||||
# Keep in sync with Cargo.toml's [package.metadata.docs.rs]
|
# Keep in sync with Cargo.toml's [package.metadata.docs.rs]
|
||||||
args: --no-default-features --no-deps --features "tls json charset cookies socks-proxy"
|
args: --no-default-features --no-deps --features "tls native-tls json charset cookies socks-proxy"
|
||||||
build_and_test:
|
build_and_test:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -47,7 +47,8 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
tls:
|
tls:
|
||||||
- ""
|
- ""
|
||||||
- tls
|
- "tls"
|
||||||
|
- "native-tls"
|
||||||
feature:
|
feature:
|
||||||
- ""
|
- ""
|
||||||
- json
|
- json
|
||||||
@@ -69,4 +70,4 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --no-default-features --features "${{ matrix.tls }} ${{ matrix.feature }}"
|
args: --no-default-features --features "${{ matrix.tls }}" "${{ matrix.feature }}"
|
||||||
|
|||||||
17
Cargo.toml
17
Cargo.toml
@@ -12,14 +12,14 @@ edition = "2018"
|
|||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# Keep in sync with .github/workflows/test.yml
|
# Keep in sync with .github/workflows/test.yml
|
||||||
features = [ "tls", "json", "charset", "cookies", "socks-proxy" ]
|
features = ["tls", "native-tls", "json", "charset", "cookies", "socks-proxy"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tls"]
|
default = ["tls"]
|
||||||
|
tls = ["webpki", "webpki-roots", "rustls"]
|
||||||
|
native-certs = ["rustls-native-certs"]
|
||||||
json = ["serde", "serde_json"]
|
json = ["serde", "serde_json"]
|
||||||
charset = ["encoding_rs"]
|
charset = ["encoding_rs"]
|
||||||
tls = ["rustls", "webpki", "webpki-roots"]
|
|
||||||
native-certs = ["rustls-native-certs"]
|
|
||||||
cookies = ["cookie", "cookie_store"]
|
cookies = ["cookie", "cookie_store"]
|
||||||
socks-proxy = ["socks"]
|
socks-proxy = ["socks"]
|
||||||
|
|
||||||
@@ -30,15 +30,16 @@ cookie = { version = "0.15", default-features = false, optional = true}
|
|||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
url = "2"
|
url = "2"
|
||||||
socks = { version = "0.3", optional = true }
|
socks = { version = "0.3", optional = true }
|
||||||
rustls = { version = "0.20.1", optional = true }
|
|
||||||
webpki = { version = "0.22", optional = true }
|
|
||||||
webpki-roots = { version = "0.22", optional = true }
|
|
||||||
rustls-native-certs = { version = "0.6", optional = true }
|
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
serde_json = { version = "1", optional = true }
|
serde_json = { version = "1", optional = true }
|
||||||
encoding_rs = { version = "0.8", optional = true }
|
encoding_rs = { version = "0.8", optional = true }
|
||||||
cookie_store = { version = "0.15", optional = true, default-features = false, features = ["preserve_order"] }
|
cookie_store = { version = "0.15", optional = true, default-features = false, features = ["preserve_order"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
webpki = { version = "0.22", optional = true }
|
||||||
|
webpki-roots = { version = "0.22", optional = true }
|
||||||
|
rustls = { version = "0.20", optional = true }
|
||||||
|
rustls-native-certs = { version = "0.6", optional = true }
|
||||||
|
native-tls = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@@ -51,4 +52,4 @@ name = "smoke-test"
|
|||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "cureq"
|
name = "cureq"
|
||||||
required-features = ["charset", "cookies", "socks-proxy"]
|
required-features = ["charset", "cookies", "socks-proxy", "native-tls"]
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -12,7 +12,7 @@ HTTPS, and charset decoding.
|
|||||||
Ureq is in pure Rust for safety and ease of understanding. It avoids using
|
Ureq is in pure Rust for safety and ease of understanding. It avoids using
|
||||||
`unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps
|
`unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps
|
||||||
the API simple and and keeps dependencies to a minimum. For TLS, ureq uses
|
the API simple and and keeps dependencies to a minimum. For TLS, ureq uses
|
||||||
[rustls].
|
[rustls or native-tls](#tls).
|
||||||
|
|
||||||
Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.
|
Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.
|
||||||
|
|
||||||
@@ -101,12 +101,18 @@ You can control them when including ureq as a dependency.
|
|||||||
`ureq = { version = "*", features = ["json", "charset"] }`
|
`ureq = { version = "*", features = ["json", "charset"] }`
|
||||||
|
|
||||||
* `tls` enables https. This is enabled by default.
|
* `tls` enables https. This is enabled by default.
|
||||||
|
* `native-certs` makes the default TLS implementation use the OS' trust store (see TLS doc below).
|
||||||
* `cookies` enables cookies.
|
* `cookies` enables cookies.
|
||||||
* `json` enables [Response::into_json()] and [Request::send_json()] via serde_json.
|
* `json` enables [Response::into_json()] and [Request::send_json()] via serde_json.
|
||||||
* `charset` enables interpreting the charset part of the Content-Type header
|
* `charset` enables interpreting the charset part of the Content-Type header
|
||||||
(e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
|
(e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
|
||||||
library defaults to Rust's built in `utf-8`.
|
library defaults to Rust's built in `utf-8`.
|
||||||
* `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix.
|
* `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix.
|
||||||
|
* `native-tls` enables an adapter so you can pass a `native_tls::TlsConnector` instance
|
||||||
|
to `AgentBuilder::tls_connector`. Due to the risk of diamond dependencies accidentally switching on an unwanted
|
||||||
|
TLS implementation, `native-tls` is never picked up as a default or used by the crate level
|
||||||
|
convenience calls (`ureq::get` etc) – it must be configured on the agent. The `native-certs` feature
|
||||||
|
does nothing for `native-tls`.
|
||||||
|
|
||||||
## Plain requests
|
## Plain requests
|
||||||
|
|
||||||
@@ -211,6 +217,40 @@ fn proxy_example_2() -> std::result::Result<(), ureq::Error> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## HTTPS / TLS / SSL
|
||||||
|
|
||||||
|
On platforms that support rustls, ureq uses rustls. On other platforms, native-tls can
|
||||||
|
be manually configured using [`AgentBuilder::tls_connector`].
|
||||||
|
|
||||||
|
You might want to use native-tls if you need to interoperate with servers that
|
||||||
|
only support less-secure TLS configurations (rustls doesn't support TLS 1.0 and 1.1, for
|
||||||
|
instance). You might also want to use it if you need to validate certificates for IP addresses,
|
||||||
|
which are not currently supported in rustls.
|
||||||
|
|
||||||
|
Here's an example of constructing an Agent that uses native-tls. It requires the
|
||||||
|
"native-tls" feature to be enabled.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ureq::Agent;
|
||||||
|
|
||||||
|
let agent = ureq::AgentBuilder::new()
|
||||||
|
.tls_connector(Arc::new(native_tls::TlsConnector::new().unwrap()))
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trusted Roots
|
||||||
|
|
||||||
|
When you use rustls (`tls` feature), ureq defaults to trusting
|
||||||
|
[webpki-roots](https://docs.rs/webpki-roots/), a
|
||||||
|
copy of the Mozilla Root program that is bundled into your program (and so won't update if your
|
||||||
|
program isn't updated). You can alternately configure
|
||||||
|
[rustls-native-certs](https://docs.rs/rustls-native-certs/) which extracts the roots from your
|
||||||
|
OS' trust store. That means it will update when your OS is updated, and also that it will
|
||||||
|
include locally installed roots.
|
||||||
|
|
||||||
|
When you use `native-tls`, ureq will use your OS' certificate verifier and root store.
|
||||||
|
|
||||||
## Blocking I/O for simplicity
|
## Blocking I/O for simplicity
|
||||||
|
|
||||||
Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O
|
Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ use std::fmt;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::time::SystemTime;
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
|
|
||||||
use rustls::{
|
use rustls::client::ServerCertVerified;
|
||||||
Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError,
|
use rustls::client::ServerCertVerifier;
|
||||||
};
|
use rustls::ServerName;
|
||||||
|
use rustls::{Certificate, ClientConfig};
|
||||||
use ureq;
|
use ureq;
|
||||||
use webpki::DNSNameRef;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct StringError(String);
|
struct StringError(String);
|
||||||
@@ -100,11 +101,13 @@ struct AcceptAll {}
|
|||||||
impl ServerCertVerifier for AcceptAll {
|
impl ServerCertVerifier for AcceptAll {
|
||||||
fn verify_server_cert(
|
fn verify_server_cert(
|
||||||
&self,
|
&self,
|
||||||
_roots: &RootCertStore,
|
_end_entity: &Certificate,
|
||||||
_presented_certs: &[Certificate],
|
_intermediates: &[Certificate],
|
||||||
_dns_name: DNSNameRef<'_>,
|
_server_name: &ServerName,
|
||||||
|
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||||
_ocsp_response: &[u8],
|
_ocsp_response: &[u8],
|
||||||
) -> Result<ServerCertVerified, TLSError> {
|
_now: SystemTime,
|
||||||
|
) -> Result<ServerCertVerified, rustls::Error> {
|
||||||
Ok(ServerCertVerified::assertion())
|
Ok(ServerCertVerified::assertion())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +135,7 @@ fn main2() -> Result<(), Error> {
|
|||||||
-k Ignore certificate errors
|
-k Ignore certificate errors
|
||||||
-m <time> Max time for the entire request
|
-m <time> Max time for the entire request
|
||||||
-ct <time> Connection timeout
|
-ct <time> Connection timeout
|
||||||
|
--native-tls Use native-tls
|
||||||
|
|
||||||
Fetch url and copy it to stdout.
|
Fetch url and copy it to stdout.
|
||||||
"##,
|
"##,
|
||||||
@@ -160,12 +164,15 @@ Fetch url and copy it to stdout.
|
|||||||
wait = Duration::from_secs(wait_seconds);
|
wait = Duration::from_secs(wait_seconds);
|
||||||
}
|
}
|
||||||
"-k" => {
|
"-k" => {
|
||||||
let mut client_config = ClientConfig::new();
|
let client_config = ClientConfig::builder()
|
||||||
client_config
|
.with_safe_defaults()
|
||||||
.dangerous()
|
.with_custom_certificate_verifier(Arc::new(AcceptAll {}))
|
||||||
.set_certificate_verifier(Arc::new(AcceptAll {}));
|
.with_no_client_auth();
|
||||||
builder = builder.tls_config(Arc::new(client_config));
|
builder = builder.tls_config(Arc::new(client_config));
|
||||||
}
|
}
|
||||||
|
"--native-tls" => {
|
||||||
|
builder = builder.tls_connector(Arc::new(native_tls::TlsConnector::new().unwrap()));
|
||||||
|
}
|
||||||
"-m" => {
|
"-m" => {
|
||||||
let t: f32 = args
|
let t: f32 = args
|
||||||
.next()
|
.next()
|
||||||
|
|||||||
60
src/agent.rs
60
src/agent.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -6,6 +7,7 @@ use crate::pool::ConnectionPool;
|
|||||||
use crate::proxy::Proxy;
|
use crate::proxy::Proxy;
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::resolve::{ArcResolver, StdResolver};
|
use crate::resolve::{ArcResolver, StdResolver};
|
||||||
|
use crate::stream::TlsConnector;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
@@ -28,7 +30,7 @@ pub struct AgentBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Config as built by AgentBuilder and then static for the lifetime of the Agent.
|
/// Config as built by AgentBuilder and then static for the lifetime of the Agent.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct AgentConfig {
|
pub(crate) struct AgentConfig {
|
||||||
pub proxy: Option<Proxy>,
|
pub proxy: Option<Proxy>,
|
||||||
pub timeout_connect: Option<Duration>,
|
pub timeout_connect: Option<Duration>,
|
||||||
@@ -37,8 +39,13 @@ pub(crate) struct AgentConfig {
|
|||||||
pub timeout: Option<Duration>,
|
pub timeout: Option<Duration>,
|
||||||
pub redirects: u32,
|
pub redirects: u32,
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
#[cfg(feature = "tls")]
|
pub tls_config: Arc<dyn TlsConnector>,
|
||||||
pub tls_config: Option<TLSClientConfig>,
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for AgentConfig {
|
||||||
|
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Agents keep state between requests.
|
/// Agents keep state between requests.
|
||||||
@@ -215,8 +222,7 @@ impl AgentBuilder {
|
|||||||
timeout: None,
|
timeout: None,
|
||||||
redirects: 5,
|
redirects: 5,
|
||||||
user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")),
|
user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")),
|
||||||
#[cfg(feature = "tls")]
|
tls_config: crate::default_tls_config(),
|
||||||
tls_config: None,
|
|
||||||
},
|
},
|
||||||
max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
|
max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
|
||||||
max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
|
max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
|
||||||
@@ -472,9 +478,10 @@ impl AgentBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the TLS client config to use for the connection. See [`ClientConfig`](https://docs.rs/rustls/latest/rustls/struct.ClientConfig.html).
|
/// Configure TLS options for rustls to use when making HTTPS connections from this Agent.
|
||||||
|
///
|
||||||
|
/// This overrides any previous call to tls_config or tls_connector.
|
||||||
///
|
///
|
||||||
/// Example:
|
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), ureq::Error> {
|
/// # fn main() -> Result<(), ureq::Error> {
|
||||||
/// # ureq::is_test(true);
|
/// # ureq::is_test(true);
|
||||||
@@ -497,10 +504,34 @@ impl AgentBuilder {
|
|||||||
/// .build();
|
/// .build();
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
|
pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
|
||||||
self.config.tls_config = Some(TLSClientConfig(tls_config));
|
self.config.tls_config = Arc::new(tls_config);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure TLS options for a backend other than rustls. The parameter can be a
|
||||||
|
/// any type which implements the [HttpsConnector] trait. If you enable the native-tls
|
||||||
|
/// feature, we provide `impl HttpsConnector for native_tls::TlsConnector` so you can pass
|
||||||
|
/// [`Arc<native_tls::TlsConnector>`](https://docs.rs/native-tls/0.2.7/native_tls/struct.TlsConnector.html).
|
||||||
|
///
|
||||||
|
/// This overrides any previous call to tls_config or tls_connector.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), ureq::Error> {
|
||||||
|
/// # ureq::is_test(true);
|
||||||
|
/// use std::sync::Arc;
|
||||||
|
/// # #[cfg(feature = "native-tls")]
|
||||||
|
/// let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap());
|
||||||
|
/// # #[cfg(feature = "native-tls")]
|
||||||
|
/// let agent = ureq::builder()
|
||||||
|
/// .tls_connector(tls_connector.clone())
|
||||||
|
/// .build();
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn tls_connector<T: TlsConnector + 'static>(mut self, tls_config: Arc<T>) -> Self {
|
||||||
|
self.config.tls_config = tls_config;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,17 +568,6 @@ impl AgentBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) struct TLSClientConfig(pub(crate) Arc<rustls::ClientConfig>);
|
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
impl std::fmt::Debug for TLSClientConfig {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("TLSClientConfig").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ impl fmt::Display for HeaderLine {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
/// Wrapper type for a header field.
|
/// Wrapper type for a header field.
|
||||||
/// https://tools.ietf.org/html/rfc7230#section-3.2
|
/// <https://tools.ietf.org/html/rfc7230#section-3.2>
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
// Line contains the unmodified bytes of single header field.
|
// Line contains the unmodified bytes of single header field.
|
||||||
// It does not contain the final CRLF.
|
// It does not contain the final CRLF.
|
||||||
|
|||||||
110
src/lib.rs
110
src/lib.rs
@@ -16,8 +16,8 @@
|
|||||||
//!
|
//!
|
||||||
//! Ureq is in pure Rust for safety and ease of understanding. It avoids using
|
//! Ureq is in pure Rust for safety and ease of understanding. It avoids using
|
||||||
//! `unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps
|
//! `unsafe` directly. It [uses blocking I/O][blocking] instead of async I/O, because that keeps
|
||||||
//! the API simple and keeps dependencies to a minimum. For TLS, ureq uses
|
//! the API simple and and keeps dependencies to a minimum. For TLS, ureq uses
|
||||||
//! [rustls].
|
//! [rustls or native-tls](#tls).
|
||||||
//!
|
//!
|
||||||
//! Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.
|
//! Version 2.0.0 was released recently and changed some APIs. See the [changelog] for details.
|
||||||
//!
|
//!
|
||||||
@@ -121,12 +121,18 @@
|
|||||||
//! `ureq = { version = "*", features = ["json", "charset"] }`
|
//! `ureq = { version = "*", features = ["json", "charset"] }`
|
||||||
//!
|
//!
|
||||||
//! * `tls` enables https. This is enabled by default.
|
//! * `tls` enables https. This is enabled by default.
|
||||||
|
//! * `native-certs` makes the default TLS implementation use the OS' trust store (see TLS doc below).
|
||||||
//! * `cookies` enables cookies.
|
//! * `cookies` enables cookies.
|
||||||
//! * `json` enables [Response::into_json()] and [Request::send_json()] via serde_json.
|
//! * `json` enables [Response::into_json()] and [Request::send_json()] via serde_json.
|
||||||
//! * `charset` enables interpreting the charset part of the Content-Type header
|
//! * `charset` enables interpreting the charset part of the Content-Type header
|
||||||
//! (e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
|
//! (e.g. `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
|
||||||
//! library defaults to Rust's built in `utf-8`.
|
//! library defaults to Rust's built in `utf-8`.
|
||||||
//! * `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix.
|
//! * `socks-proxy` enables proxy config using the `socks4://`, `socks4a://`, `socks5://` and `socks://` (equal to `socks5://`) prefix.
|
||||||
|
//! * `native-tls` enables an adapter so you can pass a `native_tls::TlsConnector` instance
|
||||||
|
//! to `AgentBuilder::tls_connector`. Due to the risk of diamond dependencies accidentally switching on an unwanted
|
||||||
|
//! TLS implementation, `native-tls` is never picked up as a default or used by the crate level
|
||||||
|
//! convenience calls (`ureq::get` etc) – it must be configured on the agent. The `native-certs` feature
|
||||||
|
//! does nothing for `native-tls`.
|
||||||
//!
|
//!
|
||||||
//! # Plain requests
|
//! # Plain requests
|
||||||
//!
|
//!
|
||||||
@@ -234,6 +240,46 @@
|
|||||||
//! # fn main() {}
|
//! # fn main() {}
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! # HTTPS / TLS / SSL
|
||||||
|
//!
|
||||||
|
//! On platforms that support rustls, ureq uses rustls. On other platforms, native-tls can
|
||||||
|
//! be manually configured using [`AgentBuilder::tls_connector`].
|
||||||
|
//!
|
||||||
|
//! You might want to use native-tls if you need to interoperate with servers that
|
||||||
|
//! only support less-secure TLS configurations (rustls doesn't support TLS 1.0 and 1.1, for
|
||||||
|
//! instance). You might also want to use it if you need to validate certificates for IP addresses,
|
||||||
|
//! which are not currently supported in rustls.
|
||||||
|
//!
|
||||||
|
//! Here's an example of constructing an Agent that uses native-tls. It requires the
|
||||||
|
//! "native-tls" feature to be enabled.
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[cfg(feature = "native-tls")]
|
||||||
|
//! # fn build() -> std::result::Result<(), ureq::Error> {
|
||||||
|
//! # ureq::is_test(true);
|
||||||
|
//! use std::sync::Arc;
|
||||||
|
//! use ureq::Agent;
|
||||||
|
//!
|
||||||
|
//! let agent = ureq::AgentBuilder::new()
|
||||||
|
//! .tls_connector(Arc::new(native_tls::TlsConnector::new().unwrap()))
|
||||||
|
//! .build();
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Trusted Roots
|
||||||
|
//!
|
||||||
|
//! When you use rustls (`tls` feature), ureq defaults to trusting
|
||||||
|
//! [webpki-roots](https://docs.rs/webpki-roots/), a
|
||||||
|
//! copy of the Mozilla Root program that is bundled into your program (and so won't update if your
|
||||||
|
//! program isn't updated). You can alternately configure
|
||||||
|
//! [rustls-native-certs](https://docs.rs/rustls-native-certs/) which extracts the roots from your
|
||||||
|
//! OS' trust store. That means it will update when your OS is updated, and also that it will
|
||||||
|
//! include locally installed roots.
|
||||||
|
//!
|
||||||
|
//! When you use `native-tls`, ureq will use your OS' certificate verifier and root store.
|
||||||
|
//!
|
||||||
//! # Blocking I/O for simplicity
|
//! # Blocking I/O for simplicity
|
||||||
//!
|
//!
|
||||||
//! Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O
|
//! Ureq uses blocking I/O rather than Rust's newer [asynchronous (async) I/O][async]. Async I/O
|
||||||
@@ -287,6 +333,46 @@ mod response;
|
|||||||
mod stream;
|
mod stream;
|
||||||
mod unit;
|
mod unit;
|
||||||
|
|
||||||
|
// rustls is our default tls engine. If the feature is on, it will be
|
||||||
|
// used for the shortcut calls the top of the crate (`ureq::get` etc).
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
mod rtls;
|
||||||
|
|
||||||
|
// native-tls is a feature that must be configured via the AgentBuilder.
|
||||||
|
// it is never picked up as a default (and never used by `ureq::get` etc).
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
mod ntls;
|
||||||
|
|
||||||
|
// If we have rustls compiled, that is the default.
|
||||||
|
#[cfg(feature = "tls")]
|
||||||
|
pub(crate) fn default_tls_config() -> std::sync::Arc<dyn TlsConnector> {
|
||||||
|
rtls::default_tls_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without rustls compiled, we just fail on https when using the shortcut
|
||||||
|
// calls at the top of the crate (`ureq::get` etc).
|
||||||
|
#[cfg(not(feature = "tls"))]
|
||||||
|
pub(crate) fn default_tls_config() -> std::sync::Arc<dyn TlsConnector> {
|
||||||
|
use crate::stream::HttpsStream;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
struct NoTlsConfig;
|
||||||
|
|
||||||
|
impl TlsConnector for NoTlsConfig {
|
||||||
|
fn connect(
|
||||||
|
&self,
|
||||||
|
_dns_name: &str,
|
||||||
|
_tcp_stream: TcpStream,
|
||||||
|
) -> Result<Box<dyn HttpsStream>, crate::error::Error> {
|
||||||
|
Err(ErrorKind::UnknownScheme
|
||||||
|
.msg("cannot make HTTPS request because no TLS backend is configured"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(NoTlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
mod cookies;
|
mod cookies;
|
||||||
|
|
||||||
@@ -307,6 +393,7 @@ pub use crate::proxy::Proxy;
|
|||||||
pub use crate::request::{Request, RequestUrl};
|
pub use crate::request::{Request, RequestUrl};
|
||||||
pub use crate::resolve::Resolver;
|
pub use crate::resolve::Resolver;
|
||||||
pub use crate::response::Response;
|
pub use crate::response::Response;
|
||||||
|
pub use crate::stream::TlsConnector;
|
||||||
|
|
||||||
// re-export
|
// re-export
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
@@ -439,7 +526,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
fn connect_https_google() {
|
fn connect_https_google_rustls() {
|
||||||
let agent = Agent::new();
|
let agent = Agent::new();
|
||||||
|
|
||||||
let resp = agent.get("https://www.google.com/").call().unwrap();
|
let resp = agent.get("https://www.google.com/").call().unwrap();
|
||||||
@@ -451,7 +538,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
|
fn connect_https_google_native_tls() {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
let tls_config = native_tls::TlsConnector::new().unwrap();
|
||||||
|
let agent = builder().tls_connector(Arc::new(tls_config)).build();
|
||||||
|
|
||||||
|
let resp = agent.get("https://www.google.com/").call().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
"text/html; charset=ISO-8859-1",
|
||||||
|
resp.header("content-type").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!("text/html", resp.content_type());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
fn connect_https_invalid_name() {
|
fn connect_https_invalid_name() {
|
||||||
let result = get("https://example.com{REQUEST_URI}/").call();
|
let result = get("https://example.com{REQUEST_URI}/").call();
|
||||||
let e = ErrorKind::Dns;
|
let e = ErrorKind::Dns;
|
||||||
|
|||||||
30
src/ntls.rs
Normal file
30
src/ntls.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
use crate::error::ErrorKind;
|
||||||
|
use crate::stream::{HttpsStream, TlsConnector};
|
||||||
|
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn default_tls_config() -> std::sync::Arc<dyn TlsConnector> {
|
||||||
|
Arc::new(native_tls::TlsConnector::new().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsConnector for native_tls::TlsConnector {
|
||||||
|
fn connect(
|
||||||
|
&self,
|
||||||
|
dns_name: &str,
|
||||||
|
tcp_stream: TcpStream,
|
||||||
|
) -> Result<Box<dyn HttpsStream>, Error> {
|
||||||
|
let stream = native_tls::TlsConnector::connect(self, dns_name, tcp_stream)
|
||||||
|
.map_err(|e| ErrorKind::Dns.new().src(e))?;
|
||||||
|
Ok(Box::new(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
impl HttpsStream for native_tls::TlsStream<TcpStream> {
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
Some(self.get_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -491,8 +491,8 @@ impl Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn to_write_vec(self) -> Vec<u8> {
|
pub fn as_write_vec(&self) -> &[u8] {
|
||||||
self.stream.to_write_vec()
|
self.stream.as_write_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
121
src/rtls.rs
Normal file
121
src/rtls.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::ErrorKind;
|
||||||
|
use crate::{
|
||||||
|
stream::{HttpsStream, TlsConnector},
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
fn is_close_notify(e: &std::io::Error) -> bool {
|
||||||
|
if e.kind() != io::ErrorKind::ConnectionAborted {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = e.get_ref() {
|
||||||
|
// :(
|
||||||
|
|
||||||
|
return msg.description().contains("CloseNotify");
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RustlsStream(rustls::StreamOwned<rustls::ClientConnection, TcpStream>);
|
||||||
|
|
||||||
|
impl HttpsStream for RustlsStream {
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
Some(self.0.get_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: After upgrading to rustls 0.20 or higher, we can remove these Read
|
||||||
|
// and Write impls, leaving only `impl TlsStream for rustls::StreamOwned...`.
|
||||||
|
// Currently we need to implement Read in order to treat close_notify specially.
|
||||||
|
// The next release of rustls will handle close_notify in a more intuitive way.
|
||||||
|
impl Read for RustlsStream {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self.0.read(buf) {
|
||||||
|
Ok(size) => Ok(size),
|
||||||
|
Err(ref e) if is_close_notify(e) => Ok(0),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for RustlsStream {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.0.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.0.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-certs")]
|
||||||
|
fn root_certs() -> rustls::RootCertStore {
|
||||||
|
let mut root_store = rustls::RootCertStore::empty();
|
||||||
|
|
||||||
|
let certs = rustls_native_certs::load_native_certs().expect("Could not load platform certs");
|
||||||
|
|
||||||
|
for cert in certs {
|
||||||
|
// Repackage the certificate DER bytes.
|
||||||
|
let rustls_cert = rustls::Certificate(cert.0);
|
||||||
|
|
||||||
|
root_store
|
||||||
|
.add(&rustls_cert)
|
||||||
|
.expect("Failed to add native certificate too root store");
|
||||||
|
}
|
||||||
|
|
||||||
|
root_store
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "native-certs"))]
|
||||||
|
fn root_certs() -> rustls::RootCertStore {
|
||||||
|
let mut root_store = rustls::RootCertStore::empty();
|
||||||
|
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
|
||||||
|
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||||
|
ta.subject,
|
||||||
|
ta.spki,
|
||||||
|
ta.name_constraints,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
root_store
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TlsConnector for Arc<rustls::ClientConfig> {
|
||||||
|
fn connect(
|
||||||
|
&self,
|
||||||
|
dns_name: &str,
|
||||||
|
mut tcp_stream: TcpStream,
|
||||||
|
) -> Result<Box<dyn HttpsStream>, Error> {
|
||||||
|
let sni =
|
||||||
|
rustls::ServerName::try_from(dns_name).map_err(|e| ErrorKind::Dns.new().src(e))?;
|
||||||
|
|
||||||
|
let mut sess = rustls::ClientConnection::new(self.clone(), sni)
|
||||||
|
.map_err(|e| ErrorKind::Io.new().src(e))?;
|
||||||
|
|
||||||
|
sess.complete_io(&mut tcp_stream)
|
||||||
|
.map_err(|err| ErrorKind::ConnectionFailed.new().src(err))?;
|
||||||
|
let stream = rustls::StreamOwned::new(sess, tcp_stream);
|
||||||
|
|
||||||
|
Ok(Box::new(RustlsStream(stream)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_tls_config() -> Arc<dyn TlsConnector> {
|
||||||
|
static TLS_CONF: Lazy<Arc<dyn TlsConnector>> = Lazy::new(|| {
|
||||||
|
let config = rustls::ClientConfig::builder()
|
||||||
|
.with_safe_defaults()
|
||||||
|
.with_root_certificates(root_certs())
|
||||||
|
.with_no_client_auth();
|
||||||
|
Arc::new(Arc::new(config))
|
||||||
|
});
|
||||||
|
TLS_CONF.clone()
|
||||||
|
}
|
||||||
242
src/stream.rs
242
src/stream.rs
@@ -8,10 +8,6 @@ use std::{fmt, io::Cursor};
|
|||||||
|
|
||||||
use chunked_transfer::Decoder as ChunkDecoder;
|
use chunked_transfer::Decoder as ChunkDecoder;
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
use rustls::ClientConnection;
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
use rustls::StreamOwned;
|
|
||||||
#[cfg(feature = "socks-proxy")]
|
#[cfg(feature = "socks-proxy")]
|
||||||
use socks::{TargetAddr, ToTargetAddr};
|
use socks::{TargetAddr, ToTargetAddr};
|
||||||
|
|
||||||
@@ -21,16 +17,83 @@ use crate::{error::Error, proxy::Proto};
|
|||||||
use crate::error::ErrorKind;
|
use crate::error::ErrorKind;
|
||||||
use crate::unit::Unit;
|
use crate::unit::Unit;
|
||||||
|
|
||||||
pub(crate) struct Stream {
|
pub trait HttpsStream: Read + Write + Send + Sync + 'static {
|
||||||
inner: BufReader<Inner>,
|
fn socket(&self) -> Option<&TcpStream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
pub trait TlsConnector: Send + Sync {
|
||||||
enum Inner {
|
fn connect(
|
||||||
Http(TcpStream),
|
&self,
|
||||||
#[cfg(feature = "tls")]
|
dns_name: &str,
|
||||||
Https(rustls::StreamOwned<rustls::ClientConnection, TcpStream>),
|
tcp_stream: TcpStream,
|
||||||
Test(Box<dyn Read + Send + Sync>, Vec<u8>),
|
) -> Result<Box<dyn HttpsStream>, crate::error::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Stream {
|
||||||
|
inner: BufReader<Box<dyn Inner + Send + Sync + 'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Inner: Read + Write {
|
||||||
|
fn is_poolable(&self) -> bool;
|
||||||
|
fn socket(&self) -> Option<&TcpStream>;
|
||||||
|
fn as_write_vec(&self) -> &[u8] {
|
||||||
|
panic!("as_write_vec on non Test stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HttpsStream + ?Sized> HttpsStream for Box<T> {
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
HttpsStream::socket(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HttpsStream> Inner for T {
|
||||||
|
fn is_poolable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
HttpsStream::socket(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner for TcpStream {
|
||||||
|
fn is_poolable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestStream(Box<dyn Read + Send + Sync>, Vec<u8>);
|
||||||
|
|
||||||
|
impl Inner for TestStream {
|
||||||
|
fn is_poolable(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn socket(&self) -> Option<&TcpStream> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn as_write_vec(&self) -> &[u8] {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for TestStream {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.0.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for TestStream {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.1.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeadlineStream wraps a stream such that read() will return an error
|
// DeadlineStream wraps a stream such that read() will return an error
|
||||||
@@ -112,16 +175,20 @@ pub(crate) fn io_err_timeout(error: String) -> io::Error {
|
|||||||
|
|
||||||
impl fmt::Debug for Stream {
|
impl fmt::Debug for Stream {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.inner.get_ref() {
|
match self.inner.get_ref().socket() {
|
||||||
Inner::Http(tcpstream) => write!(f, "{:?}", tcpstream),
|
Some(s) => write!(f, "{:?}", s),
|
||||||
#[cfg(feature = "tls")]
|
None => write!(f, "Stream(Test)"),
|
||||||
Inner::Https(tlsstream) => write!(f, "{:?}", tlsstream.get_ref()),
|
|
||||||
Inner::Test(_, _) => write!(f, "Stream(Test)"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream {
|
impl Stream {
|
||||||
|
fn new(t: impl Inner + Send + Sync + 'static) -> Stream {
|
||||||
|
Stream::logged_create(Stream {
|
||||||
|
inner: BufReader::new(Box::new(t)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn logged_create(stream: Stream) -> Stream {
|
fn logged_create(stream: Stream) -> Stream {
|
||||||
debug!("created stream: {:?}", stream);
|
debug!("created stream: {:?}", stream);
|
||||||
stream
|
stream
|
||||||
@@ -129,20 +196,13 @@ impl Stream {
|
|||||||
|
|
||||||
pub(crate) fn from_vec(v: Vec<u8>) -> Stream {
|
pub(crate) fn from_vec(v: Vec<u8>) -> Stream {
|
||||||
Stream::logged_create(Stream {
|
Stream::logged_create(Stream {
|
||||||
inner: BufReader::new(Inner::Test(Box::new(Cursor::new(v)), vec![])),
|
inner: BufReader::new(Box::new(TestStream(Box::new(Cursor::new(v)), vec![]))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_tcp_stream(t: TcpStream) -> Stream {
|
fn from_tcp_stream(t: TcpStream) -> Stream {
|
||||||
Stream::logged_create(Stream {
|
Stream::logged_create(Stream {
|
||||||
inner: BufReader::new(Inner::Http(t)),
|
inner: BufReader::new(Box::new(t)),
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
fn from_tls_stream(t: StreamOwned<ClientConnection, TcpStream>) -> Stream {
|
|
||||||
Stream::logged_create(Stream {
|
|
||||||
inner: BufReader::new(Inner::Https(t)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,12 +246,7 @@ impl Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_poolable(&self) -> bool {
|
pub fn is_poolable(&self) -> bool {
|
||||||
match self.inner.get_ref() {
|
self.inner.get_ref().is_poolable()
|
||||||
Inner::Http(_) => true,
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Inner::Https(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset(&mut self) -> io::Result<()> {
|
pub(crate) fn reset(&mut self) -> io::Result<()> {
|
||||||
@@ -206,12 +261,7 @@ impl Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn socket(&self) -> Option<&TcpStream> {
|
pub(crate) fn socket(&self) -> Option<&TcpStream> {
|
||||||
match self.inner.get_ref() {
|
self.inner.get_ref().socket()
|
||||||
Inner::Http(b) => Some(b),
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Inner::Https(b) => Some(b.get_ref()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
|
pub(crate) fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
|
||||||
@@ -223,11 +273,8 @@ impl Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn to_write_vec(&self) -> Vec<u8> {
|
pub fn as_write_vec(&self) -> &[u8] {
|
||||||
match self.inner.get_ref() {
|
self.inner.get_ref().as_write_vec()
|
||||||
Inner::Test(_, writer) => writer.clone(),
|
|
||||||
_ => panic!("to_write_vec on non Test stream"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,17 +284,6 @@ impl Read for Stream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for Inner {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
match self {
|
|
||||||
Inner::Http(sock) => sock.read(buf),
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Inner::Https(stream) => read_https(stream, buf),
|
|
||||||
Inner::Test(reader, _) => reader.read(buf),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufRead for Stream {
|
impl BufRead for Stream {
|
||||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||||
self.inner.fill_buf()
|
self.inner.fill_buf()
|
||||||
@@ -268,50 +304,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
fn read_https(
|
|
||||||
stream: &mut StreamOwned<ClientConnection, TcpStream>,
|
|
||||||
buf: &mut [u8],
|
|
||||||
) -> io::Result<usize> {
|
|
||||||
match stream.read(buf) {
|
|
||||||
Ok(size) => Ok(size),
|
|
||||||
Err(ref e) if is_close_notify(e) => Ok(0),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(deprecated)]
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
fn is_close_notify(e: &std::io::Error) -> bool {
|
|
||||||
if e.kind() != io::ErrorKind::ConnectionAborted {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(msg) = e.get_ref() {
|
|
||||||
// :(
|
|
||||||
|
|
||||||
return msg.description().contains("CloseNotify");
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for Stream {
|
impl Write for Stream {
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
match self.inner.get_mut() {
|
self.inner.get_mut().write(buf)
|
||||||
Inner::Http(sock) => sock.write(buf),
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Inner::Https(stream) => stream.write(buf),
|
|
||||||
Inner::Test(_, writer) => writer.write(buf),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
match self.inner.get_mut() {
|
self.inner.get_mut().flush()
|
||||||
Inner::Http(sock) => sock.flush(),
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
Inner::Https(stream) => stream.flush(),
|
|
||||||
Inner::Test(_, writer) => writer.flush(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,55 +326,14 @@ pub(crate) fn connect_http(unit: &Unit, hostname: &str) -> Result<Stream, Error>
|
|||||||
connect_host(unit, hostname, port).map(Stream::from_tcp_stream)
|
connect_host(unit, hostname, port).map(Stream::from_tcp_stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
pub(crate) fn connect_https(unit: &Unit, hostname: &str) -> Result<Stream, Error> {
|
pub(crate) fn connect_https(unit: &Unit, hostname: &str) -> Result<Stream, Error> {
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{convert::TryFrom, sync::Arc};
|
|
||||||
|
|
||||||
static TLS_CONF: Lazy<Arc<rustls::ClientConfig>> = Lazy::new(|| {
|
|
||||||
let mut root_store = rustls::RootCertStore::empty();
|
|
||||||
#[cfg(not(feature = "native-certs"))]
|
|
||||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
|
|
||||||
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
|
|
||||||
ta.subject,
|
|
||||||
ta.spki,
|
|
||||||
ta.name_constraints,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
#[cfg(feature = "native-certs")]
|
|
||||||
for cert in rustls_native_certs::load_native_certs().expect("Could not load platform certs")
|
|
||||||
{
|
|
||||||
root_store.add(&rustls::Certificate(cert.0)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = rustls::ClientConfig::builder()
|
|
||||||
.with_safe_defaults()
|
|
||||||
.with_root_certificates(root_store)
|
|
||||||
.with_no_client_auth();
|
|
||||||
Arc::new(config)
|
|
||||||
});
|
|
||||||
|
|
||||||
let port = unit.url.port().unwrap_or(443);
|
let port = unit.url.port().unwrap_or(443);
|
||||||
|
|
||||||
let tls_conf: Arc<rustls::ClientConfig> = unit
|
let sock = connect_host(unit, hostname, port)?;
|
||||||
.agent
|
|
||||||
.config
|
|
||||||
.tls_config
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| c.0.clone())
|
|
||||||
.unwrap_or_else(|| TLS_CONF.clone());
|
|
||||||
let mut sock = connect_host(unit, hostname, port)?;
|
|
||||||
let mut sess = rustls::ClientConnection::new(
|
|
||||||
tls_conf,
|
|
||||||
rustls::ServerName::try_from(hostname).map_err(|e| ErrorKind::Dns.new().src(e))?,
|
|
||||||
)
|
|
||||||
.map_err(|e| ErrorKind::Io.new().src(e))?;
|
|
||||||
|
|
||||||
sess.complete_io(&mut sock)
|
let tls_conf = &unit.agent.config.tls_config;
|
||||||
.map_err(|err| ErrorKind::ConnectionFailed.new().src(err))?;
|
let https_stream = tls_conf.connect(hostname, sock)?;
|
||||||
let stream = rustls::StreamOwned::new(sess, sock);
|
Ok(Stream::new(https_stream))
|
||||||
|
|
||||||
Ok(Stream::from_tls_stream(stream))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<TcpStream, Error> {
|
pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<TcpStream, Error> {
|
||||||
@@ -666,10 +623,3 @@ pub(crate) fn connect_test(unit: &Unit) -> Result<Stream, Error> {
|
|||||||
pub(crate) fn connect_test(unit: &Unit) -> Result<Stream, Error> {
|
pub(crate) fn connect_test(unit: &Unit) -> Result<Stream, Error> {
|
||||||
Err(ErrorKind::UnknownScheme.msg(&format!("unknown scheme '{}'", unit.url.scheme())))
|
Err(ErrorKind::UnknownScheme.msg(&format!("unknown scheme '{}'", unit.url.scheme())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "tls"))]
|
|
||||||
pub(crate) fn connect_https(unit: &Unit, _hostname: &str) -> Result<Stream, Error> {
|
|
||||||
Err(ErrorKind::UnknownScheme
|
|
||||||
.msg("URL has 'https:' scheme but ureq was build without HTTP support")
|
|
||||||
.url(unit.url.clone()))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fn content_length_on_str() {
|
|||||||
let resp = post("test://host/content_length_on_str")
|
let resp = post("test://host/content_length_on_str")
|
||||||
.send_string("Hello World!!!")
|
.send_string("Hello World!!!")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nContent-Length: 14\r\n"));
|
assert!(s.contains("\r\nContent-Length: 14\r\n"));
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ fn user_set_content_length_on_str() {
|
|||||||
.set("Content-Length", "12345")
|
.set("Content-Length", "12345")
|
||||||
.send_string("Hello World!!!")
|
.send_string("Hello World!!!")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nContent-Length: 12345\r\n"));
|
assert!(s.contains("\r\nContent-Length: 12345\r\n"));
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ fn content_length_on_json() {
|
|||||||
let resp = post("test://host/content_length_on_json")
|
let resp = post("test://host/content_length_on_json")
|
||||||
.send_json(SerdeValue::Object(json))
|
.send_json(SerdeValue::Object(json))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nContent-Length: 20\r\n"));
|
assert!(s.contains("\r\nContent-Length: 20\r\n"));
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ fn content_length_and_chunked() {
|
|||||||
.set("Transfer-Encoding", "chunked")
|
.set("Transfer-Encoding", "chunked")
|
||||||
.send_string("Hello World!!!")
|
.send_string("Hello World!!!")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("Transfer-Encoding: chunked\r\n"));
|
assert!(s.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
assert!(!s.contains("\r\nContent-Length:\r\n"));
|
assert!(!s.contains("\r\nContent-Length:\r\n"));
|
||||||
@@ -73,7 +73,7 @@ fn str_with_encoding() {
|
|||||||
.set("Content-Type", "text/plain; charset=iso-8859-1")
|
.set("Content-Type", "text/plain; charset=iso-8859-1")
|
||||||
.send_string("Hällo Wörld!!!")
|
.send_string("Hällo Wörld!!!")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&vec[vec.len() - 14..],
|
&vec[vec.len() - 14..],
|
||||||
//H ä l l o _ W ö r l d ! ! !
|
//H ä l l o _ W ö r l d ! ! !
|
||||||
@@ -95,7 +95,7 @@ fn content_type_on_json() {
|
|||||||
let resp = post("test://host/content_type_on_json")
|
let resp = post("test://host/content_type_on_json")
|
||||||
.send_json(SerdeValue::Object(json))
|
.send_json(SerdeValue::Object(json))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nContent-Type: application/json\r\n"));
|
assert!(s.contains("\r\nContent-Type: application/json\r\n"));
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ fn content_type_not_overriden_on_json() {
|
|||||||
.set("content-type", "text/plain")
|
.set("content-type", "text/plain")
|
||||||
.send_json(SerdeValue::Object(json))
|
.send_json(SerdeValue::Object(json))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\ncontent-type: text/plain\r\n"));
|
assert!(s.contains("\r\ncontent-type: text/plain\r\n"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ fn no_query_string() {
|
|||||||
test::make_response(200, "OK", vec![], vec![])
|
test::make_response(200, "OK", vec![], vec![])
|
||||||
});
|
});
|
||||||
let resp = get("test://host/no_query_string").call().unwrap();
|
let resp = get("test://host/no_query_string").call().unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("GET /no_query_string HTTP/1.1"))
|
assert!(s.contains("GET /no_query_string HTTP/1.1"))
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ fn escaped_query_string() {
|
|||||||
.query("baz", "yo lo")
|
.query("baz", "yo lo")
|
||||||
.call()
|
.call()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(
|
assert!(
|
||||||
s.contains("GET /escaped_query_string?foo=bar&baz=yo+lo HTTP/1.1"),
|
s.contains("GET /escaped_query_string?foo=bar&baz=yo+lo HTTP/1.1"),
|
||||||
@@ -38,7 +38,7 @@ fn query_in_path() {
|
|||||||
test::make_response(200, "OK", vec![], vec![])
|
test::make_response(200, "OK", vec![], vec![])
|
||||||
});
|
});
|
||||||
let resp = get("test://host/query_in_path?foo=bar").call().unwrap();
|
let resp = get("test://host/query_in_path?foo=bar").call().unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("GET /query_in_path?foo=bar HTTP/1.1"))
|
assert!(s.contains("GET /query_in_path?foo=bar HTTP/1.1"))
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ fn query_in_path_and_req() {
|
|||||||
.query("baz", "1 2 3")
|
.query("baz", "1 2 3")
|
||||||
.call()
|
.call()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("GET /query_in_path_and_req?foo=bar&baz=1+2+3 HTTP/1.1"))
|
assert!(s.contains("GET /query_in_path_and_req?foo=bar&baz=1+2+3 HTTP/1.1"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#[cfg(feature = "tls")]
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
use super::super::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
fn read_range() {
|
fn read_range_rustls() {
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use super::super::*;
|
||||||
|
|
||||||
|
// rustls is used via crate level convenience calls
|
||||||
let resp = get("https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt")
|
let resp = get("https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt")
|
||||||
.set("Range", "bytes=1000-1999")
|
.set("Range", "bytes=1000-1999")
|
||||||
.call()
|
.call()
|
||||||
@@ -21,3 +20,30 @@ fn read_range() {
|
|||||||
[83, 99, 111, 116, 116, 34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]
|
[83, 99, 111, 116, 116, 34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
fn read_range_native_tls() {
|
||||||
|
use std::io::Read;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::super::*;
|
||||||
|
|
||||||
|
let tls_config = native_tls::TlsConnector::new().unwrap();
|
||||||
|
let agent = builder().tls_connector(Arc::new(tls_config)).build();
|
||||||
|
|
||||||
|
let resp = agent
|
||||||
|
.get("https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt")
|
||||||
|
.set("Range", "bytes=1000-1999")
|
||||||
|
.call()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), 206);
|
||||||
|
let mut reader = resp.into_reader();
|
||||||
|
let mut buf = vec![];
|
||||||
|
let len = reader.read_to_end(&mut buf).unwrap();
|
||||||
|
assert_eq!(len, 1000);
|
||||||
|
assert_eq!(
|
||||||
|
&buf[0..20],
|
||||||
|
[83, 99, 111, 116, 116, 34, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ fn escape_path() {
|
|||||||
test::make_response(200, "OK", vec![], vec![])
|
test::make_response(200, "OK", vec![], vec![])
|
||||||
});
|
});
|
||||||
let resp = get("test://host/escape_path here").call().unwrap();
|
let resp = get("test://host/escape_path here").call().unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("GET /escape_path%20here HTTP/1.1"))
|
assert!(s.contains("GET /escape_path%20here HTTP/1.1"))
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ pub fn host_no_port() {
|
|||||||
test::make_response(200, "OK", vec![], vec![])
|
test::make_response(200, "OK", vec![], vec![])
|
||||||
});
|
});
|
||||||
let resp = get("test://myhost/host_no_port").call().unwrap();
|
let resp = get("test://myhost/host_no_port").call().unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nHost: myhost\r\n"));
|
assert!(s.contains("\r\nHost: myhost\r\n"));
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ pub fn host_with_port() {
|
|||||||
test::make_response(200, "OK", vec![], vec![])
|
test::make_response(200, "OK", vec![], vec![])
|
||||||
});
|
});
|
||||||
let resp = get("test://myhost:234/host_with_port").call().unwrap();
|
let resp = get("test://myhost:234/host_with_port").call().unwrap();
|
||||||
let vec = resp.to_write_vec();
|
let vec = resp.as_write_vec();
|
||||||
let s = String::from_utf8_lossy(&vec);
|
let s = String::from_utf8_lossy(&vec);
|
||||||
assert!(s.contains("\r\nHost: myhost:234\r\n"));
|
assert!(s.contains("\r\nHost: myhost:234\r\n"));
|
||||||
}
|
}
|
||||||
|
|||||||
12
test.sh
12
test.sh
@@ -4,11 +4,9 @@ set -eu
|
|||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
export RUSTFLAGS="-D dead_code -D unused-variables -D unused"
|
export RUSTFLAGS="-D dead_code -D unused-variables -D unused"
|
||||||
|
|
||||||
for tls in "" tls ; do
|
for feature in "" tls json charset cookies socks-proxy "tls native-certs" native-tls; do
|
||||||
for feature in "" json charset cookies socks-proxy native-certs ; do
|
if ! cargo test --no-default-features --features "${feature}" ; then
|
||||||
if ! cargo test --no-default-features --features "${tls} ${feature}" ; then
|
echo Command failed: cargo test --no-default-features --features \"${feature}\"
|
||||||
echo Command failed: cargo test \"${what}\" --no-default-features --features \"${tls} ${feature}\"
|
exit 1
|
||||||
exit 1
|
fi
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#[cfg(feature = "tls")]
|
#[cfg(all(feature = "json", any(feature = "tls", feature = "tls-native")))]
|
||||||
#[cfg(feature = "json")]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_set_header() {
|
fn agent_set_header() {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -23,10 +22,12 @@ fn agent_set_header() {
|
|||||||
assert_eq!("value", json.headers.get("Header").unwrap());
|
assert_eq!("value", json.headers.get("Header").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "tls", feature = "tls-native"))]
|
||||||
// From here https://badssl.com/download/
|
// From here https://badssl.com/download/
|
||||||
// Decrypt key with: openssl rsa -in ./badssl.com-client.pem
|
// Decrypt key with: openssl rsa -in ./badssl.com-client.pem
|
||||||
#[cfg(feature = "tls")]
|
fn tls_client_certificate() {
|
||||||
const BADSSL_CLIENT_CERT_PEM: &str = r#"Bag Attributes
|
const BADSSL_CLIENT_CERT_PEM: &str = r#"Bag Attributes
|
||||||
localKeyID: 41 C3 6C 33 C7 E3 36 DD EA 4A 1F C0 B7 23 B8 E6 9C DC D8 0F
|
localKeyID: 41 C3 6C 33 C7 E3 36 DD EA 4A 1F C0 B7 23 B8 E6 9C DC D8 0F
|
||||||
subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate
|
subject=C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Client Certificate
|
||||||
|
|
||||||
@@ -91,9 +92,6 @@ m0Wqhhi8/24Sy934t5Txgkfoltg8ahkx934WjP6WWRnSAu+cf+vW
|
|||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[cfg(feature = "tls")]
|
|
||||||
#[test]
|
|
||||||
fn tls_client_certificate() {
|
|
||||||
use ureq::OrAnyStatus;
|
use ureq::OrAnyStatus;
|
||||||
|
|
||||||
let certs = rustls_pemfile::certs(&mut BADSSL_CLIENT_CERT_PEM.as_bytes())
|
let certs = rustls_pemfile::certs(&mut BADSSL_CLIENT_CERT_PEM.as_bytes())
|
||||||
|
|||||||
Reference in New Issue
Block a user