committed by
Sean McArthur
parent
81e0f1ff2a
commit
cf8944a0f0
@@ -1,26 +1,14 @@
|
||||
use std::{fmt, str};
|
||||
use std::net::IpAddr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::net::IpAddr;
|
||||
use std::{fmt, str};
|
||||
|
||||
use crate::header::{
|
||||
Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
|
||||
CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, Poll};
|
||||
use crate::header::{
|
||||
Entry,
|
||||
HeaderMap,
|
||||
HeaderValue,
|
||||
ACCEPT,
|
||||
ACCEPT_ENCODING,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
LOCATION,
|
||||
PROXY_AUTHORIZATION,
|
||||
RANGE,
|
||||
REFERER,
|
||||
TRANSFER_ENCODING,
|
||||
USER_AGENT,
|
||||
};
|
||||
use http::Uri;
|
||||
use hyper::client::ResponseFuture;
|
||||
use mime;
|
||||
@@ -28,24 +16,22 @@ use mime;
|
||||
use native_tls::TlsConnector;
|
||||
use tokio::{clock, timer::Delay};
|
||||
|
||||
use log::{debug};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::request::{Request, RequestBuilder};
|
||||
use super::response::Response;
|
||||
use crate::connect::Connector;
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::cookie;
|
||||
use crate::redirect::{self, RedirectPolicy, remove_sensitive_headers};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
use crate::into_url::{expect_uri, try_uri};
|
||||
use crate::proxy::get_proxies;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy};
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::tls::TlsBackend;
|
||||
#[cfg(feature = "tls")]
|
||||
use crate::{Certificate, Identity};
|
||||
use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
|
||||
|
||||
static DEFAULT_USER_AGENT: &str =
|
||||
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
/// An asynchronous `Client` to make Requests with.
|
||||
///
|
||||
@@ -98,7 +84,10 @@ impl ClientBuilder {
|
||||
pub fn new() -> ClientBuilder {
|
||||
let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
|
||||
headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT));
|
||||
headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"));
|
||||
headers.insert(
|
||||
ACCEPT,
|
||||
HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"),
|
||||
);
|
||||
|
||||
ClientBuilder {
|
||||
config: Config {
|
||||
@@ -156,8 +145,13 @@ impl ClientBuilder {
|
||||
id.add_to_native_tls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_default_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
},
|
||||
Connector::new_default_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
#[cfg(feature = "rustls-tls")]
|
||||
TlsBackend::Rustls => {
|
||||
use crate::tls::NoVerifier;
|
||||
@@ -166,15 +160,14 @@ impl ClientBuilder {
|
||||
if config.http2_only {
|
||||
tls.set_protocols(&["h2".into()]);
|
||||
} else {
|
||||
tls.set_protocols(&[
|
||||
"h2".into(),
|
||||
"http/1.1".into(),
|
||||
]);
|
||||
tls.set_protocols(&["h2".into(), "http/1.1".into()]);
|
||||
}
|
||||
tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
tls.root_store
|
||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
||||
|
||||
if !config.certs_verification {
|
||||
tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier));
|
||||
tls.dangerous()
|
||||
.set_certificate_verifier(Arc::new(NoVerifier));
|
||||
}
|
||||
|
||||
for cert in config.root_certs {
|
||||
@@ -185,7 +178,12 @@ impl ClientBuilder {
|
||||
id.add_to_rustls(&mut tls)?;
|
||||
}
|
||||
|
||||
Connector::new_rustls_tls(tls, proxies.clone(), config.local_address, config.nodelay)?
|
||||
Connector::new_rustls_tls(
|
||||
tls,
|
||||
proxies.clone(),
|
||||
config.local_address,
|
||||
config.nodelay,
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +206,7 @@ impl ClientBuilder {
|
||||
|
||||
let hyper_client = builder.build(connector);
|
||||
|
||||
let proxies_maybe_http_auth = proxies
|
||||
.iter()
|
||||
.any(|p| p.maybe_has_http_auth());
|
||||
let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
|
||||
|
||||
let cookie_store = config.cookie_store.map(RwLock::new);
|
||||
|
||||
@@ -277,7 +273,10 @@ impl ClientBuilder {
|
||||
/// site will be trusted for use from any other. This introduces a
|
||||
/// significant vulnerability to man-in-the-middle attacks.
|
||||
#[cfg(feature = "default-tls")]
|
||||
pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostname: bool) -> ClientBuilder {
|
||||
pub fn danger_accept_invalid_hostnames(
|
||||
mut self,
|
||||
accept_invalid_hostname: bool,
|
||||
) -> ClientBuilder {
|
||||
self.config.hostname_verification = !accept_invalid_hostname;
|
||||
self
|
||||
}
|
||||
@@ -299,7 +298,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Sets the default headers for every request.
|
||||
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
|
||||
for (key, value) in headers.iter() {
|
||||
@@ -349,7 +347,6 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
/// Set a `RedirectPolicy` for this client.
|
||||
///
|
||||
/// Default will follow redirects up to a maximum of 10.
|
||||
@@ -454,9 +451,7 @@ impl Client {
|
||||
/// Use `Client::builder()` if you wish to handle the failure as an `Error`
|
||||
/// instead of panicking.
|
||||
pub fn new() -> Client {
|
||||
ClientBuilder::new()
|
||||
.build()
|
||||
.expect("Client::new()")
|
||||
ClientBuilder::new().build().expect("Client::new()")
|
||||
}
|
||||
|
||||
/// Creates a `ClientBuilder` to configure a `Client`.
|
||||
@@ -529,9 +524,7 @@ impl Client {
|
||||
///
|
||||
/// This method fails whenever supplied `Url` cannot be parsed.
|
||||
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let req = url
|
||||
.into_url()
|
||||
.map(move |url| Request::new(method, url));
|
||||
let req = url.into_url().map(move |url| Request::new(method, url));
|
||||
RequestBuilder::new(self.clone(), req)
|
||||
}
|
||||
|
||||
@@ -551,14 +544,8 @@ impl Client {
|
||||
self.execute_request(request)
|
||||
}
|
||||
|
||||
|
||||
pub(super) fn execute_request(&self, req: Request) -> Pending {
|
||||
let (
|
||||
method,
|
||||
url,
|
||||
mut headers,
|
||||
body
|
||||
) = req.pieces();
|
||||
let (method, url, mut headers, body) = req.pieces();
|
||||
|
||||
// insert default headers in the request headers
|
||||
// without overwriting already appended headers.
|
||||
@@ -576,9 +563,8 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
if self.inner.gzip &&
|
||||
!headers.contains_key(ACCEPT_ENCODING) &&
|
||||
!headers.contains_key(RANGE) {
|
||||
if self.inner.gzip && !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE)
|
||||
{
|
||||
headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip"));
|
||||
}
|
||||
|
||||
@@ -588,10 +574,8 @@ impl Client {
|
||||
Some(body) => {
|
||||
let (reusable, body) = body.into_hyper();
|
||||
(Some(reusable), body)
|
||||
},
|
||||
None => {
|
||||
(None, hyper::Body::empty())
|
||||
}
|
||||
None => (None, hyper::Body::empty()),
|
||||
};
|
||||
|
||||
self.proxy_auth(&uri, &mut headers);
|
||||
@@ -606,9 +590,10 @@ impl Client {
|
||||
|
||||
let in_flight = self.inner.hyper.request(req);
|
||||
|
||||
let timeout = self.inner.request_timeout.map(|dur| {
|
||||
Delay::new(clock::now() + dur)
|
||||
});
|
||||
let timeout = self
|
||||
.inner
|
||||
.request_timeout
|
||||
.map(|dur| Delay::new(clock::now() + dur));
|
||||
|
||||
Pending {
|
||||
inner: PendingInner::Request(PendingRequest {
|
||||
@@ -643,14 +628,10 @@ impl Client {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for proxy in self.inner.proxies.iter() {
|
||||
if proxy.is_match(dst) {
|
||||
if let Some(header) = proxy.http_basic_auth(dst) {
|
||||
headers.insert(
|
||||
PROXY_AUTHORIZATION,
|
||||
header,
|
||||
);
|
||||
headers.insert(PROXY_AUTHORIZATION, header);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -671,8 +652,7 @@ impl fmt::Debug for Client {
|
||||
|
||||
impl fmt::Debug for ClientBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("ClientBuilder")
|
||||
.finish()
|
||||
f.debug_struct("ClientBuilder").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +706,9 @@ impl Future for Pending {
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref mut req) => req.poll(),
|
||||
PendingInner::Error(ref mut err) => Err(err.take().expect("Pending error polled more than once")),
|
||||
PendingInner::Error(ref mut err) => {
|
||||
Err(err.take().expect("Pending error polled more than once"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,56 +737,58 @@ impl Future for PendingRequest {
|
||||
store.0.store_response_cookies(cookies, &self.url);
|
||||
}
|
||||
let should_redirect = match res.status() {
|
||||
StatusCode::MOVED_PERMANENTLY |
|
||||
StatusCode::FOUND |
|
||||
StatusCode::SEE_OTHER => {
|
||||
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
|
||||
self.body = None;
|
||||
for header in &[TRANSFER_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, CONTENT_LENGTH] {
|
||||
for header in &[
|
||||
TRANSFER_ENCODING,
|
||||
CONTENT_ENCODING,
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LENGTH,
|
||||
] {
|
||||
self.headers.remove(header);
|
||||
}
|
||||
|
||||
match self.method {
|
||||
Method::GET | Method::HEAD => {},
|
||||
Method::GET | Method::HEAD => {}
|
||||
_ => {
|
||||
self.method = Method::GET;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
StatusCode::TEMPORARY_REDIRECT |
|
||||
StatusCode::PERMANENT_REDIRECT => match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
},
|
||||
}
|
||||
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {
|
||||
match self.body {
|
||||
Some(Some(_)) | None => true,
|
||||
Some(None) => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if should_redirect {
|
||||
let loc = res.headers()
|
||||
.get(LOCATION)
|
||||
.and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
let loc = res.headers().get(LOCATION).and_then(|val| {
|
||||
let loc = (|| -> Option<Url> {
|
||||
// Some sites may send a utf-8 Location header,
|
||||
// even though we're supposed to treat those bytes
|
||||
// as opaque, we'll check specifically for utf8.
|
||||
self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok()
|
||||
})();
|
||||
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
// Check that the `url` is also a valid `http::Uri`.
|
||||
//
|
||||
// If not, just log it and skip the redirect.
|
||||
let loc = loc.and_then(|url| {
|
||||
if try_uri(&url).is_some() {
|
||||
Some(url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
loc
|
||||
});
|
||||
|
||||
if loc.is_none() {
|
||||
debug!("Location header had invalid URI: {:?}", val);
|
||||
}
|
||||
loc
|
||||
});
|
||||
if let Some(loc) = loc {
|
||||
if self.client.referer {
|
||||
if let Some(referer) = make_referer(&loc, &self.url) {
|
||||
@@ -812,11 +796,10 @@ impl Future for PendingRequest {
|
||||
}
|
||||
}
|
||||
self.urls.push(self.url.clone());
|
||||
let action = self.client.redirect_policy.check(
|
||||
res.status(),
|
||||
&loc,
|
||||
&self.urls,
|
||||
);
|
||||
let action = self
|
||||
.client
|
||||
.redirect_policy
|
||||
.check(res.status(), &loc, &self.urls);
|
||||
|
||||
match action {
|
||||
redirect::Action::Follow => {
|
||||
@@ -844,13 +827,13 @@ impl Future for PendingRequest {
|
||||
*req.headers_mut() = self.headers.clone();
|
||||
self.in_flight = self.client.hyper.request(req);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
redirect::Action::Stop => {
|
||||
debug!("redirect_policy disallowed redirection to '{}'", loc);
|
||||
},
|
||||
}
|
||||
redirect::Action::LoopDetected => {
|
||||
return Err(crate::error::loop_detected(self.url.clone()));
|
||||
},
|
||||
}
|
||||
redirect::Action::TooManyRedirects => {
|
||||
return Err(crate::error::too_many_redirects(self.url.clone()));
|
||||
}
|
||||
@@ -866,17 +849,12 @@ impl Future for PendingRequest {
|
||||
impl fmt::Debug for Pending {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.inner {
|
||||
PendingInner::Request(ref req) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish()
|
||||
},
|
||||
PendingInner::Error(ref err) => {
|
||||
f.debug_struct("Pending")
|
||||
.field("error", err)
|
||||
.finish()
|
||||
}
|
||||
PendingInner::Request(ref req) => f
|
||||
.debug_struct("Pending")
|
||||
.field("method", &req.method)
|
||||
.field("url", &req.url)
|
||||
.finish(),
|
||||
PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,7 +881,7 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore
|
||||
if !header.is_empty() {
|
||||
headers.insert(
|
||||
crate::header::COOKIE,
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap()
|
||||
HeaderValue::from_bytes(header.as_bytes()).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user