summaryrefslogtreecommitdiffstats
path: root/vendor/reqwest/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/reqwest/src')
-rw-r--r--vendor/reqwest/src/async_impl/body.rs370
-rw-r--r--vendor/reqwest/src/async_impl/client.rs2352
-rw-r--r--vendor/reqwest/src/async_impl/decoder.rs421
-rw-r--r--vendor/reqwest/src/async_impl/h3_client/connect.rs87
-rw-r--r--vendor/reqwest/src/async_impl/h3_client/dns.rs43
-rw-r--r--vendor/reqwest/src/async_impl/h3_client/mod.rs88
-rw-r--r--vendor/reqwest/src/async_impl/h3_client/pool.rs198
-rw-r--r--vendor/reqwest/src/async_impl/mod.rs18
-rw-r--r--vendor/reqwest/src/async_impl/multipart.rs674
-rw-r--r--vendor/reqwest/src/async_impl/request.rs1129
-rw-r--r--vendor/reqwest/src/async_impl/response.rs448
-rw-r--r--vendor/reqwest/src/async_impl/upgrade.rs73
-rw-r--r--vendor/reqwest/src/blocking/body.rs352
-rw-r--r--vendor/reqwest/src/blocking/client.rs1151
-rw-r--r--vendor/reqwest/src/blocking/mod.rs109
-rw-r--r--vendor/reqwest/src/blocking/multipart.rs483
-rw-r--r--vendor/reqwest/src/blocking/request.rs1063
-rw-r--r--vendor/reqwest/src/blocking/response.rs425
-rw-r--r--vendor/reqwest/src/blocking/wait.rs78
-rw-r--r--vendor/reqwest/src/connect.rs1156
-rw-r--r--vendor/reqwest/src/cookie.rs191
-rw-r--r--vendor/reqwest/src/dns/gai.rs32
-rw-r--r--vendor/reqwest/src/dns/mod.rs9
-rw-r--r--vendor/reqwest/src/dns/resolve.rs84
-rw-r--r--vendor/reqwest/src/dns/trust_dns.rs108
-rw-r--r--vendor/reqwest/src/error.rs383
-rw-r--r--vendor/reqwest/src/into_url.rs120
-rw-r--r--vendor/reqwest/src/lib.rs351
-rw-r--r--vendor/reqwest/src/proxy.rs1857
-rw-r--r--vendor/reqwest/src/redirect.rs337
-rw-r--r--vendor/reqwest/src/response.rs41
-rw-r--r--vendor/reqwest/src/tls.rs510
-rw-r--r--vendor/reqwest/src/util.rs89
-rw-r--r--vendor/reqwest/src/wasm/body.rs291
-rw-r--r--vendor/reqwest/src/wasm/client.rs379
-rw-r--r--vendor/reqwest/src/wasm/mod.rs53
-rw-r--r--vendor/reqwest/src/wasm/multipart.rs360
-rw-r--r--vendor/reqwest/src/wasm/request.rs479
-rw-r--r--vendor/reqwest/src/wasm/response.rs189
39 files changed, 16581 insertions, 0 deletions
diff --git a/vendor/reqwest/src/async_impl/body.rs b/vendor/reqwest/src/async_impl/body.rs
new file mode 100644
index 000000000..c9c506f3d
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/body.rs
@@ -0,0 +1,370 @@
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use bytes::Bytes;
+use futures_core::Stream;
+use http_body::Body as HttpBody;
+use pin_project_lite::pin_project;
+#[cfg(feature = "stream")]
+use tokio::fs::File;
+use tokio::time::Sleep;
+#[cfg(feature = "stream")]
+use tokio_util::io::ReaderStream;
+
+/// An asynchronous request body.
+pub struct Body {
+ inner: Inner,
+}
+
+// The `Stream` trait isn't stable, so the impl isn't public.
+pub(crate) struct ImplStream(Body);
+
+enum Inner {
+ Reusable(Bytes),
+ Streaming {
+ body: Pin<
+ Box<
+ dyn HttpBody<Data = Bytes, Error = Box<dyn std::error::Error + Send + Sync>>
+ + Send
+ + Sync,
+ >,
+ >,
+ timeout: Option<Pin<Box<Sleep>>>,
+ },
+}
+
+pin_project! {
+ struct WrapStream<S> {
+ #[pin]
+ inner: S,
+ }
+}
+
+struct WrapHyper(hyper::Body);
+
+impl Body {
+ /// Returns a reference to the internal data of the `Body`.
+ ///
+ /// `None` is returned, if the underlying data is a stream.
+ pub fn as_bytes(&self) -> Option<&[u8]> {
+ match &self.inner {
+ Inner::Reusable(bytes) => Some(bytes.as_ref()),
+ Inner::Streaming { .. } => None,
+ }
+ }
+
+ /// Wrap a futures `Stream` in a box inside `Body`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use reqwest::Body;
+ /// # use futures_util;
+ /// # fn main() {
+ /// let chunks: Vec<Result<_, ::std::io::Error>> = vec![
+ /// Ok("hello"),
+ /// Ok(" "),
+ /// Ok("world"),
+ /// ];
+ ///
+ /// let stream = futures_util::stream::iter(chunks);
+ ///
+ /// let body = Body::wrap_stream(stream);
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the `stream` feature to be enabled.
+ #[cfg(feature = "stream")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
+ pub fn wrap_stream<S>(stream: S) -> Body
+ where
+ S: futures_core::stream::TryStream + Send + Sync + 'static,
+ S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
+ Bytes: From<S::Ok>,
+ {
+ Body::stream(stream)
+ }
+
+ pub(crate) fn stream<S>(stream: S) -> Body
+ where
+ S: futures_core::stream::TryStream + Send + Sync + 'static,
+ S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
+ Bytes: From<S::Ok>,
+ {
+ use futures_util::TryStreamExt;
+
+ let body = Box::pin(WrapStream {
+ inner: stream.map_ok(Bytes::from).map_err(Into::into),
+ });
+ Body {
+ inner: Inner::Streaming {
+ body,
+ timeout: None,
+ },
+ }
+ }
+
+ pub(crate) fn response(body: hyper::Body, timeout: Option<Pin<Box<Sleep>>>) -> Body {
+ Body {
+ inner: Inner::Streaming {
+ body: Box::pin(WrapHyper(body)),
+ timeout,
+ },
+ }
+ }
+
+ #[cfg(feature = "blocking")]
+ pub(crate) fn wrap(body: hyper::Body) -> Body {
+ Body {
+ inner: Inner::Streaming {
+ body: Box::pin(WrapHyper(body)),
+ timeout: None,
+ },
+ }
+ }
+
+ pub(crate) fn empty() -> Body {
+ Body::reusable(Bytes::new())
+ }
+
+ pub(crate) fn reusable(chunk: Bytes) -> Body {
+ Body {
+ inner: Inner::Reusable(chunk),
+ }
+ }
+
+ pub(crate) fn try_reuse(self) -> (Option<Bytes>, Self) {
+ let reuse = match self.inner {
+ Inner::Reusable(ref chunk) => Some(chunk.clone()),
+ Inner::Streaming { .. } => None,
+ };
+
+ (reuse, self)
+ }
+
+ pub(crate) fn try_clone(&self) -> Option<Body> {
+ match self.inner {
+ Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())),
+ Inner::Streaming { .. } => None,
+ }
+ }
+
+ pub(crate) fn into_stream(self) -> ImplStream {
+ ImplStream(self)
+ }
+
+ #[cfg(feature = "multipart")]
+ pub(crate) fn content_length(&self) -> Option<u64> {
+ match self.inner {
+ Inner::Reusable(ref bytes) => Some(bytes.len() as u64),
+ Inner::Streaming { ref body, .. } => body.size_hint().exact(),
+ }
+ }
+}
+
+impl From<hyper::Body> for Body {
+ #[inline]
+ fn from(body: hyper::Body) -> Body {
+ Self {
+ inner: Inner::Streaming {
+ body: Box::pin(WrapHyper(body)),
+ timeout: None,
+ },
+ }
+ }
+}
+
+impl From<Bytes> for Body {
+ #[inline]
+ fn from(bytes: Bytes) -> Body {
+ Body::reusable(bytes)
+ }
+}
+
+impl From<Vec<u8>> for Body {
+ #[inline]
+ fn from(vec: Vec<u8>) -> Body {
+ Body::reusable(vec.into())
+ }
+}
+
+impl From<&'static [u8]> for Body {
+ #[inline]
+ fn from(s: &'static [u8]) -> Body {
+ Body::reusable(Bytes::from_static(s))
+ }
+}
+
+impl From<String> for Body {
+ #[inline]
+ fn from(s: String) -> Body {
+ Body::reusable(s.into())
+ }
+}
+
+impl From<&'static str> for Body {
+ #[inline]
+ fn from(s: &'static str) -> Body {
+ s.as_bytes().into()
+ }
+}
+
+#[cfg(feature = "stream")]
+#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
+impl From<File> for Body {
+ #[inline]
+ fn from(file: File) -> Body {
+ Body::wrap_stream(ReaderStream::new(file))
+ }
+}
+
+impl fmt::Debug for Body {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Body").finish()
+ }
+}
+
+// ===== impl ImplStream =====
+
+impl HttpBody for ImplStream {
+ type Data = Bytes;
+ type Error = crate::Error;
+
+ fn poll_data(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
+ let opt_try_chunk = match self.0.inner {
+ Inner::Streaming {
+ ref mut body,
+ ref mut timeout,
+ } => {
+ if let Some(ref mut timeout) = timeout {
+ if let Poll::Ready(()) = timeout.as_mut().poll(cx) {
+ return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
+ }
+ }
+ futures_core::ready!(Pin::new(body).poll_data(cx))
+ .map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::body))
+ }
+ Inner::Reusable(ref mut bytes) => {
+ if bytes.is_empty() {
+ None
+ } else {
+ Some(Ok(std::mem::replace(bytes, Bytes::new())))
+ }
+ }
+ };
+
+ Poll::Ready(opt_try_chunk)
+ }
+
+ fn poll_trailers(
+ self: Pin<&mut Self>,
+ _cx: &mut Context,
+ ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
+ Poll::Ready(Ok(None))
+ }
+
+ fn is_end_stream(&self) -> bool {
+ match self.0.inner {
+ Inner::Streaming { ref body, .. } => body.is_end_stream(),
+ Inner::Reusable(ref bytes) => bytes.is_empty(),
+ }
+ }
+
+ fn size_hint(&self) -> http_body::SizeHint {
+ match self.0.inner {
+ Inner::Streaming { ref body, .. } => body.size_hint(),
+ Inner::Reusable(ref bytes) => {
+ let mut hint = http_body::SizeHint::default();
+ hint.set_exact(bytes.len() as u64);
+ hint
+ }
+ }
+ }
+}
+
+impl Stream for ImplStream {
+ type Item = Result<Bytes, crate::Error>;
+
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ self.poll_data(cx)
+ }
+}
+
+// ===== impl WrapStream =====
+
+impl<S, D, E> HttpBody for WrapStream<S>
+where
+ S: Stream<Item = Result<D, E>>,
+ D: Into<Bytes>,
+ E: Into<Box<dyn std::error::Error + Send + Sync>>,
+{
+ type Data = Bytes;
+ type Error = E;
+
+ fn poll_data(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
+ let item = futures_core::ready!(self.project().inner.poll_next(cx)?);
+
+ Poll::Ready(item.map(|val| Ok(val.into())))
+ }
+
+ fn poll_trailers(
+ self: Pin<&mut Self>,
+ _cx: &mut Context,
+ ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
+ Poll::Ready(Ok(None))
+ }
+}
+
+// ===== impl WrapHyper =====
+
+impl HttpBody for WrapHyper {
+ type Data = Bytes;
+ type Error = Box<dyn std::error::Error + Send + Sync>;
+
+ fn poll_data(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
+ // safe pin projection
+ Pin::new(&mut self.0)
+ .poll_data(cx)
+ .map(|opt| opt.map(|res| res.map_err(Into::into)))
+ }
+
+ fn poll_trailers(
+ self: Pin<&mut Self>,
+ _cx: &mut Context,
+ ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
+ Poll::Ready(Ok(None))
+ }
+
+ fn is_end_stream(&self) -> bool {
+ self.0.is_end_stream()
+ }
+
+ fn size_hint(&self) -> http_body::SizeHint {
+ HttpBody::size_hint(&self.0)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Body;
+
+ #[test]
+ fn test_as_bytes() {
+ let test_data = b"Test body";
+ let body = Body::from(&test_data[..]);
+ assert_eq!(body.as_bytes(), Some(&test_data[..]));
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/client.rs b/vendor/reqwest/src/async_impl/client.rs
new file mode 100644
index 000000000..559e5f365
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/client.rs
@@ -0,0 +1,2352 @@
+#[cfg(any(feature = "native-tls", feature = "__rustls",))]
+use std::any::Any;
+use std::net::IpAddr;
+use std::sync::Arc;
+use std::time::Duration;
+use std::{collections::HashMap, convert::TryInto, net::SocketAddr};
+use std::{fmt, str};
+
+use bytes::Bytes;
+use http::header::{
+ Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
+ CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT,
+};
+use http::uri::Scheme;
+use http::Uri;
+use hyper::client::{HttpConnector, ResponseFuture as HyperResponseFuture};
+#[cfg(feature = "native-tls-crate")]
+use native_tls_crate::TlsConnector;
+use pin_project_lite::pin_project;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+use tokio::time::Sleep;
+
+use super::decoder::Accepts;
+use super::request::{Request, RequestBuilder};
+use super::response::Response;
+use super::Body;
+#[cfg(feature = "http3")]
+use crate::async_impl::h3_client::connect::H3Connector;
+#[cfg(feature = "http3")]
+use crate::async_impl::h3_client::{H3Client, H3ResponseFuture};
+use crate::connect::Connector;
+#[cfg(feature = "cookies")]
+use crate::cookie;
+#[cfg(feature = "trust-dns")]
+use crate::dns::trust_dns::TrustDnsResolver;
+use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolve};
+use crate::error;
+use crate::into_url::{expect_uri, try_uri};
+use crate::redirect::{self, remove_sensitive_headers};
+#[cfg(feature = "__tls")]
+use crate::tls::{self, TlsBackend};
+#[cfg(feature = "__tls")]
+use crate::Certificate;
+#[cfg(any(feature = "native-tls", feature = "__rustls"))]
+use crate::Identity;
+use crate::{IntoUrl, Method, Proxy, StatusCode, Url};
+use log::{debug, trace};
+#[cfg(feature = "http3")]
+use quinn::TransportConfig;
+#[cfg(feature = "http3")]
+use quinn::VarInt;
+
+/// An asynchronous `Client` to make Requests with.
+///
+/// The Client has various configuration values to tweak, but the defaults
+/// are set to what is usually the most commonly desired value. To configure a
+/// `Client`, use `Client::builder()`.
+///
+/// The `Client` holds a connection pool internally, so it is advised that
+/// you create one and **reuse** it.
+///
+/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it,
+/// because it already uses an [`Arc`] internally.
+///
+/// [`Rc`]: std::rc::Rc
+#[derive(Clone)]
+pub struct Client {
+ inner: Arc<ClientRef>,
+}
+
+/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
+#[must_use]
+pub struct ClientBuilder {
+ config: Config,
+}
+
+enum HttpVersionPref {
+ Http1,
+ Http2,
+ #[cfg(feature = "http3")]
+ Http3,
+ All,
+}
+
+struct Config {
+ // NOTE: When adding a new field, update `fmt::Debug for ClientBuilder`
+ accepts: Accepts,
+ headers: HeaderMap,
+ #[cfg(feature = "native-tls")]
+ hostname_verification: bool,
+ #[cfg(feature = "__tls")]
+ certs_verification: bool,
+ #[cfg(feature = "__tls")]
+ tls_sni: bool,
+ connect_timeout: Option<Duration>,
+ connection_verbose: bool,
+ pool_idle_timeout: Option<Duration>,
+ pool_max_idle_per_host: usize,
+ tcp_keepalive: Option<Duration>,
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ identity: Option<Identity>,
+ proxies: Vec<Proxy>,
+ auto_sys_proxy: bool,
+ redirect_policy: redirect::Policy,
+ referer: bool,
+ timeout: Option<Duration>,
+ #[cfg(feature = "__tls")]
+ root_certs: Vec<Certificate>,
+ #[cfg(feature = "__tls")]
+ tls_built_in_root_certs: bool,
+ #[cfg(feature = "__tls")]
+ min_tls_version: Option<tls::Version>,
+ #[cfg(feature = "__tls")]
+ max_tls_version: Option<tls::Version>,
+ #[cfg(feature = "__tls")]
+ tls: TlsBackend,
+ http_version_pref: HttpVersionPref,
+ http09_responses: bool,
+ http1_title_case_headers: bool,
+ http1_allow_obsolete_multiline_headers_in_responses: bool,
+ http2_initial_stream_window_size: Option<u32>,
+ http2_initial_connection_window_size: Option<u32>,
+ http2_adaptive_window: bool,
+ http2_max_frame_size: Option<u32>,
+ http2_keep_alive_interval: Option<Duration>,
+ http2_keep_alive_timeout: Option<Duration>,
+ http2_keep_alive_while_idle: bool,
+ local_address: Option<IpAddr>,
+ nodelay: bool,
+ #[cfg(feature = "cookies")]
+ cookie_store: Option<Arc<dyn cookie::CookieStore>>,
+ trust_dns: bool,
+ error: Option<crate::Error>,
+ https_only: bool,
+ #[cfg(feature = "http3")]
+ tls_enable_early_data: bool,
+ #[cfg(feature = "http3")]
+ quic_max_idle_timeout: Option<Duration>,
+ #[cfg(feature = "http3")]
+ quic_stream_receive_window: Option<VarInt>,
+ #[cfg(feature = "http3")]
+ quic_receive_window: Option<VarInt>,
+ #[cfg(feature = "http3")]
+ quic_send_window: Option<u64>,
+ dns_overrides: HashMap<String, Vec<SocketAddr>>,
+ dns_resolver: Option<Arc<dyn Resolve>>,
+}
+
+impl Default for ClientBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ClientBuilder {
+ /// Constructs a new `ClientBuilder`.
+ ///
+ /// This is the same as `Client::builder()`.
+ pub fn new() -> ClientBuilder {
+ let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
+ headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
+
+ ClientBuilder {
+ config: Config {
+ error: None,
+ accepts: Accepts::default(),
+ headers,
+ #[cfg(feature = "native-tls")]
+ hostname_verification: true,
+ #[cfg(feature = "__tls")]
+ certs_verification: true,
+ #[cfg(feature = "__tls")]
+ tls_sni: true,
+ connect_timeout: None,
+ connection_verbose: false,
+ pool_idle_timeout: Some(Duration::from_secs(90)),
+ pool_max_idle_per_host: std::usize::MAX,
+ // TODO: Re-enable default duration once hyper's HttpConnector is fixed
+ // to no longer error when an option fails.
+ tcp_keepalive: None, //Some(Duration::from_secs(60)),
+ proxies: Vec::new(),
+ auto_sys_proxy: true,
+ redirect_policy: redirect::Policy::default(),
+ referer: true,
+ timeout: None,
+ #[cfg(feature = "__tls")]
+ root_certs: Vec::new(),
+ #[cfg(feature = "__tls")]
+ tls_built_in_root_certs: true,
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ identity: None,
+ #[cfg(feature = "__tls")]
+ min_tls_version: None,
+ #[cfg(feature = "__tls")]
+ max_tls_version: None,
+ #[cfg(feature = "__tls")]
+ tls: TlsBackend::default(),
+ http_version_pref: HttpVersionPref::All,
+ http09_responses: false,
+ http1_title_case_headers: false,
+ http1_allow_obsolete_multiline_headers_in_responses: false,
+ http2_initial_stream_window_size: None,
+ http2_initial_connection_window_size: None,
+ http2_adaptive_window: false,
+ http2_max_frame_size: None,
+ http2_keep_alive_interval: None,
+ http2_keep_alive_timeout: None,
+ http2_keep_alive_while_idle: false,
+ local_address: None,
+ nodelay: true,
+ trust_dns: cfg!(feature = "trust-dns"),
+ #[cfg(feature = "cookies")]
+ cookie_store: None,
+ https_only: false,
+ dns_overrides: HashMap::new(),
+ #[cfg(feature = "http3")]
+ tls_enable_early_data: false,
+ #[cfg(feature = "http3")]
+ quic_max_idle_timeout: None,
+ #[cfg(feature = "http3")]
+ quic_stream_receive_window: None,
+ #[cfg(feature = "http3")]
+ quic_receive_window: None,
+ #[cfg(feature = "http3")]
+ quic_send_window: None,
+ dns_resolver: None,
+ },
+ }
+ }
+
+ /// Returns a `Client` that uses this `ClientBuilder` configuration.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if a TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ pub fn build(self) -> crate::Result<Client> {
+ let config = self.config;
+
+ if let Some(err) = config.error {
+ return Err(err);
+ }
+
+ let mut proxies = config.proxies;
+ if config.auto_sys_proxy {
+ proxies.push(Proxy::system());
+ }
+ let proxies = Arc::new(proxies);
+
+ #[allow(unused)]
+ #[cfg(feature = "http3")]
+ let mut h3_connector = None;
+
+ let mut connector = {
+ #[cfg(feature = "__tls")]
+ fn user_agent(headers: &HeaderMap) -> Option<HeaderValue> {
+ headers.get(USER_AGENT).cloned()
+ }
+
+ let mut resolver: Arc<dyn Resolve> = match config.trust_dns {
+ false => Arc::new(GaiResolver::new()),
+ #[cfg(feature = "trust-dns")]
+ true => Arc::new(TrustDnsResolver::new().map_err(crate::error::builder)?),
+ #[cfg(not(feature = "trust-dns"))]
+ true => unreachable!("trust-dns shouldn't be enabled unless the feature is"),
+ };
+ if let Some(dns_resolver) = config.dns_resolver {
+ resolver = dns_resolver;
+ }
+ if !config.dns_overrides.is_empty() {
+ resolver = Arc::new(DnsResolverWithOverrides::new(
+ resolver,
+ config.dns_overrides,
+ ));
+ }
+ let http = HttpConnector::new_with_resolver(DynResolver::new(resolver.clone()));
+
+ #[cfg(feature = "__tls")]
+ match config.tls {
+ #[cfg(feature = "default-tls")]
+ TlsBackend::Default => {
+ let mut tls = TlsConnector::builder();
+
+ #[cfg(all(feature = "native-tls-alpn", not(feature = "http3")))]
+ {
+ match config.http_version_pref {
+ HttpVersionPref::Http1 => {
+ tls.request_alpns(&["http/1.1"]);
+ }
+ HttpVersionPref::Http2 => {
+ tls.request_alpns(&["h2"]);
+ }
+ HttpVersionPref::All => {
+ tls.request_alpns(&["h2", "http/1.1"]);
+ }
+ }
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ tls.danger_accept_invalid_hostnames(!config.hostname_verification);
+ }
+
+ tls.danger_accept_invalid_certs(!config.certs_verification);
+
+ tls.use_sni(config.tls_sni);
+
+ tls.disable_built_in_roots(!config.tls_built_in_root_certs);
+
+ for cert in config.root_certs {
+ cert.add_to_native_tls(&mut tls);
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ if let Some(id) = config.identity {
+ id.add_to_native_tls(&mut tls)?;
+ }
+ }
+
+ if let Some(min_tls_version) = config.min_tls_version {
+ let protocol = min_tls_version.to_native_tls().ok_or_else(|| {
+ // TLS v1.3. This would be entirely reasonable,
+ // native-tls just doesn't support it.
+ // https://github.com/sfackler/rust-native-tls/issues/140
+ crate::error::builder("invalid minimum TLS version for backend")
+ })?;
+ tls.min_protocol_version(Some(protocol));
+ }
+
+ if let Some(max_tls_version) = config.max_tls_version {
+ let protocol = max_tls_version.to_native_tls().ok_or_else(|| {
+ // TLS v1.3.
+ // We could arguably do max_protocol_version(None), given
+ // that 1.4 does not exist yet, but that'd get messy in the
+ // future.
+ crate::error::builder("invalid maximum TLS version for backend")
+ })?;
+ tls.max_protocol_version(Some(protocol));
+ }
+
+ Connector::new_default_tls(
+ http,
+ tls,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ )?
+ }
+ #[cfg(feature = "native-tls")]
+ TlsBackend::BuiltNativeTls(conn) => Connector::from_built_default_tls(
+ http,
+ conn,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ ),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::BuiltRustls(conn) => Connector::new_rustls_tls(
+ http,
+ conn,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ ),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::Rustls => {
+ use crate::tls::NoVerifier;
+
+ // Set root certificates.
+ let mut root_cert_store = rustls::RootCertStore::empty();
+ for cert in config.root_certs {
+ cert.add_to_rustls(&mut root_cert_store)?;
+ }
+
+ #[cfg(feature = "rustls-tls-webpki-roots")]
+ if config.tls_built_in_root_certs {
+ use rustls::OwnedTrustAnchor;
+
+ let trust_anchors =
+ webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|trust_anchor| {
+ OwnedTrustAnchor::from_subject_spki_name_constraints(
+ trust_anchor.subject,
+ trust_anchor.spki,
+ trust_anchor.name_constraints,
+ )
+ });
+
+ root_cert_store.add_server_trust_anchors(trust_anchors);
+ }
+
+ #[cfg(feature = "rustls-tls-native-roots")]
+ if config.tls_built_in_root_certs {
+ let mut valid_count = 0;
+ let mut invalid_count = 0;
+ for cert in rustls_native_certs::load_native_certs()
+ .map_err(crate::error::builder)?
+ {
+ let cert = rustls::Certificate(cert.0);
+ // Continue on parsing errors, as native stores often include ancient or syntactically
+ // invalid certificates, like root certificates without any X509 extensions.
+ // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112
+ match root_cert_store.add(&cert) {
+ Ok(_) => valid_count += 1,
+ Err(err) => {
+ invalid_count += 1;
+ log::warn!(
+ "rustls failed to parse DER certificate {:?} {:?}",
+ &err,
+ &cert
+ );
+ }
+ }
+ }
+ if valid_count == 0 && invalid_count > 0 {
+ return Err(crate::error::builder(
+ "zero valid certificates found in native root store",
+ ));
+ }
+ }
+
+ // Set TLS versions.
+ let mut versions = rustls::ALL_VERSIONS.to_vec();
+
+ if let Some(min_tls_version) = config.min_tls_version {
+ versions.retain(|&supported_version| {
+ match tls::Version::from_rustls(supported_version.version) {
+ Some(version) => version >= min_tls_version,
+ // Assume it's so new we don't know about it, allow it
+ // (as of writing this is unreachable)
+ None => true,
+ }
+ });
+ }
+
+ if let Some(max_tls_version) = config.max_tls_version {
+ versions.retain(|&supported_version| {
+ match tls::Version::from_rustls(supported_version.version) {
+ Some(version) => version <= max_tls_version,
+ None => false,
+ }
+ });
+ }
+
+ // Build TLS config
+ let config_builder = rustls::ClientConfig::builder()
+ .with_safe_default_cipher_suites()
+ .with_safe_default_kx_groups()
+ .with_protocol_versions(&versions)
+ .map_err(crate::error::builder)?
+ .with_root_certificates(root_cert_store);
+
+ // Finalize TLS config
+ let mut tls = if let Some(id) = config.identity {
+ id.add_to_rustls(config_builder)?
+ } else {
+ config_builder.with_no_client_auth()
+ };
+
+ // Certificate verifier
+ if !config.certs_verification {
+ tls.dangerous()
+ .set_certificate_verifier(Arc::new(NoVerifier));
+ }
+
+ tls.enable_sni = config.tls_sni;
+
+ // ALPN protocol
+ match config.http_version_pref {
+ HttpVersionPref::Http1 => {
+ tls.alpn_protocols = vec!["http/1.1".into()];
+ }
+ HttpVersionPref::Http2 => {
+ tls.alpn_protocols = vec!["h2".into()];
+ }
+ #[cfg(feature = "http3")]
+ HttpVersionPref::Http3 => {
+ tls.alpn_protocols = vec!["h3".into()];
+ }
+ HttpVersionPref::All => {
+ tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
+ }
+ }
+
+ #[cfg(feature = "http3")]
+ {
+ tls.enable_early_data = config.tls_enable_early_data;
+
+ let mut transport_config = TransportConfig::default();
+
+ if let Some(max_idle_timeout) = config.quic_max_idle_timeout {
+ transport_config.max_idle_timeout(Some(
+ max_idle_timeout.try_into().map_err(error::builder)?,
+ ));
+ }
+
+ if let Some(stream_receive_window) = config.quic_stream_receive_window {
+ transport_config.stream_receive_window(stream_receive_window);
+ }
+
+ if let Some(receive_window) = config.quic_receive_window {
+ transport_config.receive_window(receive_window);
+ }
+
+ if let Some(send_window) = config.quic_send_window {
+ transport_config.send_window(send_window);
+ }
+
+ h3_connector = Some(H3Connector::new(
+ DynResolver::new(resolver),
+ tls.clone(),
+ config.local_address,
+ transport_config,
+ ));
+ }
+
+ Connector::new_rustls_tls(
+ http,
+ tls,
+ proxies.clone(),
+ user_agent(&config.headers),
+ config.local_address,
+ config.nodelay,
+ )
+ }
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ TlsBackend::UnknownPreconfigured => {
+ return Err(crate::error::builder(
+ "Unknown TLS backend passed to `use_preconfigured_tls`",
+ ));
+ }
+ }
+
+ #[cfg(not(feature = "__tls"))]
+ Connector::new(http, proxies.clone(), config.local_address, config.nodelay)
+ };
+
+ connector.set_timeout(config.connect_timeout);
+ connector.set_verbose(config.connection_verbose);
+
+ let mut builder = hyper::Client::builder();
+ if matches!(config.http_version_pref, HttpVersionPref::Http2) {
+ builder.http2_only(true);
+ }
+
+ if let Some(http2_initial_stream_window_size) = config.http2_initial_stream_window_size {
+ builder.http2_initial_stream_window_size(http2_initial_stream_window_size);
+ }
+ if let Some(http2_initial_connection_window_size) =
+ config.http2_initial_connection_window_size
+ {
+ builder.http2_initial_connection_window_size(http2_initial_connection_window_size);
+ }
+ if config.http2_adaptive_window {
+ builder.http2_adaptive_window(true);
+ }
+ if let Some(http2_max_frame_size) = config.http2_max_frame_size {
+ builder.http2_max_frame_size(http2_max_frame_size);
+ }
+ if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval {
+ builder.http2_keep_alive_interval(http2_keep_alive_interval);
+ }
+ if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout {
+ builder.http2_keep_alive_timeout(http2_keep_alive_timeout);
+ }
+ if config.http2_keep_alive_while_idle {
+ builder.http2_keep_alive_while_idle(true);
+ }
+
+ builder.pool_idle_timeout(config.pool_idle_timeout);
+ builder.pool_max_idle_per_host(config.pool_max_idle_per_host);
+ connector.set_keepalive(config.tcp_keepalive);
+
+ if config.http09_responses {
+ builder.http09_responses(true);
+ }
+
+ if config.http1_title_case_headers {
+ builder.http1_title_case_headers(true);
+ }
+
+ if config.http1_allow_obsolete_multiline_headers_in_responses {
+ builder.http1_allow_obsolete_multiline_headers_in_responses(true);
+ }
+
+ let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth());
+
+ Ok(Client {
+ inner: Arc::new(ClientRef {
+ accepts: config.accepts,
+ #[cfg(feature = "cookies")]
+ cookie_store: config.cookie_store,
+ #[cfg(feature = "http3")]
+ h3_client: H3Client::new(
+ h3_connector.expect("missing HTTP/3 connector"),
+ config.pool_idle_timeout,
+ ),
+ hyper: builder.build(connector),
+ headers: config.headers,
+ redirect_policy: config.redirect_policy,
+ referer: config.referer,
+ request_timeout: config.timeout,
+ proxies,
+ proxies_maybe_http_auth,
+ https_only: config.https_only,
+ }),
+ })
+ }
+
+ // Higher-level options
+
+ /// Sets the `User-Agent` header to be used by this client.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// // Name your user agent after your app?
+ /// static APP_USER_AGENT: &str = concat!(
+ /// env!("CARGO_PKG_NAME"),
+ /// "/",
+ /// env!("CARGO_PKG_VERSION"),
+ /// );
+ ///
+ /// let client = reqwest::Client::builder()
+ /// .user_agent(APP_USER_AGENT)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send().await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
+ where
+ V: TryInto<HeaderValue>,
+ V::Error: Into<http::Error>,
+ {
+ match value.try_into() {
+ Ok(value) => {
+ self.config.headers.insert(USER_AGENT, value);
+ }
+ Err(e) => {
+ self.config.error = Some(crate::error::builder(e.into()));
+ }
+ };
+ self
+ }
+ /// Sets the default headers for every request.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // Consider marking security-sensitive headers with `set_sensitive`.
+ /// let mut auth_value = header::HeaderValue::from_static("secret");
+ /// auth_value.set_sensitive(true);
+ /// headers.insert(header::AUTHORIZATION, auth_value);
+ ///
+ /// // get a client builder
+ /// let client = reqwest::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send().await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Override the default headers:
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # async fn doc() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // get a client builder
+ /// let client = reqwest::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client
+ /// .get("https://www.rust-lang.org")
+ /// .header("X-MY-HEADER", "new_value")
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
+ for (key, value) in headers.iter() {
+ self.config.headers.insert(key, value.clone());
+ }
+ self
+ }
+
+ /// Enable a persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be preserved and included in
+ /// additional requests.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_store(mut self, enable: bool) -> ClientBuilder {
+ if enable {
+ self.cookie_provider(Arc::new(cookie::Jar::default()))
+ } else {
+ self.config.cookie_store = None;
+ self
+ }
+ }
+
+ /// Set the persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be passed to this store, and
+ /// additional requests will query this store for cookies.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_provider<C: cookie::CookieStore + 'static>(
+ mut self,
+ cookie_store: Arc<C>,
+ ) -> ClientBuilder {
+ self.config.cookie_store = Some(cookie_store as _);
+ self
+ }
+
+ /// Enable auto gzip decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto gzip decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if its headers contain a `Content-Encoding` value of
+ /// `gzip`, both `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `gzip` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `gzip` feature to be enabled
+ #[cfg(feature = "gzip")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
+ pub fn gzip(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.gzip = enable;
+ self
+ }
+
+ /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto brotli decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if its headers contain a `Content-Encoding` value of
+ /// `br`, both `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `brotli` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `brotli` feature to be enabled
+ #[cfg(feature = "brotli")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
+ pub fn brotli(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.brotli = enable;
+ self
+ }
+
+ /// Enable auto deflate decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto deflate decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `deflate` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `deflate` feature to be enabled
+ #[cfg(feature = "deflate")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))]
+ pub fn deflate(mut self, enable: bool) -> ClientBuilder {
+ self.config.accepts.deflate = enable;
+ self
+ }
+
+ /// Disable auto response body gzip decompression.
+ ///
+ /// This method exists even if the optional `gzip` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use gzip decompression
+ /// even if another dependency were to enable the optional `gzip` feature.
+ pub fn no_gzip(self) -> ClientBuilder {
+ #[cfg(feature = "gzip")]
+ {
+ self.gzip(false)
+ }
+
+ #[cfg(not(feature = "gzip"))]
+ {
+ self
+ }
+ }
+
+ /// Disable auto response body brotli decompression.
+ ///
+ /// This method exists even if the optional `brotli` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use brotli decompression
+ /// even if another dependency were to enable the optional `brotli` feature.
+ pub fn no_brotli(self) -> ClientBuilder {
+ #[cfg(feature = "brotli")]
+ {
+ self.brotli(false)
+ }
+
+ #[cfg(not(feature = "brotli"))]
+ {
+ self
+ }
+ }
+
+ /// Disable auto response body deflate decompression.
+ ///
+ /// This method exists even if the optional `deflate` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use deflate decompression
+ /// even if another dependency were to enable the optional `deflate` feature.
+ pub fn no_deflate(self) -> ClientBuilder {
+ #[cfg(feature = "deflate")]
+ {
+ self.deflate(false)
+ }
+
+ #[cfg(not(feature = "deflate"))]
+ {
+ self
+ }
+ }
+
+ // Redirect options
+
+ /// Set a `RedirectPolicy` for this client.
+ ///
+ /// Default will follow redirects up to a maximum of 10.
+ pub fn redirect(mut self, policy: redirect::Policy) -> ClientBuilder {
+ self.config.redirect_policy = policy;
+ self
+ }
+
+ /// Enable or disable automatic setting of the `Referer` header.
+ ///
+ /// Default is `true`.
+ pub fn referer(mut self, enable: bool) -> ClientBuilder {
+ self.config.referer = enable;
+ self
+ }
+
+ // Proxy options
+
+ /// Add a `Proxy` to the list of proxies the `Client` will use.
+ ///
+ /// # Note
+ ///
+ /// Adding a proxy will disable the automatic usage of the "system" proxy.
+ pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder {
+ self.config.proxies.push(proxy);
+ self.config.auto_sys_proxy = false;
+ self
+ }
+
+ /// Clear all `Proxies`, so `Client` will use no proxy anymore.
+ ///
+ /// # Note
+ /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()]
+ /// on all desired proxies instead.
+ ///
+ /// This also disables the automatic usage of the "system" proxy.
+ pub fn no_proxy(mut self) -> ClientBuilder {
+ self.config.proxies.clear();
+ self.config.auto_sys_proxy = false;
+ self
+ }
+
+ // Timeout options
+
+ /// Enables a request timeout.
+ ///
+ /// The timeout is applied from when the request starts connecting until the
+ /// response body has finished.
+ ///
+ /// Default is no timeout.
+ pub fn timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.timeout = Some(timeout);
+ self
+ }
+
+ /// Set a timeout for only the connect phase of a `Client`.
+ ///
+ /// Default is `None`.
+ ///
+ /// # Note
+ ///
+ /// This **requires** the futures be executed in a tokio runtime with
+ /// a tokio timer enabled.
+ pub fn connect_timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.connect_timeout = Some(timeout);
+ self
+ }
+
+ /// Set whether connections should emit verbose logs.
+ ///
+ /// Enabling this option will emit [log][] messages at the `TRACE` level
+ /// for read and write operations on connections.
+ ///
+ /// [log]: https://crates.io/crates/log
+ pub fn connection_verbose(mut self, verbose: bool) -> ClientBuilder {
+ self.config.connection_verbose = verbose;
+ self
+ }
+
+ // HTTP options
+
+ /// Set an optional timeout for idle sockets being kept-alive.
+ ///
+ /// Pass `None` to disable timeout.
+ ///
+ /// Default is 90 seconds.
+ pub fn pool_idle_timeout<D>(mut self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.config.pool_idle_timeout = val.into();
+ self
+ }
+
+ /// Sets the maximum idle connection per host allowed in the pool.
+ pub fn pool_max_idle_per_host(mut self, max: usize) -> ClientBuilder {
+ self.config.pool_max_idle_per_host = max;
+ self
+ }
+
+ /// Send headers as title case instead of lowercase.
+ pub fn http1_title_case_headers(mut self) -> ClientBuilder {
+ self.config.http1_title_case_headers = true;
+ self
+ }
+
+ /// Set whether HTTP/1 connections will accept obsolete line folding for
+ /// header values.
+ ///
+ /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
+ /// parsing.
+ pub fn http1_allow_obsolete_multiline_headers_in_responses(
+ mut self,
+ value: bool,
+ ) -> ClientBuilder {
+ self.config
+ .http1_allow_obsolete_multiline_headers_in_responses = value;
+ self
+ }
+
+ /// Only use HTTP/1.
+ pub fn http1_only(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http1;
+ self
+ }
+
+ /// Allow HTTP/0.9 responses
+ pub fn http09_responses(mut self) -> ClientBuilder {
+ self.config.http09_responses = true;
+ self
+ }
+
+ /// Only use HTTP/2.
+ pub fn http2_prior_knowledge(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http2;
+ self
+ }
+
+ /// Only use HTTP/3.
+ #[cfg(feature = "http3")]
+ pub fn http3_prior_knowledge(mut self) -> ClientBuilder {
+ self.config.http_version_pref = HttpVersionPref::Http3;
+ self
+ }
+
+ /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control.
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.config.http2_initial_stream_window_size = sz.into();
+ self
+ }
+
+ /// Sets the max connection-level flow control for HTTP2
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_connection_window_size(
+ mut self,
+ sz: impl Into<Option<u32>>,
+ ) -> ClientBuilder {
+ self.config.http2_initial_connection_window_size = sz.into();
+ self
+ }
+
+ /// Sets whether to use an adaptive flow control.
+ ///
+ /// Enabling this will override the limits set in `http2_initial_stream_window_size` and
+ /// `http2_initial_connection_window_size`.
+ pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder {
+ self.config.http2_adaptive_window = enabled;
+ self
+ }
+
+ /// Sets the maximum frame size to use for HTTP2.
+ ///
+ /// Default is currently 16,384 but may change internally to optimize for common uses.
+ pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.config.http2_max_frame_size = sz.into();
+ self
+ }
+
+ /// Sets an interval for HTTP2 Ping frames should be sent to keep a connection alive.
+ ///
+ /// Pass `None` to disable HTTP2 keep-alive.
+ /// Default is currently disabled.
+ pub fn http2_keep_alive_interval(
+ mut self,
+ interval: impl Into<Option<Duration>>,
+ ) -> ClientBuilder {
+ self.config.http2_keep_alive_interval = interval.into();
+ self
+ }
+
+ /// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
+ ///
+ /// If the ping is not acknowledged within the timeout, the connection will be closed.
+ /// Does nothing if `http2_keep_alive_interval` is disabled.
+ /// Default is currently disabled.
+ pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder {
+ self.config.http2_keep_alive_timeout = Some(timeout);
+ self
+ }
+
+ /// Sets whether HTTP2 keep-alive should apply while the connection is idle.
+ ///
+ /// If disabled, keep-alive pings are only sent while there are open request/responses streams.
+ /// If enabled, pings are also sent when no streams are active.
+ /// Does nothing if `http2_keep_alive_interval` is disabled.
+ /// Default is `false`.
+ pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder {
+ self.config.http2_keep_alive_while_idle = enabled;
+ self
+ }
+
+ // TCP options
+
+ /// Set whether sockets have `TCP_NODELAY` enabled.
+ ///
+ /// Default is `true`.
+ pub fn tcp_nodelay(mut self, enabled: bool) -> ClientBuilder {
+ self.config.nodelay = enabled;
+ self
+ }
+
+ /// Bind to a local IP Address.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::net::IpAddr;
+ /// let local_addr = IpAddr::from([12, 4, 1, 8]);
+ /// let client = reqwest::Client::builder()
+ /// .local_address(local_addr)
+ /// .build().unwrap();
+ /// ```
+ pub fn local_address<T>(mut self, addr: T) -> ClientBuilder
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ self.config.local_address = addr.into();
+ self
+ }
+
+ /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
+ ///
+ /// If `None`, the option will not be set.
+ pub fn tcp_keepalive<D>(mut self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.config.tcp_keepalive = val.into();
+ self
+ }
+
+ // TLS options
+
+ /// Add a custom root certificate.
+ ///
+ /// This can be used to connect to a server that has a self-signed
+ /// certificate for example.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder {
+ self.config.root_certs.push(cert);
+ self
+ }
+
+ /// Controls the use of built-in/preloaded certificates during certificate validation.
+ ///
+ /// Defaults to `true` -- built-in system certs will be used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_built_in_root_certs(mut self, tls_built_in_root_certs: bool) -> ClientBuilder {
+ self.config.tls_built_in_root_certs = tls_built_in_root_certs;
+ self
+ }
+
+ /// Sets the identity to be used for client certificate authentication.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be
+ /// enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn identity(mut self, identity: Identity) -> ClientBuilder {
+ self.config.identity = Some(identity);
+ self
+ }
+
+ /// Controls the use of hostname verification.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before you use this method. If
+ /// hostname verification is not used, any valid certificate for any
+ /// site will be trusted for use from any other. This introduces a
+ /// significant vulnerability to man-in-the-middle attacks.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn danger_accept_invalid_hostnames(
+ mut self,
+ accept_invalid_hostname: bool,
+ ) -> ClientBuilder {
+ self.config.hostname_verification = !accept_invalid_hostname;
+ self
+ }
+
+ /// Controls the use of certificate validation.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before using this method. If
+ /// invalid certificates are trusted, *any* certificate for *any* site
+ /// will be trusted for use. This includes expired certificates. This
+ /// introduces significant vulnerabilities, and should only be used
+ /// as a last resort.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder {
+ self.config.certs_verification = !accept_invalid_certs;
+ self
+ }
+
+ /// Controls the use of TLS server name indication.
+ ///
+ /// Defaults to `true`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_sni(mut self, tls_sni: bool) -> ClientBuilder {
+ self.config.tls_sni = tls_sni;
+ self
+ }
+
+ /// Set the minimum required TLS version for connections.
+ ///
+ /// By default the TLS backend's own default is used.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a minimum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn min_tls_version(mut self, version: tls::Version) -> ClientBuilder {
+ self.config.min_tls_version = Some(version);
+ self
+ }
+
+ /// Set the maximum allowed TLS version for connections.
+ ///
+ /// By default there's no maximum.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a maximum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn max_tls_version(mut self, version: tls::Version) -> ClientBuilder {
+ self.config.max_tls_version = Some(version);
+ self
+ }
+
+ /// Force using the native TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `native-tls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn use_native_tls(mut self) -> ClientBuilder {
+ self.config.tls = TlsBackend::Default;
+ self
+ }
+
+ /// Force using the Rustls TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `rustls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `rustls-tls(-...)` feature to be enabled.
+ #[cfg(feature = "__rustls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
+ pub fn use_rustls_tls(mut self) -> ClientBuilder {
+ self.config.tls = TlsBackend::Rustls;
+ self
+ }
+
+ /// Use a preconfigured TLS backend.
+ ///
+ /// If the passed `Any` argument is not a TLS backend that reqwest
+ /// understands, the `ClientBuilder` will error when calling `build`.
+ ///
+ /// # Advanced
+ ///
+ /// This is an advanced option, and can be somewhat brittle. Usage requires
+ /// keeping the preconfigured TLS argument version in sync with reqwest,
+ /// since version mismatches will result in an "unknown" TLS backend.
+ ///
+ /// If possible, it's preferable to use the methods on `ClientBuilder`
+ /// to configure reqwest's TLS.
+ ///
+ /// # Optional
+ ///
+ /// This requires one of the optional features `native-tls` or
+ /// `rustls-tls(-...)` to be enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn use_preconfigured_tls(mut self, tls: impl Any) -> ClientBuilder {
+ let mut tls = Some(tls);
+ #[cfg(feature = "native-tls")]
+ {
+ if let Some(conn) =
+ (&mut tls as &mut dyn Any).downcast_mut::<Option<native_tls_crate::TlsConnector>>()
+ {
+ let tls = conn.take().expect("is definitely Some");
+ let tls = crate::tls::TlsBackend::BuiltNativeTls(tls);
+ self.config.tls = tls;
+ return self;
+ }
+ }
+ #[cfg(feature = "__rustls")]
+ {
+ if let Some(conn) =
+ (&mut tls as &mut dyn Any).downcast_mut::<Option<rustls::ClientConfig>>()
+ {
+ let tls = conn.take().expect("is definitely Some");
+ let tls = crate::tls::TlsBackend::BuiltRustls(tls);
+ self.config.tls = tls;
+ return self;
+ }
+ }
+
+ // Otherwise, we don't recognize the TLS backend!
+ self.config.tls = crate::tls::TlsBackend::UnknownPreconfigured;
+ self
+ }
+
+ /// Enables the [trust-dns](trust_dns_resolver) async resolver instead of a default threadpool using `getaddrinfo`.
+ ///
+ /// If the `trust-dns` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `trust-dns` feature to be enabled
+ #[cfg(feature = "trust-dns")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "trust-dns")))]
+ pub fn trust_dns(mut self, enable: bool) -> ClientBuilder {
+ self.config.trust_dns = enable;
+ self
+ }
+
+ /// Disables the trust-dns async resolver.
+ ///
+ /// This method exists even if the optional `trust-dns` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use the trust-dns async resolver
+ /// even if another dependency were to enable the optional `trust-dns` feature.
+ pub fn no_trust_dns(self) -> ClientBuilder {
+ #[cfg(feature = "trust-dns")]
+ {
+ self.trust_dns(false)
+ }
+
+ #[cfg(not(feature = "trust-dns"))]
+ {
+ self
+ }
+ }
+
+ /// Restrict the Client to be used with HTTPS only requests.
+ ///
+ /// Defaults to false.
+ pub fn https_only(mut self, enabled: bool) -> ClientBuilder {
+ self.config.https_only = enabled;
+ self
+ }
+
+ /// Override DNS resolution for specific domains to a particular IP address.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addr will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder {
+ self.resolve_to_addrs(domain, &[addr])
+ }
+
+ /// Override DNS resolution for specific domains to particular IP addresses.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addresses will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve_to_addrs(mut self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder {
+ self.config
+ .dns_overrides
+ .insert(domain.to_string(), addrs.to_vec());
+ self
+ }
+
+ /// Override the DNS resolver implementation.
+ ///
+ /// Pass an `Arc` wrapping a trait object implementing `Resolve`.
+ /// Overrides for specific names passed to `resolve` and `resolve_to_addrs` will
+ /// still be applied on top of this resolver.
+ pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> ClientBuilder {
+ self.config.dns_resolver = Some(resolver as _);
+ self
+ }
+
+ /// Whether to send data on the first flight ("early data") in TLS 1.3 handshakes
+ /// for HTTP/3 connections.
+ ///
+ /// The default is false.
+ #[cfg(feature = "http3")]
+ pub fn set_tls_enable_early_data(mut self, enabled: bool) -> ClientBuilder {
+ self.config.tls_enable_early_data = enabled;
+ self
+ }
+
+ /// Maximum duration of inactivity to accept before timing out the QUIC connection.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_max_idle_timeout(mut self, value: Duration) -> ClientBuilder {
+ self.config.quic_max_idle_timeout = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
+ /// before becoming blocked.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_stream_receive_window(mut self, value: VarInt) -> ClientBuilder {
+ self.config.quic_stream_receive_window = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes the peer may transmit across all streams of a connection before
+ /// becoming blocked.
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_receive_window(mut self, value: VarInt) -> ClientBuilder {
+ self.config.quic_receive_window = Some(value);
+ self
+ }
+
+ /// Maximum number of bytes to transmit to a peer without acknowledgment
+ ///
+ /// Please see docs in [`TransportConfig`] in [`quinn`].
+ ///
+ /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html
+ #[cfg(feature = "http3")]
+ pub fn set_quic_send_window(mut self, value: u64) -> ClientBuilder {
+ self.config.quic_send_window = Some(value);
+ self
+ }
+}
+
+type HyperClient = hyper::Client<Connector, super::body::ImplStream>;
+
+impl Default for Client {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Client {
+ /// Constructs a new `Client`.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if a TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// 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()")
+ }
+
+ /// Creates a `ClientBuilder` to configure a `Client`.
+ ///
+ /// This is the same as `ClientBuilder::new()`.
+ pub fn builder() -> ClientBuilder {
+ ClientBuilder::new()
+ }
+
+ /// Convenience method to make a `GET` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::GET, url)
+ }
+
+ /// Convenience method to make a `POST` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::POST, url)
+ }
+
+ /// Convenience method to make a `PUT` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PUT, url)
+ }
+
+ /// Convenience method to make a `PATCH` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PATCH, url)
+ }
+
+ /// Convenience method to make a `DELETE` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::DELETE, url)
+ }
+
+ /// Convenience method to make a `HEAD` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the supplied `Url` cannot be parsed.
+ pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::HEAD, url)
+ }
+
+ /// Start building a `Request` with the `Method` and `Url`.
+ ///
+ /// Returns a `RequestBuilder`, which will allow setting headers and
+ /// the request body before sending.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the 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));
+ RequestBuilder::new(self.clone(), req)
+ }
+
+ /// Executes a `Request`.
+ ///
+ /// A `Request` can be built manually with `Request::new()` or obtained
+ /// from a RequestBuilder with `RequestBuilder::build()`.
+ ///
+ /// You should prefer to use the `RequestBuilder` and
+ /// `RequestBuilder::send()`.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ pub fn execute(
+ &self,
+ request: Request,
+ ) -> impl Future<Output = Result<Response, crate::Error>> {
+ self.execute_request(request)
+ }
+
+ pub(super) fn execute_request(&self, req: Request) -> Pending {
+ let (method, url, mut headers, body, timeout, version) = req.pieces();
+ if url.scheme() != "http" && url.scheme() != "https" {
+ return Pending::new_err(error::url_bad_scheme(url));
+ }
+
+ // check if we're in https_only mode and check the scheme of the current URL
+ if self.inner.https_only && url.scheme() != "https" {
+ return Pending::new_err(error::url_bad_scheme(url));
+ }
+
+ // insert default headers in the request headers
+ // without overwriting already appended headers.
+ for (key, value) in &self.inner.headers {
+ if let Entry::Vacant(entry) = headers.entry(key) {
+ entry.insert(value.clone());
+ }
+ }
+
+ // Add cookies from the cookie store.
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(cookie_store) = self.inner.cookie_store.as_ref() {
+ if headers.get(crate::header::COOKIE).is_none() {
+ add_cookie_header(&mut headers, &**cookie_store, &url);
+ }
+ }
+ }
+
+ let accept_encoding = self.inner.accepts.as_str();
+
+ if let Some(accept_encoding) = accept_encoding {
+ if !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) {
+ headers.insert(ACCEPT_ENCODING, HeaderValue::from_static(accept_encoding));
+ }
+ }
+
+ let uri = expect_uri(&url);
+
+ let (reusable, body) = match body {
+ Some(body) => {
+ let (reusable, body) = body.try_reuse();
+ (Some(reusable), body)
+ }
+ None => (None, Body::empty()),
+ };
+
+ self.proxy_auth(&uri, &mut headers);
+
+ let builder = hyper::Request::builder()
+ .method(method.clone())
+ .uri(uri)
+ .version(version);
+
+ let in_flight = match version {
+ #[cfg(feature = "http3")]
+ http::Version::HTTP_3 => {
+ let mut req = builder.body(body).expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ ResponseFuture::H3(self.inner.h3_client.request(req))
+ }
+ _ => {
+ let mut req = builder
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ ResponseFuture::Default(self.inner.hyper.request(req))
+ }
+ };
+
+ let timeout = timeout
+ .or(self.inner.request_timeout)
+ .map(tokio::time::sleep)
+ .map(Box::pin);
+
+ Pending {
+ inner: PendingInner::Request(PendingRequest {
+ method,
+ url,
+ headers,
+ body: reusable,
+
+ urls: Vec::new(),
+
+ retry_count: 0,
+
+ client: self.inner.clone(),
+
+ in_flight,
+ timeout,
+ }),
+ }
+ }
+
+ fn proxy_auth(&self, dst: &Uri, headers: &mut HeaderMap) {
+ if !self.inner.proxies_maybe_http_auth {
+ return;
+ }
+
+ // Only set the header here if the destination scheme is 'http',
+ // since otherwise, the header will be included in the CONNECT tunnel
+ // request instead.
+ if dst.scheme() != Some(&Scheme::HTTP) {
+ return;
+ }
+
+ if headers.contains_key(PROXY_AUTHORIZATION) {
+ 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);
+ }
+
+ break;
+ }
+ }
+ }
+}
+
+impl fmt::Debug for Client {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("Client");
+ self.inner.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+impl tower_service::Service<Request> for Client {
+ type Response = Response;
+ type Error = crate::Error;
+ type Future = Pending;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ self.execute_request(req)
+ }
+}
+
+impl tower_service::Service<Request> for &'_ Client {
+ type Response = Response;
+ type Error = crate::Error;
+ type Future = Pending;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, req: Request) -> Self::Future {
+ self.execute_request(req)
+ }
+}
+
+impl fmt::Debug for ClientBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("ClientBuilder");
+ self.config.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+impl Config {
+ fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
+ // Instead of deriving Debug, only print fields when their output
+ // would provide relevant or interesting data.
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(_) = self.cookie_store {
+ f.field("cookie_store", &true);
+ }
+ }
+
+ f.field("accepts", &self.accepts);
+
+ if !self.proxies.is_empty() {
+ f.field("proxies", &self.proxies);
+ }
+
+ if !self.redirect_policy.is_default() {
+ f.field("redirect_policy", &self.redirect_policy);
+ }
+
+ if self.referer {
+ f.field("referer", &true);
+ }
+
+ f.field("default_headers", &self.headers);
+
+ if self.http1_title_case_headers {
+ f.field("http1_title_case_headers", &true);
+ }
+
+ if self.http1_allow_obsolete_multiline_headers_in_responses {
+ f.field("http1_allow_obsolete_multiline_headers_in_responses", &true);
+ }
+
+ if matches!(self.http_version_pref, HttpVersionPref::Http1) {
+ f.field("http1_only", &true);
+ }
+
+ if matches!(self.http_version_pref, HttpVersionPref::Http2) {
+ f.field("http2_prior_knowledge", &true);
+ }
+
+ if let Some(ref d) = self.connect_timeout {
+ f.field("connect_timeout", d);
+ }
+
+ if let Some(ref d) = self.timeout {
+ f.field("timeout", d);
+ }
+
+ if let Some(ref v) = self.local_address {
+ f.field("local_address", v);
+ }
+
+ if self.nodelay {
+ f.field("tcp_nodelay", &true);
+ }
+
+ #[cfg(feature = "native-tls")]
+ {
+ if !self.hostname_verification {
+ f.field("danger_accept_invalid_hostnames", &true);
+ }
+ }
+
+ #[cfg(feature = "__tls")]
+ {
+ if !self.certs_verification {
+ f.field("danger_accept_invalid_certs", &true);
+ }
+
+ if let Some(ref min_tls_version) = self.min_tls_version {
+ f.field("min_tls_version", min_tls_version);
+ }
+
+ if let Some(ref max_tls_version) = self.max_tls_version {
+ f.field("max_tls_version", max_tls_version);
+ }
+
+ f.field("tls_sni", &self.tls_sni);
+ }
+
+ #[cfg(all(feature = "native-tls-crate", feature = "__rustls"))]
+ {
+ f.field("tls_backend", &self.tls);
+ }
+
+ if !self.dns_overrides.is_empty() {
+ f.field("dns_overrides", &self.dns_overrides);
+ }
+
+ #[cfg(feature = "http3")]
+ {
+ if self.tls_enable_early_data {
+ f.field("tls_enable_early_data", &true);
+ }
+ }
+ }
+}
+
+struct ClientRef {
+ accepts: Accepts,
+ #[cfg(feature = "cookies")]
+ cookie_store: Option<Arc<dyn cookie::CookieStore>>,
+ headers: HeaderMap,
+ hyper: HyperClient,
+ #[cfg(feature = "http3")]
+ h3_client: H3Client,
+ redirect_policy: redirect::Policy,
+ referer: bool,
+ request_timeout: Option<Duration>,
+ proxies: Arc<Vec<Proxy>>,
+ proxies_maybe_http_auth: bool,
+ https_only: bool,
+}
+
+impl ClientRef {
+ fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
+ // Instead of deriving Debug, only print fields when their output
+ // would provide relevant or interesting data.
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(_) = self.cookie_store {
+ f.field("cookie_store", &true);
+ }
+ }
+
+ f.field("accepts", &self.accepts);
+
+ if !self.proxies.is_empty() {
+ f.field("proxies", &self.proxies);
+ }
+
+ if !self.redirect_policy.is_default() {
+ f.field("redirect_policy", &self.redirect_policy);
+ }
+
+ if self.referer {
+ f.field("referer", &true);
+ }
+
+ f.field("default_headers", &self.headers);
+
+ if let Some(ref d) = self.request_timeout {
+ f.field("timeout", d);
+ }
+ }
+}
+
+pin_project! {
+ pub struct Pending {
+ #[pin]
+ inner: PendingInner,
+ }
+}
+
+enum PendingInner {
+ Request(PendingRequest),
+ Error(Option<crate::Error>),
+}
+
+pin_project! {
+ struct PendingRequest {
+ method: Method,
+ url: Url,
+ headers: HeaderMap,
+ body: Option<Option<Bytes>>,
+
+ urls: Vec<Url>,
+
+ retry_count: usize,
+
+ client: Arc<ClientRef>,
+
+ #[pin]
+ in_flight: ResponseFuture,
+ #[pin]
+ timeout: Option<Pin<Box<Sleep>>>,
+ }
+}
+
+enum ResponseFuture {
+ Default(HyperResponseFuture),
+ #[cfg(feature = "http3")]
+ H3(H3ResponseFuture),
+}
+
+impl PendingRequest {
+ fn in_flight(self: Pin<&mut Self>) -> Pin<&mut ResponseFuture> {
+ self.project().in_flight
+ }
+
+ fn timeout(self: Pin<&mut Self>) -> Pin<&mut Option<Pin<Box<Sleep>>>> {
+ self.project().timeout
+ }
+
+ fn urls(self: Pin<&mut Self>) -> &mut Vec<Url> {
+ self.project().urls
+ }
+
+ fn headers(self: Pin<&mut Self>) -> &mut HeaderMap {
+ self.project().headers
+ }
+
+ fn retry_error(mut self: Pin<&mut Self>, err: &(dyn std::error::Error + 'static)) -> bool {
+ if !is_retryable_error(err) {
+ return false;
+ }
+
+ trace!("can retry {:?}", err);
+
+ let body = match self.body {
+ Some(Some(ref body)) => Body::reusable(body.clone()),
+ Some(None) => {
+ debug!("error was retryable, but body not reusable");
+ return false;
+ }
+ None => Body::empty(),
+ };
+
+ if self.retry_count >= 2 {
+ trace!("retry count too high");
+ return false;
+ }
+ self.retry_count += 1;
+
+ let uri = expect_uri(&self.url);
+
+ *self.as_mut().in_flight().get_mut() = match *self.as_mut().in_flight().as_ref() {
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(_) => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri)
+ .body(body)
+ .expect("valid request parts");
+ *req.headers_mut() = self.headers.clone();
+ ResponseFuture::H3(self.client.h3_client.request(req))
+ }
+ _ => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri)
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = self.headers.clone();
+ ResponseFuture::Default(self.client.hyper.request(req))
+ }
+ };
+
+ true
+ }
+}
+
+fn is_retryable_error(err: &(dyn std::error::Error + 'static)) -> bool {
+ #[cfg(feature = "http3")]
+ if let Some(cause) = err.source() {
+ if let Some(err) = cause.downcast_ref::<h3::Error>() {
+ debug!("determining if HTTP/3 error {} can be retried", err);
+ // TODO: Does h3 provide an API for checking the error?
+ return err.to_string().as_str() == "timeout";
+ }
+ }
+
+ if let Some(cause) = err.source() {
+ if let Some(err) = cause.downcast_ref::<h2::Error>() {
+ // They sent us a graceful shutdown, try with a new connection!
+ return err.is_go_away()
+ && err.is_remote()
+ && err.reason() == Some(h2::Reason::NO_ERROR);
+ }
+ }
+ false
+}
+
+impl Pending {
+ pub(super) fn new_err(err: crate::Error) -> Pending {
+ Pending {
+ inner: PendingInner::Error(Some(err)),
+ }
+ }
+
+ fn inner(self: Pin<&mut Self>) -> Pin<&mut PendingInner> {
+ self.project().inner
+ }
+}
+
+impl Future for Pending {
+ type Output = Result<Response, crate::Error>;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ let inner = self.inner();
+ match inner.get_mut() {
+ PendingInner::Request(ref mut req) => Pin::new(req).poll(cx),
+ PendingInner::Error(ref mut err) => Poll::Ready(Err(err
+ .take()
+ .expect("Pending error polled more than once"))),
+ }
+ }
+}
+
+impl Future for PendingRequest {
+ type Output = Result<Response, crate::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ if let Some(delay) = self.as_mut().timeout().as_mut().as_pin_mut() {
+ if let Poll::Ready(()) = delay.poll(cx) {
+ return Poll::Ready(Err(
+ crate::error::request(crate::error::TimedOut).with_url(self.url.clone())
+ ));
+ }
+ }
+
+ loop {
+ let res = match self.as_mut().in_flight().get_mut() {
+ ResponseFuture::Default(r) => match Pin::new(r).poll(cx) {
+ Poll::Ready(Err(e)) => {
+ if self.as_mut().retry_error(&e) {
+ continue;
+ }
+ return Poll::Ready(Err(
+ crate::error::request(e).with_url(self.url.clone())
+ ));
+ }
+ Poll::Ready(Ok(res)) => res,
+ Poll::Pending => return Poll::Pending,
+ },
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(r) => match Pin::new(r).poll(cx) {
+ Poll::Ready(Err(e)) => {
+ if self.as_mut().retry_error(&e) {
+ continue;
+ }
+ return Poll::Ready(Err(
+ crate::error::request(e).with_url(self.url.clone())
+ ));
+ }
+ Poll::Ready(Ok(res)) => res,
+ Poll::Pending => return Poll::Pending,
+ },
+ };
+
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(ref cookie_store) = self.client.cookie_store {
+ let mut cookies =
+ cookie::extract_response_cookie_headers(&res.headers()).peekable();
+ if cookies.peek().is_some() {
+ cookie_store.set_cookies(&mut cookies, &self.url);
+ }
+ }
+ }
+ let should_redirect = match res.status() {
+ StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
+ self.body = None;
+ for header in &[
+ TRANSFER_ENCODING,
+ CONTENT_ENCODING,
+ CONTENT_TYPE,
+ CONTENT_LENGTH,
+ ] {
+ self.headers.remove(header);
+ }
+
+ match self.method {
+ 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,
+ }
+ }
+ _ => 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()
+ })();
+
+ // 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);
+ }
+ loc
+ });
+ if let Some(loc) = loc {
+ if self.client.referer {
+ if let Some(referer) = make_referer(&loc, &self.url) {
+ self.headers.insert(REFERER, referer);
+ }
+ }
+ let url = self.url.clone();
+ self.as_mut().urls().push(url);
+ let action = self
+ .client
+ .redirect_policy
+ .check(res.status(), &loc, &self.urls);
+
+ match action {
+ redirect::ActionKind::Follow => {
+ debug!("redirecting '{}' to '{}'", self.url, loc);
+
+ if self.client.https_only && loc.scheme() != "https" {
+ return Poll::Ready(Err(error::redirect(
+ error::url_bad_scheme(loc.clone()),
+ loc,
+ )));
+ }
+
+ self.url = loc;
+ let mut headers =
+ std::mem::replace(self.as_mut().headers(), HeaderMap::new());
+
+ remove_sensitive_headers(&mut headers, &self.url, &self.urls);
+ let uri = expect_uri(&self.url);
+ let body = match self.body {
+ Some(Some(ref body)) => Body::reusable(body.clone()),
+ _ => Body::empty(),
+ };
+
+ // Add cookies from the cookie store.
+ #[cfg(feature = "cookies")]
+ {
+ if let Some(ref cookie_store) = self.client.cookie_store {
+ add_cookie_header(&mut headers, &**cookie_store, &self.url);
+ }
+ }
+
+ *self.as_mut().in_flight().get_mut() =
+ match *self.as_mut().in_flight().as_ref() {
+ #[cfg(feature = "http3")]
+ ResponseFuture::H3(_) => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri.clone())
+ .body(body)
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ std::mem::swap(self.as_mut().headers(), &mut headers);
+ ResponseFuture::H3(self.client.h3_client.request(req))
+ }
+ _ => {
+ let mut req = hyper::Request::builder()
+ .method(self.method.clone())
+ .uri(uri.clone())
+ .body(body.into_stream())
+ .expect("valid request parts");
+ *req.headers_mut() = headers.clone();
+ std::mem::swap(self.as_mut().headers(), &mut headers);
+ ResponseFuture::Default(self.client.hyper.request(req))
+ }
+ };
+
+ continue;
+ }
+ redirect::ActionKind::Stop => {
+ debug!("redirect policy disallowed redirection to '{}'", loc);
+ }
+ redirect::ActionKind::Error(err) => {
+ return Poll::Ready(Err(crate::error::redirect(err, self.url.clone())));
+ }
+ }
+ }
+ }
+
+ let res = Response::new(
+ res,
+ self.url.clone(),
+ self.client.accepts,
+ self.timeout.take(),
+ );
+ return Poll::Ready(Ok(res));
+ }
+ }
+}
+
+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(),
+ }
+ }
+}
+
+fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
+ if next.scheme() == "http" && previous.scheme() == "https" {
+ return None;
+ }
+
+ let mut referer = previous.clone();
+ let _ = referer.set_username("");
+ let _ = referer.set_password(None);
+ referer.set_fragment(None);
+ referer.as_str().parse().ok()
+}
+
+#[cfg(feature = "cookies")]
+fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieStore, url: &Url) {
+ if let Some(header) = cookie_store.cookies(url) {
+ headers.insert(crate::header::COOKIE, header);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[tokio::test]
+ async fn execute_request_rejects_invald_urls() {
+ let url_str = "hxxps://www.rust-lang.org/";
+ let url = url::Url::parse(url_str).unwrap();
+ let result = crate::get(url.clone()).await;
+
+ assert!(result.is_err());
+ let err = result.err().unwrap();
+ assert!(err.is_builder());
+ assert_eq!(url_str, err.url().unwrap().as_str());
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/decoder.rs b/vendor/reqwest/src/async_impl/decoder.rs
new file mode 100644
index 000000000..c0542cfb1
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/decoder.rs
@@ -0,0 +1,421 @@
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+#[cfg(feature = "gzip")]
+use async_compression::tokio::bufread::GzipDecoder;
+
+#[cfg(feature = "brotli")]
+use async_compression::tokio::bufread::BrotliDecoder;
+
+#[cfg(feature = "deflate")]
+use async_compression::tokio::bufread::ZlibDecoder;
+
+use bytes::Bytes;
+use futures_core::Stream;
+use futures_util::stream::Peekable;
+use http::HeaderMap;
+use hyper::body::HttpBody;
+
+#[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))]
+use tokio_util::codec::{BytesCodec, FramedRead};
+#[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))]
+use tokio_util::io::StreamReader;
+
+use super::super::Body;
+use crate::error;
+
+#[derive(Clone, Copy, Debug)]
+pub(super) struct Accepts {
+ #[cfg(feature = "gzip")]
+ pub(super) gzip: bool,
+ #[cfg(feature = "brotli")]
+ pub(super) brotli: bool,
+ #[cfg(feature = "deflate")]
+ pub(super) deflate: bool,
+}
+
+/// A response decompressor over a non-blocking stream of chunks.
+///
+/// The inner decoder may be constructed asynchronously.
+pub(crate) struct Decoder {
+ inner: Inner,
+}
+
+type PeekableIoStream = Peekable<IoStream>;
+
+#[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))]
+type PeekableIoStreamReader = StreamReader<PeekableIoStream, Bytes>;
+
+enum Inner {
+ /// A `PlainText` decoder just returns the response content as is.
+ PlainText(super::body::ImplStream),
+
+ /// A `Gzip` decoder will uncompress the gzipped response content before returning it.
+ #[cfg(feature = "gzip")]
+ Gzip(Pin<Box<FramedRead<GzipDecoder<PeekableIoStreamReader>, BytesCodec>>>),
+
+ /// A `Brotli` decoder will uncompress the brotlied response content before returning it.
+ #[cfg(feature = "brotli")]
+ Brotli(Pin<Box<FramedRead<BrotliDecoder<PeekableIoStreamReader>, BytesCodec>>>),
+
+ /// A `Deflate` decoder will uncompress the deflated response content before returning it.
+ #[cfg(feature = "deflate")]
+ Deflate(Pin<Box<FramedRead<ZlibDecoder<PeekableIoStreamReader>, BytesCodec>>>),
+
+ /// A decoder that doesn't have a value yet.
+ #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
+ Pending(Pin<Box<Pending>>),
+}
+
+/// A future attempt to poll the response body for EOF so we know whether to use gzip or not.
+struct Pending(PeekableIoStream, DecoderType);
+
+struct IoStream(super::body::ImplStream);
+
+enum DecoderType {
+ #[cfg(feature = "gzip")]
+ Gzip,
+ #[cfg(feature = "brotli")]
+ Brotli,
+ #[cfg(feature = "deflate")]
+ Deflate,
+}
+
+impl fmt::Debug for Decoder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Decoder").finish()
+ }
+}
+
+impl Decoder {
+ #[cfg(feature = "blocking")]
+ pub(crate) fn empty() -> Decoder {
+ Decoder {
+ inner: Inner::PlainText(Body::empty().into_stream()),
+ }
+ }
+
+ /// A plain text decoder.
+ ///
+ /// This decoder will emit the underlying chunks as-is.
+ fn plain_text(body: Body) -> Decoder {
+ Decoder {
+ inner: Inner::PlainText(body.into_stream()),
+ }
+ }
+
+ /// A gzip decoder.
+ ///
+ /// This decoder will buffer and decompress chunks that are gzipped.
+ #[cfg(feature = "gzip")]
+ fn gzip(body: Body) -> Decoder {
+ use futures_util::StreamExt;
+
+ Decoder {
+ inner: Inner::Pending(Box::pin(Pending(
+ IoStream(body.into_stream()).peekable(),
+ DecoderType::Gzip,
+ ))),
+ }
+ }
+
+ /// A brotli decoder.
+ ///
+ /// This decoder will buffer and decompress chunks that are brotlied.
+ #[cfg(feature = "brotli")]
+ fn brotli(body: Body) -> Decoder {
+ use futures_util::StreamExt;
+
+ Decoder {
+ inner: Inner::Pending(Box::pin(Pending(
+ IoStream(body.into_stream()).peekable(),
+ DecoderType::Brotli,
+ ))),
+ }
+ }
+
+ /// A deflate decoder.
+ ///
+ /// This decoder will buffer and decompress chunks that are deflated.
+ #[cfg(feature = "deflate")]
+ fn deflate(body: Body) -> Decoder {
+ use futures_util::StreamExt;
+
+ Decoder {
+ inner: Inner::Pending(Box::pin(Pending(
+ IoStream(body.into_stream()).peekable(),
+ DecoderType::Deflate,
+ ))),
+ }
+ }
+
+ #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
+ fn detect_encoding(headers: &mut HeaderMap, encoding_str: &str) -> bool {
+ use http::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
+ use log::warn;
+
+ let mut is_content_encoded = {
+ headers
+ .get_all(CONTENT_ENCODING)
+ .iter()
+ .any(|enc| enc == encoding_str)
+ || headers
+ .get_all(TRANSFER_ENCODING)
+ .iter()
+ .any(|enc| enc == encoding_str)
+ };
+ if is_content_encoded {
+ if let Some(content_length) = headers.get(CONTENT_LENGTH) {
+ if content_length == "0" {
+ warn!("{} response with content-length of 0", encoding_str);
+ is_content_encoded = false;
+ }
+ }
+ }
+ if is_content_encoded {
+ headers.remove(CONTENT_ENCODING);
+ headers.remove(CONTENT_LENGTH);
+ }
+ is_content_encoded
+ }
+
+ /// Constructs a Decoder from a hyper request.
+ ///
+ /// A decoder is just a wrapper around the hyper request that knows
+ /// how to decode the content body of the request.
+ ///
+ /// Uses the correct variant by inspecting the Content-Encoding header.
+ pub(super) fn detect(_headers: &mut HeaderMap, body: Body, _accepts: Accepts) -> Decoder {
+ #[cfg(feature = "gzip")]
+ {
+ if _accepts.gzip && Decoder::detect_encoding(_headers, "gzip") {
+ return Decoder::gzip(body);
+ }
+ }
+
+ #[cfg(feature = "brotli")]
+ {
+ if _accepts.brotli && Decoder::detect_encoding(_headers, "br") {
+ return Decoder::brotli(body);
+ }
+ }
+
+ #[cfg(feature = "deflate")]
+ {
+ if _accepts.deflate && Decoder::detect_encoding(_headers, "deflate") {
+ return Decoder::deflate(body);
+ }
+ }
+
+ Decoder::plain_text(body)
+ }
+}
+
+impl Stream for Decoder {
+ type Item = Result<Bytes, error::Error>;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ // Do a read or poll for a pending decoder value.
+ match self.inner {
+ #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
+ Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) {
+ Poll::Ready(Ok(inner)) => {
+ self.inner = inner;
+ self.poll_next(cx)
+ }
+ Poll::Ready(Err(e)) => Poll::Ready(Some(Err(crate::error::decode_io(e)))),
+ Poll::Pending => Poll::Pending,
+ },
+ Inner::PlainText(ref mut body) => Pin::new(body).poll_next(cx),
+ #[cfg(feature = "gzip")]
+ Inner::Gzip(ref mut decoder) => {
+ match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
+ Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
+ Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
+ None => Poll::Ready(None),
+ }
+ }
+ #[cfg(feature = "brotli")]
+ Inner::Brotli(ref mut decoder) => {
+ match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
+ Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
+ Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
+ None => Poll::Ready(None),
+ }
+ }
+ #[cfg(feature = "deflate")]
+ Inner::Deflate(ref mut decoder) => {
+ match futures_core::ready!(Pin::new(decoder).poll_next(cx)) {
+ Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))),
+ Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))),
+ None => Poll::Ready(None),
+ }
+ }
+ }
+ }
+}
+
+impl HttpBody for Decoder {
+ type Data = Bytes;
+ type Error = crate::Error;
+
+ fn poll_data(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
+ self.poll_next(cx)
+ }
+
+ fn poll_trailers(
+ self: Pin<&mut Self>,
+ _cx: &mut Context,
+ ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
+ Poll::Ready(Ok(None))
+ }
+
+ fn size_hint(&self) -> http_body::SizeHint {
+ match self.inner {
+ Inner::PlainText(ref body) => HttpBody::size_hint(body),
+ // the rest are "unknown", so default
+ #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))]
+ _ => http_body::SizeHint::default(),
+ }
+ }
+}
+
+impl Future for Pending {
+ type Output = Result<Inner, std::io::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ use futures_util::StreamExt;
+
+ match futures_core::ready!(Pin::new(&mut self.0).poll_peek(cx)) {
+ Some(Ok(_)) => {
+ // fallthrough
+ }
+ Some(Err(_e)) => {
+ // error was just a ref, so we need to really poll to move it
+ return Poll::Ready(Err(futures_core::ready!(
+ Pin::new(&mut self.0).poll_next(cx)
+ )
+ .expect("just peeked Some")
+ .unwrap_err()));
+ }
+ None => return Poll::Ready(Ok(Inner::PlainText(Body::empty().into_stream()))),
+ };
+
+ let _body = std::mem::replace(
+ &mut self.0,
+ IoStream(Body::empty().into_stream()).peekable(),
+ );
+
+ match self.1 {
+ #[cfg(feature = "brotli")]
+ DecoderType::Brotli => Poll::Ready(Ok(Inner::Brotli(Box::pin(FramedRead::new(
+ BrotliDecoder::new(StreamReader::new(_body)),
+ BytesCodec::new(),
+ ))))),
+ #[cfg(feature = "gzip")]
+ DecoderType::Gzip => Poll::Ready(Ok(Inner::Gzip(Box::pin(FramedRead::new(
+ GzipDecoder::new(StreamReader::new(_body)),
+ BytesCodec::new(),
+ ))))),
+ #[cfg(feature = "deflate")]
+ DecoderType::Deflate => Poll::Ready(Ok(Inner::Deflate(Box::pin(FramedRead::new(
+ ZlibDecoder::new(StreamReader::new(_body)),
+ BytesCodec::new(),
+ ))))),
+ }
+ }
+}
+
+impl Stream for IoStream {
+ type Item = Result<Bytes, std::io::Error>;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ match futures_core::ready!(Pin::new(&mut self.0).poll_next(cx)) {
+ Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk))),
+ Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))),
+ None => Poll::Ready(None),
+ }
+ }
+}
+
+// ===== impl Accepts =====
+
+impl Accepts {
+ pub(super) fn none() -> Self {
+ Accepts {
+ #[cfg(feature = "gzip")]
+ gzip: false,
+ #[cfg(feature = "brotli")]
+ brotli: false,
+ #[cfg(feature = "deflate")]
+ deflate: false,
+ }
+ }
+
+ pub(super) fn as_str(&self) -> Option<&'static str> {
+ match (self.is_gzip(), self.is_brotli(), self.is_deflate()) {
+ (true, true, true) => Some("gzip, br, deflate"),
+ (true, true, false) => Some("gzip, br"),
+ (true, false, true) => Some("gzip, deflate"),
+ (false, true, true) => Some("br, deflate"),
+ (true, false, false) => Some("gzip"),
+ (false, true, false) => Some("br"),
+ (false, false, true) => Some("deflate"),
+ (false, false, false) => None,
+ }
+ }
+
+ fn is_gzip(&self) -> bool {
+ #[cfg(feature = "gzip")]
+ {
+ self.gzip
+ }
+
+ #[cfg(not(feature = "gzip"))]
+ {
+ false
+ }
+ }
+
+ fn is_brotli(&self) -> bool {
+ #[cfg(feature = "brotli")]
+ {
+ self.brotli
+ }
+
+ #[cfg(not(feature = "brotli"))]
+ {
+ false
+ }
+ }
+
+ fn is_deflate(&self) -> bool {
+ #[cfg(feature = "deflate")]
+ {
+ self.deflate
+ }
+
+ #[cfg(not(feature = "deflate"))]
+ {
+ false
+ }
+ }
+}
+
+impl Default for Accepts {
+ fn default() -> Accepts {
+ Accepts {
+ #[cfg(feature = "gzip")]
+ gzip: true,
+ #[cfg(feature = "brotli")]
+ brotli: true,
+ #[cfg(feature = "deflate")]
+ deflate: true,
+ }
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/h3_client/connect.rs b/vendor/reqwest/src/async_impl/h3_client/connect.rs
new file mode 100644
index 000000000..daedb6e44
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/h3_client/connect.rs
@@ -0,0 +1,87 @@
+use crate::async_impl::h3_client::dns::resolve;
+use crate::dns::DynResolver;
+use crate::error::BoxError;
+use bytes::Bytes;
+use h3::client::SendRequest;
+use h3_quinn::{Connection, OpenStreams};
+use http::Uri;
+use hyper::client::connect::dns::Name;
+use quinn::{ClientConfig, Endpoint, TransportConfig};
+use std::net::{IpAddr, SocketAddr};
+use std::str::FromStr;
+use std::sync::Arc;
+
+type H3Connection = (
+ h3::client::Connection<Connection, Bytes>,
+ SendRequest<OpenStreams, Bytes>,
+);
+
+#[derive(Clone)]
+pub(crate) struct H3Connector {
+ resolver: DynResolver,
+ endpoint: Endpoint,
+}
+
+impl H3Connector {
+ pub fn new(
+ resolver: DynResolver,
+ tls: rustls::ClientConfig,
+ local_addr: Option<IpAddr>,
+ transport_config: TransportConfig,
+ ) -> H3Connector {
+ let mut config = ClientConfig::new(Arc::new(tls));
+ // FIXME: Replace this when there is a setter.
+ config.transport_config(Arc::new(transport_config));
+
+ let socket_addr = match local_addr {
+ Some(ip) => SocketAddr::new(ip, 0),
+ None => "[::]:0".parse::<SocketAddr>().unwrap(),
+ };
+
+ let mut endpoint = Endpoint::client(socket_addr).expect("unable to create QUIC endpoint");
+ endpoint.set_default_client_config(config);
+
+ Self { resolver, endpoint }
+ }
+
+ pub async fn connect(&mut self, dest: Uri) -> Result<H3Connection, BoxError> {
+ let host = dest.host().ok_or("destination must have a host")?;
+ let port = dest.port_u16().unwrap_or(443);
+
+ let addrs = if let Some(addr) = IpAddr::from_str(host).ok() {
+ // If the host is already an IP address, skip resolving.
+ vec![SocketAddr::new(addr, port)]
+ } else {
+ let addrs = resolve(&mut self.resolver, Name::from_str(host)?).await?;
+ let addrs = addrs.map(|mut addr| {
+ addr.set_port(port);
+ addr
+ });
+ addrs.collect()
+ };
+
+ self.remote_connect(addrs, host).await
+ }
+
+ async fn remote_connect(
+ &mut self,
+ addrs: Vec<SocketAddr>,
+ server_name: &str,
+ ) -> Result<H3Connection, BoxError> {
+ let mut err = None;
+ for addr in addrs {
+ match self.endpoint.connect(addr, server_name)?.await {
+ Ok(new_conn) => {
+ let quinn_conn = Connection::new(new_conn);
+ return Ok(h3::client::new(quinn_conn).await?);
+ }
+ Err(e) => err = Some(e),
+ }
+ }
+
+ match err {
+ Some(e) => Err(Box::new(e) as BoxError),
+ None => Err("failed to establish connection for HTTP/3 request".into()),
+ }
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/h3_client/dns.rs b/vendor/reqwest/src/async_impl/h3_client/dns.rs
new file mode 100644
index 000000000..9cb50d1e3
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/h3_client/dns.rs
@@ -0,0 +1,43 @@
+use core::task;
+use hyper::client::connect::dns::Name;
+use std::future::Future;
+use std::net::SocketAddr;
+use std::task::Poll;
+use tower_service::Service;
+
+// Trait from hyper to implement DNS resolution for HTTP/3 client.
+pub trait Resolve {
+ type Addrs: Iterator<Item = SocketAddr>;
+ type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
+ type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
+
+ fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
+ fn resolve(&mut self, name: Name) -> Self::Future;
+}
+
+impl<S> Resolve for S
+where
+ S: Service<Name>,
+ S::Response: Iterator<Item = SocketAddr>,
+ S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
+{
+ type Addrs = S::Response;
+ type Error = S::Error;
+ type Future = S::Future;
+
+ fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Service::poll_ready(self, cx)
+ }
+
+ fn resolve(&mut self, name: Name) -> Self::Future {
+ Service::call(self, name)
+ }
+}
+
+pub(super) async fn resolve<R>(resolver: &mut R, name: Name) -> Result<R::Addrs, R::Error>
+where
+ R: Resolve,
+{
+ futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?;
+ resolver.resolve(name).await
+}
diff --git a/vendor/reqwest/src/async_impl/h3_client/mod.rs b/vendor/reqwest/src/async_impl/h3_client/mod.rs
new file mode 100644
index 000000000..919e13c0a
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/h3_client/mod.rs
@@ -0,0 +1,88 @@
+#![cfg(feature = "http3")]
+
+pub(crate) mod connect;
+pub(crate) mod dns;
+mod pool;
+
+use crate::async_impl::h3_client::pool::{Key, Pool, PoolClient};
+use crate::error::{BoxError, Error, Kind};
+use crate::{error, Body};
+use connect::H3Connector;
+use futures_util::future;
+use http::{Request, Response};
+use hyper::Body as HyperBody;
+use log::trace;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+use std::time::Duration;
+
+#[derive(Clone)]
+pub(crate) struct H3Client {
+ pool: Pool,
+ connector: H3Connector,
+}
+
+impl H3Client {
+ pub fn new(connector: H3Connector, pool_timeout: Option<Duration>) -> Self {
+ H3Client {
+ pool: Pool::new(pool_timeout),
+ connector,
+ }
+ }
+
+ async fn get_pooled_client(&mut self, key: Key) -> Result<PoolClient, BoxError> {
+ if let Some(client) = self.pool.try_pool(&key) {
+ trace!("getting client from pool with key {:?}", key);
+ return Ok(client);
+ }
+
+ trace!("did not find connection {:?} in pool so connecting...", key);
+
+ let dest = pool::domain_as_uri(key.clone());
+ self.pool.connecting(key.clone())?;
+ let (driver, tx) = self.connector.connect(dest).await?;
+ Ok(self.pool.new_connection(key, driver, tx))
+ }
+
+ async fn send_request(
+ mut self,
+ key: Key,
+ req: Request<Body>,
+ ) -> Result<Response<HyperBody>, Error> {
+ let mut pooled = match self.get_pooled_client(key).await {
+ Ok(client) => client,
+ Err(e) => return Err(error::request(e)),
+ };
+ pooled
+ .send_request(req)
+ .await
+ .map_err(|e| Error::new(Kind::Request, Some(e)))
+ }
+
+ pub fn request(&self, mut req: Request<Body>) -> H3ResponseFuture {
+ let pool_key = match pool::extract_domain(req.uri_mut()) {
+ Ok(s) => s,
+ Err(e) => {
+ return H3ResponseFuture {
+ inner: Box::pin(future::err(e)),
+ }
+ }
+ };
+ H3ResponseFuture {
+ inner: Box::pin(self.clone().send_request(pool_key, req)),
+ }
+ }
+}
+
+pub(crate) struct H3ResponseFuture {
+ inner: Pin<Box<dyn Future<Output = Result<Response<HyperBody>, Error>> + Send>>,
+}
+
+impl Future for H3ResponseFuture {
+ type Output = Result<Response<HyperBody>, Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ self.inner.as_mut().poll(cx)
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/h3_client/pool.rs b/vendor/reqwest/src/async_impl/h3_client/pool.rs
new file mode 100644
index 000000000..6fcb8e719
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/h3_client/pool.rs
@@ -0,0 +1,198 @@
+use bytes::Bytes;
+use std::collections::{HashMap, HashSet};
+use std::sync::mpsc::{Receiver, TryRecvError};
+use std::sync::{Arc, Mutex};
+use std::time::Duration;
+use tokio::time::Instant;
+
+use crate::error::{BoxError, Error, Kind};
+use crate::Body;
+use bytes::Buf;
+use futures_util::future;
+use h3::client::SendRequest;
+use h3_quinn::{Connection, OpenStreams};
+use http::uri::{Authority, Scheme};
+use http::{Request, Response, Uri};
+use hyper::Body as HyperBody;
+use log::trace;
+
+pub(super) type Key = (Scheme, Authority);
+
+#[derive(Clone)]
+pub struct Pool {
+ inner: Arc<Mutex<PoolInner>>,
+}
+
+impl Pool {
+ pub fn new(timeout: Option<Duration>) -> Self {
+ Self {
+ inner: Arc::new(Mutex::new(PoolInner {
+ connecting: HashSet::new(),
+ idle_conns: HashMap::new(),
+ timeout,
+ })),
+ }
+ }
+
+ pub fn connecting(&self, key: Key) -> Result<(), BoxError> {
+ let mut inner = self.inner.lock().unwrap();
+ if !inner.connecting.insert(key.clone()) {
+ return Err(format!("HTTP/3 connecting already in progress for {:?}", key).into());
+ }
+ return Ok(());
+ }
+
+ pub fn try_pool(&self, key: &Key) -> Option<PoolClient> {
+ let mut inner = self.inner.lock().unwrap();
+ let timeout = inner.timeout;
+ if let Some(conn) = inner.idle_conns.get(&key) {
+ // We check first if the connection still valid
+ // and if not, we remove it from the pool.
+ if conn.is_invalid() {
+ trace!("pooled HTTP/3 connection is invalid so removing it...");
+ inner.idle_conns.remove(&key);
+ return None;
+ }
+
+ if let Some(duration) = timeout {
+ if Instant::now().saturating_duration_since(conn.idle_timeout) > duration {
+ trace!("pooled connection expired");
+ return None;
+ }
+ }
+ }
+
+ inner
+ .idle_conns
+ .get_mut(&key)
+ .and_then(|conn| Some(conn.pool()))
+ }
+
+ pub fn new_connection(
+ &mut self,
+ key: Key,
+ mut driver: h3::client::Connection<Connection, Bytes>,
+ tx: SendRequest<OpenStreams, Bytes>,
+ ) -> PoolClient {
+ let (close_tx, close_rx) = std::sync::mpsc::channel();
+ tokio::spawn(async move {
+ if let Err(e) = future::poll_fn(|cx| driver.poll_close(cx)).await {
+ trace!("poll_close returned error {:?}", e);
+ close_tx.send(e).ok();
+ }
+ });
+
+ let mut inner = self.inner.lock().unwrap();
+
+ let client = PoolClient::new(tx);
+ let conn = PoolConnection::new(client.clone(), close_rx);
+ inner.insert(key.clone(), conn);
+
+ // We clean up "connecting" here so we don't have to acquire the lock again.
+ let existed = inner.connecting.remove(&key);
+ debug_assert!(existed, "key not in connecting set");
+
+ client
+ }
+}
+
+struct PoolInner {
+ connecting: HashSet<Key>,
+ idle_conns: HashMap<Key, PoolConnection>,
+ timeout: Option<Duration>,
+}
+
+impl PoolInner {
+ fn insert(&mut self, key: Key, conn: PoolConnection) {
+ if self.idle_conns.contains_key(&key) {
+ trace!("connection already exists for key {:?}", key);
+ }
+
+ self.idle_conns.insert(key, conn);
+ }
+}
+
+#[derive(Clone)]
+pub struct PoolClient {
+ inner: SendRequest<OpenStreams, Bytes>,
+}
+
+impl PoolClient {
+ pub fn new(tx: SendRequest<OpenStreams, Bytes>) -> Self {
+ Self { inner: tx }
+ }
+
+ pub async fn send_request(
+ &mut self,
+ req: Request<Body>,
+ ) -> Result<Response<HyperBody>, BoxError> {
+ let (head, req_body) = req.into_parts();
+ let req = Request::from_parts(head, ());
+ let mut stream = self.inner.send_request(req).await?;
+
+ match req_body.as_bytes() {
+ Some(b) if !b.is_empty() => {
+ stream.send_data(Bytes::copy_from_slice(b)).await?;
+ }
+ _ => {}
+ }
+
+ stream.finish().await?;
+
+ let resp = stream.recv_response().await?;
+
+ let mut resp_body = Vec::new();
+ while let Some(chunk) = stream.recv_data().await? {
+ resp_body.extend(chunk.chunk())
+ }
+
+ Ok(resp.map(|_| HyperBody::from(resp_body)))
+ }
+}
+
+pub struct PoolConnection {
+ // This receives errors from polling h3 driver.
+ close_rx: Receiver<h3::Error>,
+ client: PoolClient,
+ idle_timeout: Instant,
+}
+
+impl PoolConnection {
+ pub fn new(client: PoolClient, close_rx: Receiver<h3::Error>) -> Self {
+ Self {
+ close_rx,
+ client,
+ idle_timeout: Instant::now(),
+ }
+ }
+
+ pub fn pool(&mut self) -> PoolClient {
+ self.idle_timeout = Instant::now();
+ self.client.clone()
+ }
+
+ pub fn is_invalid(&self) -> bool {
+ match self.close_rx.try_recv() {
+ Err(TryRecvError::Empty) => false,
+ Err(TryRecvError::Disconnected) => true,
+ Ok(_) => true,
+ }
+ }
+}
+
+pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
+ let uri_clone = uri.clone();
+ match (uri_clone.scheme(), uri_clone.authority()) {
+ (Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())),
+ _ => Err(Error::new(Kind::Request, None::<Error>)),
+ }
+}
+
+pub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri {
+ http::uri::Builder::new()
+ .scheme(scheme)
+ .authority(auth)
+ .path_and_query("/")
+ .build()
+ .expect("domain is valid Uri")
+}
diff --git a/vendor/reqwest/src/async_impl/mod.rs b/vendor/reqwest/src/async_impl/mod.rs
new file mode 100644
index 000000000..5d99ef027
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/mod.rs
@@ -0,0 +1,18 @@
+pub use self::body::Body;
+pub use self::client::{Client, ClientBuilder};
+pub use self::request::{Request, RequestBuilder};
+pub use self::response::Response;
+pub use self::upgrade::Upgraded;
+
+#[cfg(feature = "blocking")]
+pub(crate) use self::decoder::Decoder;
+
+pub mod body;
+pub mod client;
+pub mod decoder;
+pub mod h3_client;
+#[cfg(feature = "multipart")]
+pub mod multipart;
+pub(crate) mod request;
+mod response;
+mod upgrade;
diff --git a/vendor/reqwest/src/async_impl/multipart.rs b/vendor/reqwest/src/async_impl/multipart.rs
new file mode 100644
index 000000000..6b6a81e3d
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/multipart.rs
@@ -0,0 +1,674 @@
+//! multipart/form-data
+use std::borrow::Cow;
+use std::fmt;
+use std::pin::Pin;
+
+use bytes::Bytes;
+use mime_guess::Mime;
+use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
+
+use futures_core::Stream;
+use futures_util::{future, stream, StreamExt};
+
+use super::Body;
+use crate::header::HeaderMap;
+
+/// An async multipart/form-data request.
+pub struct Form {
+ inner: FormParts<Part>,
+}
+
+/// A field in a multipart form.
+pub struct Part {
+ meta: PartMetadata,
+ value: Body,
+ body_length: Option<u64>,
+}
+
+pub(crate) struct FormParts<P> {
+ pub(crate) boundary: String,
+ pub(crate) computed_headers: Vec<Vec<u8>>,
+ pub(crate) fields: Vec<(Cow<'static, str>, P)>,
+ pub(crate) percent_encoding: PercentEncoding,
+}
+
+pub(crate) struct PartMetadata {
+ mime: Option<Mime>,
+ file_name: Option<Cow<'static, str>>,
+ pub(crate) headers: HeaderMap,
+}
+
+pub(crate) trait PartProps {
+ fn value_len(&self) -> Option<u64>;
+ fn metadata(&self) -> &PartMetadata;
+}
+
+// ===== impl Form =====
+
+impl Default for Form {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Form {
+ /// Creates a new async Form without any content.
+ pub fn new() -> Form {
+ Form {
+ inner: FormParts::new(),
+ }
+ }
+
+ /// Get the boundary that this form will use.
+ #[inline]
+ pub fn boundary(&self) -> &str {
+ self.inner.boundary()
+ }
+
+ /// Add a data field with supplied name and value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let form = reqwest::multipart::Form::new()
+ /// .text("username", "seanmonstar")
+ /// .text("password", "secret");
+ /// ```
+ pub fn text<T, U>(self, name: T, value: U) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ U: Into<Cow<'static, str>>,
+ {
+ self.part(name, Part::text(value))
+ }
+
+ /// Adds a customized Part.
+ pub fn part<T>(self, name: T, part: Part) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.part(name, part))
+ }
+
+ /// Configure this `Form` to percent-encode using the `path-segment` rules.
+ pub fn percent_encode_path_segment(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_path_segment())
+ }
+
+ /// Configure this `Form` to percent-encode using the `attr-char` rules.
+ pub fn percent_encode_attr_chars(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_attr_chars())
+ }
+
+ /// Configure this `Form` to skip percent-encoding
+ pub fn percent_encode_noop(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_noop())
+ }
+
+ /// Consume this instance and transform into an instance of Body for use in a request.
+ pub(crate) fn stream(mut self) -> Body {
+ if self.inner.fields.is_empty() {
+ return Body::empty();
+ }
+
+ // create initial part to init reduce chain
+ let (name, part) = self.inner.fields.remove(0);
+ let start = Box::pin(self.part_stream(name, part))
+ as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
+
+ let fields = self.inner.take_fields();
+ // for each field, chain an additional stream
+ let stream = fields.into_iter().fold(start, |memo, (name, part)| {
+ let part_stream = self.part_stream(name, part);
+ Box::pin(memo.chain(part_stream))
+ as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
+ });
+ // append special ending boundary
+ let last = stream::once(future::ready(Ok(
+ format!("--{}--\r\n", self.boundary()).into()
+ )));
+ Body::stream(stream.chain(last))
+ }
+
+ /// Generate a hyper::Body stream for a single Part instance of a Form request.
+ pub(crate) fn part_stream<T>(
+ &mut self,
+ name: T,
+ part: Part,
+ ) -> impl Stream<Item = Result<Bytes, crate::Error>>
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ // start with boundary
+ let boundary = stream::once(future::ready(Ok(
+ format!("--{}\r\n", self.boundary()).into()
+ )));
+ // append headers
+ let header = stream::once(future::ready(Ok({
+ let mut h = self
+ .inner
+ .percent_encoding
+ .encode_headers(&name.into(), &part.meta);
+ h.extend_from_slice(b"\r\n\r\n");
+ h.into()
+ })));
+ // then append form data followed by terminating CRLF
+ boundary
+ .chain(header)
+ .chain(part.value.into_stream())
+ .chain(stream::once(future::ready(Ok("\r\n".into()))))
+ }
+
+ pub(crate) fn compute_length(&mut self) -> Option<u64> {
+ self.inner.compute_length()
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(FormParts<Part>) -> FormParts<Part>,
+ {
+ Form {
+ inner: func(self.inner),
+ }
+ }
+}
+
+impl fmt::Debug for Form {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt_fields("Form", f)
+ }
+}
+
+// ===== impl Part =====
+
+impl Part {
+ /// Makes a text parameter.
+ pub fn text<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(string) => Body::from(string),
+ };
+ Part::new(body, None)
+ }
+
+ /// Makes a new parameter from arbitrary bytes.
+ pub fn bytes<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, [u8]>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(vec) => Body::from(vec),
+ };
+ Part::new(body, None)
+ }
+
+ /// Makes a new parameter from an arbitrary stream.
+ pub fn stream<T: Into<Body>>(value: T) -> Part {
+ Part::new(value.into(), None)
+ }
+
+ /// Makes a new parameter from an arbitrary stream with a known length. This is particularly
+ /// useful when adding something like file contents as a stream, where you can know the content
+ /// length beforehand.
+ pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
+ Part::new(value.into(), Some(length))
+ }
+
+ fn new(value: Body, body_length: Option<u64>) -> Part {
+ Part {
+ meta: PartMetadata::new(),
+ value,
+ body_length,
+ }
+ }
+
+ /// Tries to set the mime of this part.
+ pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
+ Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
+ }
+
+ // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
+ fn mime(self, mime: Mime) -> Part {
+ self.with_inner(move |inner| inner.mime(mime))
+ }
+
+ /// Sets the filename, builder style.
+ pub fn file_name<T>(self, filename: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.file_name(filename))
+ }
+
+ /// Sets custom headers for the part.
+ pub fn headers(self, headers: HeaderMap) -> Part {
+ self.with_inner(move |inner| inner.headers(headers))
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(PartMetadata) -> PartMetadata,
+ {
+ Part {
+ meta: func(self.meta),
+ ..self
+ }
+ }
+}
+
+impl fmt::Debug for Part {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut dbg = f.debug_struct("Part");
+ dbg.field("value", &self.value);
+ self.meta.fmt_fields(&mut dbg);
+ dbg.finish()
+ }
+}
+
+impl PartProps for Part {
+ fn value_len(&self) -> Option<u64> {
+ if self.body_length.is_some() {
+ self.body_length
+ } else {
+ self.value.content_length()
+ }
+ }
+
+ fn metadata(&self) -> &PartMetadata {
+ &self.meta
+ }
+}
+
+// ===== impl FormParts =====
+
+impl<P: PartProps> FormParts<P> {
+ pub(crate) fn new() -> Self {
+ FormParts {
+ boundary: gen_boundary(),
+ computed_headers: Vec::new(),
+ fields: Vec::new(),
+ percent_encoding: PercentEncoding::PathSegment,
+ }
+ }
+
+ pub(crate) fn boundary(&self) -> &str {
+ &self.boundary
+ }
+
+ /// Adds a customized Part.
+ pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.fields.push((name.into(), part));
+ self
+ }
+
+ /// Configure this `Form` to percent-encode using the `path-segment` rules.
+ pub(crate) fn percent_encode_path_segment(mut self) -> Self {
+ self.percent_encoding = PercentEncoding::PathSegment;
+ self
+ }
+
+ /// Configure this `Form` to percent-encode using the `attr-char` rules.
+ pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
+ self.percent_encoding = PercentEncoding::AttrChar;
+ self
+ }
+
+ /// Configure this `Form` to skip percent-encoding
+ pub(crate) fn percent_encode_noop(mut self) -> Self {
+ self.percent_encoding = PercentEncoding::NoOp;
+ self
+ }
+
+ // If predictable, computes the length the request will have
+ // The length should be preditable if only String and file fields have been added,
+ // but not if a generic reader has been added;
+ pub(crate) fn compute_length(&mut self) -> Option<u64> {
+ let mut length = 0u64;
+ for &(ref name, ref field) in self.fields.iter() {
+ match field.value_len() {
+ Some(value_length) => {
+ // We are constructing the header just to get its length. To not have to
+ // construct it again when the request is sent we cache these headers.
+ let header = self.percent_encoding.encode_headers(name, field.metadata());
+ let header_length = header.len();
+ self.computed_headers.push(header);
+ // The additions mimic the format string out of which the field is constructed
+ // in Reader. Not the cleanest solution because if that format string is
+ // ever changed then this formula needs to be changed too which is not an
+ // obvious dependency in the code.
+ length += 2
+ + self.boundary().len() as u64
+ + 2
+ + header_length as u64
+ + 4
+ + value_length
+ + 2
+ }
+ _ => return None,
+ }
+ }
+ // If there is a at least one field there is a special boundary for the very last field.
+ if !self.fields.is_empty() {
+ length += 2 + self.boundary().len() as u64 + 4
+ }
+ Some(length)
+ }
+
+ /// Take the fields vector of this instance, replacing with an empty vector.
+ fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
+ std::mem::replace(&mut self.fields, Vec::new())
+ }
+}
+
+impl<P: fmt::Debug> FormParts<P> {
+ pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct(ty_name)
+ .field("boundary", &self.boundary)
+ .field("parts", &self.fields)
+ .finish()
+ }
+}
+
+// ===== impl PartMetadata =====
+
+impl PartMetadata {
+ pub(crate) fn new() -> Self {
+ PartMetadata {
+ mime: None,
+ file_name: None,
+ headers: HeaderMap::default(),
+ }
+ }
+
+ pub(crate) fn mime(mut self, mime: Mime) -> Self {
+ self.mime = Some(mime);
+ self
+ }
+
+ pub(crate) fn file_name<T>(mut self, filename: T) -> Self
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.file_name = Some(filename.into());
+ self
+ }
+
+ pub(crate) fn headers<T>(mut self, headers: T) -> Self
+ where
+ T: Into<HeaderMap>,
+ {
+ self.headers = headers.into();
+ self
+ }
+}
+
+impl PartMetadata {
+ pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
+ &self,
+ debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
+ ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
+ debug_struct
+ .field("mime", &self.mime)
+ .field("file_name", &self.file_name)
+ .field("headers", &self.headers)
+ }
+}
+
+// https://url.spec.whatwg.org/#fragment-percent-encode-set
+const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
+ .add(b' ')
+ .add(b'"')
+ .add(b'<')
+ .add(b'>')
+ .add(b'`');
+
+// https://url.spec.whatwg.org/#path-percent-encode-set
+const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
+
+const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
+
+// https://tools.ietf.org/html/rfc8187#section-3.2.1
+const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
+ .remove(b'!')
+ .remove(b'#')
+ .remove(b'$')
+ .remove(b'&')
+ .remove(b'+')
+ .remove(b'-')
+ .remove(b'.')
+ .remove(b'^')
+ .remove(b'_')
+ .remove(b'`')
+ .remove(b'|')
+ .remove(b'~');
+
+pub(crate) enum PercentEncoding {
+ PathSegment,
+ AttrChar,
+ NoOp,
+}
+
+impl PercentEncoding {
+ pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
+ let mut buf = Vec::new();
+ buf.extend_from_slice(b"Content-Disposition: form-data; ");
+
+ match self.percent_encode(name) {
+ Cow::Borrowed(value) => {
+ // nothing has been percent encoded
+ buf.extend_from_slice(b"name=\"");
+ buf.extend_from_slice(value.as_bytes());
+ buf.extend_from_slice(b"\"");
+ }
+ Cow::Owned(value) => {
+ // something has been percent encoded
+ buf.extend_from_slice(b"name*=utf-8''");
+ buf.extend_from_slice(value.as_bytes());
+ }
+ }
+
+ // According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
+ // See https://github.com/seanmonstar/reqwest/issues/419.
+ if let Some(filename) = &field.file_name {
+ buf.extend_from_slice(b"; filename=\"");
+ let legal_filename = filename
+ .replace('\\', "\\\\")
+ .replace('"', "\\\"")
+ .replace('\r', "\\\r")
+ .replace('\n', "\\\n");
+ buf.extend_from_slice(legal_filename.as_bytes());
+ buf.extend_from_slice(b"\"");
+ }
+
+ if let Some(mime) = &field.mime {
+ buf.extend_from_slice(b"\r\nContent-Type: ");
+ buf.extend_from_slice(mime.as_ref().as_bytes());
+ }
+
+ for (k, v) in field.headers.iter() {
+ buf.extend_from_slice(b"\r\n");
+ buf.extend_from_slice(k.as_str().as_bytes());
+ buf.extend_from_slice(b": ");
+ buf.extend_from_slice(v.as_bytes());
+ }
+ buf
+ }
+
+ fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> {
+ use percent_encoding::utf8_percent_encode as percent_encode;
+
+ match self {
+ Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(),
+ Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(),
+ Self::NoOp => value.into(),
+ }
+ }
+}
+
+fn gen_boundary() -> String {
+ use crate::util::fast_random as random;
+
+ let a = random();
+ let b = random();
+ let c = random();
+ let d = random();
+
+ format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use futures_util::TryStreamExt;
+ use futures_util::{future, stream};
+ use tokio::{self, runtime};
+
+ #[test]
+ fn form_empty() {
+ let form = Form::new();
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let body = form.stream().into_stream();
+ let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
+
+ let out = rt.block_on(s);
+ assert!(out.unwrap().is_empty());
+ }
+
+ #[test]
+ fn stream_to_end() {
+ let mut form = Form::new()
+ .part(
+ "reader1",
+ Part::stream(Body::stream(stream::once(future::ready::<
+ Result<String, crate::Error>,
+ >(Ok(
+ "part1".to_owned()
+ ))))),
+ )
+ .part("key1", Part::text("value1"))
+ .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
+ .part(
+ "reader2",
+ Part::stream(Body::stream(stream::once(future::ready::<
+ Result<String, crate::Error>,
+ >(Ok(
+ "part2".to_owned()
+ ))))),
+ )
+ .part("key3", Part::text("value3").file_name("filename"));
+ form.inner.boundary = "boundary".to_string();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
+ part1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
+ value1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\r\n\
+ value2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
+ part2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
+ value3\r\n--boundary--\r\n";
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let body = form.stream().into_stream();
+ let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
+
+ let out = rt.block_on(s).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&out).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
+ }
+
+ #[test]
+ fn stream_to_end_with_header() {
+ let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
+ let mut headers = HeaderMap::new();
+ headers.insert("Hdr3", "/a/b/c".parse().unwrap());
+ part = part.headers(headers);
+ let mut form = Form::new().part("key2", part);
+ form.inner.boundary = "boundary".to_string();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\
+ hdr3: /a/b/c\r\n\
+ \r\n\
+ value2\r\n\
+ --boundary--\r\n";
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let body = form.stream().into_stream();
+ let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
+
+ let out = rt.block_on(s).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&out).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
+ }
+
+ #[test]
+ fn correct_content_length() {
+ // Setup an arbitrary data stream
+ let stream_data = b"just some stream data";
+ let stream_len = stream_data.len();
+ let stream_data = stream_data
+ .chunks(3)
+ .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
+ let the_stream = futures_util::stream::iter(stream_data);
+
+ let bytes_data = b"some bytes data".to_vec();
+ let bytes_len = bytes_data.len();
+
+ let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
+ let body_part = Part::bytes(bytes_data);
+
+ // A simple check to make sure we get the configured body length
+ assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
+
+ // Make sure it delegates to the underlying body if length is not specified
+ assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
+ }
+
+ #[test]
+ fn header_percent_encoding() {
+ let name = "start%'\"\r\nßend";
+ let field = Part::text("");
+
+ assert_eq!(
+ PercentEncoding::PathSegment.encode_headers(name, &field.meta),
+ &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
+ );
+
+ assert_eq!(
+ PercentEncoding::AttrChar.encode_headers(name, &field.meta),
+ &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
+ );
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/request.rs b/vendor/reqwest/src/async_impl/request.rs
new file mode 100644
index 000000000..a5e22e060
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/request.rs
@@ -0,0 +1,1129 @@
+use std::convert::TryFrom;
+use std::fmt;
+use std::future::Future;
+use std::time::Duration;
+
+use serde::Serialize;
+#[cfg(feature = "json")]
+use serde_json;
+
+use super::body::Body;
+use super::client::{Client, Pending};
+#[cfg(feature = "multipart")]
+use super::multipart;
+use super::response::Response;
+#[cfg(feature = "multipart")]
+use crate::header::CONTENT_LENGTH;
+use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
+use crate::{Method, Url};
+use http::{request::Parts, Request as HttpRequest, Version};
+
+/// A request which can be executed with `Client::execute()`.
+pub struct Request {
+ method: Method,
+ url: Url,
+ headers: HeaderMap,
+ body: Option<Body>,
+ timeout: Option<Duration>,
+ version: Version,
+}
+
+/// A builder to construct the properties of a `Request`.
+///
+/// To construct a `RequestBuilder`, refer to the `Client` documentation.
+#[must_use = "RequestBuilder does nothing until you 'send' it"]
+pub struct RequestBuilder {
+ client: Client,
+ request: crate::Result<Request>,
+}
+
+impl Request {
+ /// Constructs a new request.
+ #[inline]
+ pub fn new(method: Method, url: Url) -> Self {
+ Request {
+ method,
+ url,
+ headers: HeaderMap::new(),
+ body: None,
+ timeout: None,
+ version: Version::default(),
+ }
+ }
+
+ /// Get the method.
+ #[inline]
+ pub fn method(&self) -> &Method {
+ &self.method
+ }
+
+ /// Get a mutable reference to the method.
+ #[inline]
+ pub fn method_mut(&mut self) -> &mut Method {
+ &mut self.method
+ }
+
+ /// Get the url.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /// Get a mutable reference to the url.
+ #[inline]
+ pub fn url_mut(&mut self) -> &mut Url {
+ &mut self.url
+ }
+
+ /// Get the headers.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ &self.headers
+ }
+
+ /// Get a mutable reference to the headers.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ &mut self.headers
+ }
+
+ /// Get the body.
+ #[inline]
+ pub fn body(&self) -> Option<&Body> {
+ self.body.as_ref()
+ }
+
+ /// Get a mutable reference to the body.
+ #[inline]
+ pub fn body_mut(&mut self) -> &mut Option<Body> {
+ &mut self.body
+ }
+
+ /// Get the timeout.
+ #[inline]
+ pub fn timeout(&self) -> Option<&Duration> {
+ self.timeout.as_ref()
+ }
+
+ /// Get a mutable reference to the timeout.
+ #[inline]
+ pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
+ &mut self.timeout
+ }
+
+ /// Get the http version.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.version
+ }
+
+ /// Get a mutable reference to the http version.
+ #[inline]
+ pub fn version_mut(&mut self) -> &mut Version {
+ &mut self.version
+ }
+
+ /// Attempt to clone the request.
+ ///
+ /// `None` is returned if the request can not be cloned, i.e. if the body is a stream.
+ pub fn try_clone(&self) -> Option<Request> {
+ let body = match self.body.as_ref() {
+ Some(body) => Some(body.try_clone()?),
+ None => None,
+ };
+ let mut req = Request::new(self.method().clone(), self.url().clone());
+ *req.timeout_mut() = self.timeout().cloned();
+ *req.headers_mut() = self.headers().clone();
+ *req.version_mut() = self.version();
+ req.body = body;
+ Some(req)
+ }
+
+ pub(super) fn pieces(
+ self,
+ ) -> (
+ Method,
+ Url,
+ HeaderMap,
+ Option<Body>,
+ Option<Duration>,
+ Version,
+ ) {
+ (
+ self.method,
+ self.url,
+ self.headers,
+ self.body,
+ self.timeout,
+ self.version,
+ )
+ }
+}
+
+impl RequestBuilder {
+ pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
+ let mut builder = RequestBuilder { client, request };
+
+ let auth = builder
+ .request
+ .as_mut()
+ .ok()
+ .and_then(|req| extract_authority(&mut req.url));
+
+ if let Some((username, password)) = auth {
+ builder.basic_auth(username, password)
+ } else {
+ builder
+ }
+ }
+
+ /// Assemble a builder starting from an existing `Client` and a `Request`.
+ pub fn from_parts(client: Client, request: Request) -> RequestBuilder {
+ RequestBuilder {
+ client,
+ request: crate::Result::Ok(request),
+ }
+ }
+
+ /// Add a `Header` to this Request.
+ pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ self.header_sensitive(key, value, false)
+ }
+
+ /// Add a `Header` to this Request with ability to define if header_value is sensitive.
+ fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match <HeaderName as TryFrom<K>>::try_from(key) {
+ Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(mut value) => {
+ // We want to potentially make an unsensitive header
+ // to be sensitive, not the reverse. So, don't turn off
+ // a previously sensitive header.
+ if sensitive {
+ value.set_sensitive(true);
+ }
+ req.headers_mut().append(key, value);
+ }
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ },
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ };
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Add a set of Headers to the existing ones on this Request.
+ ///
+ /// The headers will be merged in to any already set.
+ pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ crate::util::replace_headers(req.headers_mut(), headers);
+ }
+ self
+ }
+
+ /// Enable HTTP basic authentication.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ ///
+ /// # async fn run() -> Result<(), Error> {
+ /// let client = reqwest::Client::new();
+ /// let resp = client.delete("http://httpbin.org/delete")
+ /// .basic_auth("admin", Some("good password"))
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
+ where
+ U: fmt::Display,
+ P: fmt::Display,
+ {
+ let header_value = crate::util::basic_auth(username, password);
+ self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
+ }
+
+ /// Enable HTTP bearer authentication.
+ pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
+ where
+ T: fmt::Display,
+ {
+ let header_value = format!("Bearer {}", token);
+ self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
+ }
+
+ /// Set the request body.
+ pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.body_mut() = Some(body.into());
+ }
+ self
+ }
+
+ /// Enables a request timeout.
+ ///
+ /// The timeout is applied from when the request starts connecting until the
+ /// response body has finished. It affects only this request and overrides
+ /// the timeout configured using `ClientBuilder::timeout()`.
+ pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.timeout_mut() = Some(timeout);
+ }
+ self
+ }
+
+ /// Sends a multipart/form-data body.
+ ///
+ /// ```
+ /// # use reqwest::Error;
+ ///
+ /// # async fn run() -> Result<(), Error> {
+ /// let client = reqwest::Client::new();
+ /// let form = reqwest::multipart::Form::new()
+ /// .text("key3", "value3")
+ /// .text("key4", "value4");
+ ///
+ ///
+ /// let response = client.post("your url")
+ /// .multipart(form)
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[cfg(feature = "multipart")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+ pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
+ let mut builder = self.header(
+ CONTENT_TYPE,
+ format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
+ );
+
+ builder = match multipart.compute_length() {
+ Some(length) => builder.header(CONTENT_LENGTH, length),
+ None => builder,
+ };
+
+ if let Ok(ref mut req) = builder.request {
+ *req.body_mut() = Some(multipart.stream())
+ }
+ builder
+ }
+
+ /// Modify the query string of the URL.
+ ///
+ /// Modifies the URL of this request, adding the parameters provided.
+ /// This method appends and does not overwrite. This means that it can
+ /// be called multiple times and that existing query parameters are not
+ /// overwritten if the same key is used. The key will simply show up
+ /// twice in the query string.
+ /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
+ ///
+ /// # Note
+ /// This method does not support serializing a single key-value
+ /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
+ /// as `.query(&[("key", "val")])`. It's also possible to serialize structs
+ /// and maps into a key-value pair.
+ ///
+ /// # Errors
+ /// This method will fail if the object you provide cannot be serialized
+ /// into a query string.
+ pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ let url = req.url_mut();
+ let mut pairs = url.query_pairs_mut();
+ let serializer = serde_urlencoded::Serializer::new(&mut pairs);
+
+ if let Err(err) = query.serialize(serializer) {
+ error = Some(crate::error::builder(err));
+ }
+ }
+ if let Ok(ref mut req) = self.request {
+ if let Some("") = req.url().query() {
+ req.url_mut().set_query(None);
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Set HTTP version
+ pub fn version(mut self, version: Version) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.version = version;
+ }
+ self
+ }
+
+ /// Send a form body.
+ ///
+ /// Sets the body to the url encoded serialization of the passed value,
+ /// and also sets the `Content-Type: application/x-www-form-urlencoded`
+ /// header.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// # use std::collections::HashMap;
+ /// #
+ /// # async fn run() -> Result<(), Error> {
+ /// let mut params = HashMap::new();
+ /// params.insert("lang", "rust");
+ ///
+ /// let client = reqwest::Client::new();
+ /// let res = client.post("http://httpbin.org")
+ /// .form(&params)
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails if the passed value cannot be serialized into
+ /// url encoded format
+ pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_urlencoded::to_string(form) {
+ Ok(body) => {
+ req.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("application/x-www-form-urlencoded"),
+ );
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Send a JSON body.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Errors
+ ///
+ /// Serialization can fail if `T`'s implementation of `Serialize` decides to
+ /// fail, or if `T` contains a map with non-string keys.
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_json::to_vec(json) {
+ Ok(body) => {
+ if !req.headers().contains_key(CONTENT_TYPE) {
+ req.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ }
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Disable CORS on fetching the request.
+ ///
+ /// # WASM
+ ///
+ /// This option is only effective with WebAssembly target.
+ ///
+ /// The [request mode][mdn] will be set to 'no-cors'.
+ ///
+ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
+ pub fn fetch_mode_no_cors(self) -> RequestBuilder {
+ self
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ pub fn build(self) -> crate::Result<Request> {
+ self.request
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ ///
+ /// This is similar to [`RequestBuilder::build()`], but also returns the
+ /// embedded `Client`.
+ pub fn build_split(self) -> (Client, crate::Result<Request>) {
+ (self.client, self.request)
+ }
+
+ /// Constructs the Request and sends it to the target URL, returning a
+ /// future Response.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use reqwest::Error;
+ /// #
+ /// # async fn run() -> Result<(), Error> {
+ /// let response = reqwest::Client::new()
+ /// .get("https://hyper.rs")
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn send(self) -> impl Future<Output = Result<Response, crate::Error>> {
+ match self.request {
+ Ok(req) => self.client.execute_request(req),
+ Err(err) => Pending::new_err(err),
+ }
+ }
+
+ /// Attempt to clone the RequestBuilder.
+ ///
+ /// `None` is returned if the RequestBuilder can not be cloned,
+ /// i.e. if the request body is a stream.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use reqwest::Error;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let client = reqwest::Client::new();
+ /// let builder = client.post("http://httpbin.org/post")
+ /// .body("from a &str!");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn try_clone(&self) -> Option<RequestBuilder> {
+ self.request
+ .as_ref()
+ .ok()
+ .and_then(|req| req.try_clone())
+ .map(|req| RequestBuilder {
+ client: self.client.clone(),
+ request: Ok(req),
+ })
+ }
+}
+
+impl fmt::Debug for Request {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
+ }
+}
+
+impl fmt::Debug for RequestBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("RequestBuilder");
+ match self.request {
+ Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
+ Err(ref err) => builder.field("error", err).finish(),
+ }
+ }
+}
+
+fn fmt_request_fields<'a, 'b>(
+ f: &'a mut fmt::DebugStruct<'a, 'b>,
+ req: &Request,
+) -> &'a mut fmt::DebugStruct<'a, 'b> {
+ f.field("method", &req.method)
+ .field("url", &req.url)
+ .field("headers", &req.headers)
+}
+
+/// Check the request URL for a "username:password" type authority, and if
+/// found, remove it from the URL and return it.
+pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>)> {
+ use percent_encoding::percent_decode;
+
+ if url.has_authority() {
+ let username: String = percent_decode(url.username().as_bytes())
+ .decode_utf8()
+ .ok()?
+ .into();
+ let password = url.password().and_then(|pass| {
+ percent_decode(pass.as_bytes())
+ .decode_utf8()
+ .ok()
+ .map(String::from)
+ });
+ if !username.is_empty() || password.is_some() {
+ url.set_username("")
+ .expect("has_authority means set_username shouldn't fail");
+ url.set_password(None)
+ .expect("has_authority means set_password shouldn't fail");
+ return Some((username, password));
+ }
+ }
+
+ None
+}
+
+impl<T> TryFrom<HttpRequest<T>> for Request
+where
+ T: Into<Body>,
+{
+ type Error = crate::Error;
+
+ fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
+ let (parts, body) = req.into_parts();
+ let Parts {
+ method,
+ uri,
+ headers,
+ version,
+ ..
+ } = parts;
+ let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
+ Ok(Request {
+ method,
+ url,
+ headers,
+ body: Some(body.into()),
+ timeout: None,
+ version,
+ })
+ }
+}
+
+impl TryFrom<Request> for HttpRequest<Body> {
+ type Error = crate::Error;
+
+ fn try_from(req: Request) -> crate::Result<Self> {
+ let Request {
+ method,
+ url,
+ headers,
+ body,
+ version,
+ ..
+ } = req;
+
+ let mut req = HttpRequest::builder()
+ .version(version)
+ .method(method)
+ .uri(url.as_str())
+ .body(body.unwrap_or_else(Body::empty))
+ .map_err(crate::error::builder)?;
+
+ *req.headers_mut() = headers;
+ Ok(req)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{Client, HttpRequest, Request, RequestBuilder, Version};
+ use crate::Method;
+ use serde::Serialize;
+ use std::collections::BTreeMap;
+ use std::convert::TryFrom;
+
+ #[test]
+ fn add_query_append() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url);
+
+ let r = r.query(&[("foo", "bar")]);
+ let r = r.query(&[("qux", 3)]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_append_same() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url);
+
+ let r = r.query(&[("foo", "a"), ("foo", "b")]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=a&foo=b"));
+ }
+
+ #[test]
+ fn add_query_struct() {
+ #[derive(Serialize)]
+ struct Params {
+ foo: String,
+ qux: i32,
+ }
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url);
+
+ let params = Params {
+ foo: "bar".into(),
+ qux: 3,
+ };
+
+ let r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_map() {
+ let mut params = BTreeMap::new();
+ params.insert("foo", "bar");
+ params.insert("qux", "three");
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url);
+
+ let r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=three"));
+ }
+
+ #[test]
+ fn test_replace_headers() {
+ use http::HeaderMap;
+
+ let mut headers = HeaderMap::new();
+ headers.insert("foo", "bar".parse().unwrap());
+ headers.append("foo", "baz".parse().unwrap());
+
+ let client = Client::new();
+ let req = client
+ .get("https://hyper.rs")
+ .header("im-a", "keeper")
+ .header("foo", "pop me")
+ .headers(headers)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.headers()["im-a"], "keeper");
+
+ let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
+ assert_eq!(foo.len(), 2);
+ assert_eq!(foo[0], "bar");
+ assert_eq!(foo[1], "baz");
+ }
+
+ #[test]
+ fn normalize_empty_query() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let empty_query: &[(&str, &str)] = &[];
+
+ let req = client
+ .get(some_url)
+ .query(empty_query)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().query(), None);
+ assert_eq!(req.url().as_str(), "https://google.com/");
+ }
+
+ #[test]
+ fn try_clone_reusable() {
+ let client = Client::new();
+ let builder = client
+ .post("http://httpbin.org/post")
+ .header("foo", "bar")
+ .body("from a &str!");
+ let req = builder
+ .try_clone()
+ .expect("clone successful")
+ .build()
+ .expect("request is valid");
+ assert_eq!(req.url().as_str(), "http://httpbin.org/post");
+ assert_eq!(req.method(), Method::POST);
+ assert_eq!(req.headers()["foo"], "bar");
+ }
+
+ #[test]
+ fn try_clone_no_body() {
+ let client = Client::new();
+ let builder = client.get("http://httpbin.org/get");
+ let req = builder
+ .try_clone()
+ .expect("clone successful")
+ .build()
+ .expect("request is valid");
+ assert_eq!(req.url().as_str(), "http://httpbin.org/get");
+ assert_eq!(req.method(), Method::GET);
+ assert!(req.body().is_none());
+ }
+
+ #[test]
+ #[cfg(feature = "stream")]
+ fn try_clone_stream() {
+ let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello"), Ok(" "), Ok("world")];
+ let stream = futures_util::stream::iter(chunks);
+ let client = Client::new();
+ let builder = client
+ .get("http://httpbin.org/get")
+ .body(super::Body::wrap_stream(stream));
+ let clone = builder.try_clone();
+ assert!(clone.is_none());
+ }
+
+ #[test]
+ fn convert_url_authority_into_basic_auth() {
+ let client = Client::new();
+ let some_url = "https://Aladdin:open sesame@localhost/";
+
+ let req = client.get(some_url).build().expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ }
+
+ #[test]
+ fn test_basic_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .basic_auth("Aladdin", Some("open sesame"))
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ assert!(req.headers()["authorization"].is_sensitive());
+ }
+
+ #[test]
+ fn test_bearer_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .bearer_auth("Hold my bear")
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
+ assert!(req.headers()["authorization"].is_sensitive());
+ }
+
+ #[test]
+ fn test_explicit_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let mut header = http::HeaderValue::from_static("in plain sight");
+ header.set_sensitive(true);
+
+ let req = client
+ .get(some_url)
+ .header("hiding", header)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(req.headers()["hiding"], "in plain sight");
+ assert!(req.headers()["hiding"].is_sensitive());
+ }
+
+ #[test]
+ fn convert_from_http_request() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert!(req.body().is_some());
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ }
+
+ #[test]
+ fn set_http_request_version() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .version(Version::HTTP_11)
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert!(req.body().is_some());
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ assert_eq!(req.version(), Version::HTTP_11);
+ }
+
+ #[test]
+ fn builder_split_reassemble() {
+ let builder = {
+ let client = Client::new();
+ client.get("http://example.com")
+ };
+ let (client, inner) = builder.build_split();
+ let request = inner.unwrap();
+ let builder = RequestBuilder::from_parts(client, request);
+ builder.build().unwrap();
+ }
+
+ /*
+ use {body, Method};
+ use super::Client;
+ use header::{Host, Headers, ContentType};
+ use std::collections::HashMap;
+ use serde_urlencoded;
+ use serde_json;
+
+ #[test]
+ fn basic_get_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Get);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_head_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.head(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Head);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_post_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Post);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_put_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.put(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Put);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_patch_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.patch(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Patch);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_delete_request() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let r = client.delete(some_url).unwrap().build();
+
+ assert_eq!(r.method, Method::Delete);
+ assert_eq!(r.url.as_str(), some_url);
+ }
+
+ #[test]
+ fn add_header() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let header = Host {
+ hostname: "google.com".to_string(),
+ port: None,
+ };
+
+ // Add a copy of the header to the request builder
+ let r = r.header(header.clone()).build();
+
+ // then check it was actually added
+ assert_eq!(r.headers.get::<Host>(), Some(&header));
+ }
+
+ #[test]
+ fn add_headers() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let header = Host {
+ hostname: "google.com".to_string(),
+ port: None,
+ };
+
+ let mut headers = Headers::new();
+ headers.set(header);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers, headers);
+ }
+
+ #[test]
+ fn add_headers_multi() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let header = Host {
+ hostname: "google.com".to_string(),
+ port: None,
+ };
+
+ let mut headers = Headers::new();
+ headers.set(header);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers, headers);
+ }
+
+ #[test]
+ fn add_body() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let body = "Some interesting content";
+
+ let r = r.body(body).build();
+
+ let buf = body::read_to_string(r.body.unwrap()).unwrap();
+
+ assert_eq!(buf, body);
+ }
+
+ #[test]
+ fn add_form() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let mut form_data = HashMap::new();
+ form_data.insert("foo", "bar");
+
+ let r = r.form(&form_data).unwrap().build();
+
+ // Make sure the content type was set
+ assert_eq!(r.headers.get::<ContentType>(),
+ Some(&ContentType::form_url_encoded()));
+
+ let buf = body::read_to_string(r.body.unwrap()).unwrap();
+
+ let body_should_be = serde_urlencoded::to_string(&form_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ fn add_json() {
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+
+ let mut json_data = HashMap::new();
+ json_data.insert("foo", "bar");
+
+ let r = r.json(&json_data).unwrap().build();
+
+ // Make sure the content type was set
+ assert_eq!(r.headers.get::<ContentType>(), Some(&ContentType::json()));
+
+ let buf = body::read_to_string(r.body.unwrap()).unwrap();
+
+ let body_should_be = serde_json::to_string(&json_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ fn add_json_fail() {
+ use serde::{Serialize, Serializer};
+ use serde::ser::Error;
+ struct MyStruct;
+ impl Serialize for MyStruct {
+ fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+ where S: Serializer
+ {
+ Err(S::Error::custom("nope"))
+ }
+ }
+
+ let client = Client::new().unwrap();
+ let some_url = "https://google.com/";
+ let mut r = client.post(some_url).unwrap();
+ let json_data = MyStruct{};
+ assert!(r.json(&json_data).unwrap_err().is_serialization());
+ }
+ */
+}
diff --git a/vendor/reqwest/src/async_impl/response.rs b/vendor/reqwest/src/async_impl/response.rs
new file mode 100644
index 000000000..340e54174
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/response.rs
@@ -0,0 +1,448 @@
+use std::borrow::Cow;
+use std::fmt;
+use std::net::SocketAddr;
+use std::pin::Pin;
+
+use bytes::Bytes;
+use encoding_rs::{Encoding, UTF_8};
+use futures_util::stream::StreamExt;
+use hyper::client::connect::HttpInfo;
+use hyper::{HeaderMap, StatusCode, Version};
+use mime::Mime;
+#[cfg(feature = "json")]
+use serde::de::DeserializeOwned;
+#[cfg(feature = "json")]
+use serde_json;
+use tokio::time::Sleep;
+use url::Url;
+
+use super::body::Body;
+use super::decoder::{Accepts, Decoder};
+#[cfg(feature = "cookies")]
+use crate::cookie;
+use crate::response::ResponseUrl;
+
+/// A Response to a submitted `Request`.
+pub struct Response {
+ pub(super) res: hyper::Response<Decoder>,
+ // Boxed to save space (11 words to 1 word), and it's not accessed
+ // frequently internally.
+ url: Box<Url>,
+}
+
+impl Response {
+ pub(super) fn new(
+ res: hyper::Response<hyper::Body>,
+ url: Url,
+ accepts: Accepts,
+ timeout: Option<Pin<Box<Sleep>>>,
+ ) -> Response {
+ let (mut parts, body) = res.into_parts();
+ let decoder = Decoder::detect(&mut parts.headers, Body::response(body, timeout), accepts);
+ let res = hyper::Response::from_parts(parts, decoder);
+
+ Response {
+ res,
+ url: Box::new(url),
+ }
+ }
+
+ /// Get the `StatusCode` of this `Response`.
+ #[inline]
+ pub fn status(&self) -> StatusCode {
+ self.res.status()
+ }
+
+ /// Get the HTTP `Version` of this `Response`.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.res.version()
+ }
+
+ /// Get the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.res.headers()
+ }
+
+ /// Get a mutable reference to the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.res.headers_mut()
+ }
+
+ /// Get the content-length of this response, if known.
+ ///
+ /// Reasons it may not be known:
+ ///
+ /// - The server didn't send a `content-length` header.
+ /// - The response is compressed and automatically decoded (thus changing
+ /// the actual decoded length).
+ pub fn content_length(&self) -> Option<u64> {
+ use hyper::body::HttpBody;
+
+ HttpBody::size_hint(self.res.body()).exact()
+ }
+
+ /// Retrieve the cookies contained in the response.
+ ///
+ /// Note that invalid 'Set-Cookie' headers will be ignored.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
+ cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok)
+ }
+
+ /// Get the final `Url` of this `Response`.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /// Get the remote address used to get this `Response`.
+ pub fn remote_addr(&self) -> Option<SocketAddr> {
+ self.res
+ .extensions()
+ .get::<HttpInfo>()
+ .map(|info| info.remote_addr())
+ }
+
+ /// Returns a reference to the associated extensions.
+ pub fn extensions(&self) -> &http::Extensions {
+ self.res.extensions()
+ }
+
+ /// Returns a mutable reference to the associated extensions.
+ pub fn extensions_mut(&mut self) -> &mut http::Extensions {
+ self.res.extensions_mut()
+ }
+
+ // body methods
+
+ /// Get the full response text.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// Encoding is determined from the `charset` parameter of `Content-Type` header,
+ /// and defaults to `utf-8` if not presented.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let content = reqwest::get("http://httpbin.org/range/26")
+ /// .await?
+ /// .text()
+ /// .await?;
+ ///
+ /// println!("text: {:?}", content);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn text(self) -> crate::Result<String> {
+ self.text_with_charset("utf-8").await
+ }
+
+ /// Get the full response text given a specific encoding.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// You can provide a default encoding for decoding the raw message, while the
+ /// `charset` parameter of `Content-Type` header is still prioritized. For more information
+ /// about the possible encoding name, please go to [`encoding_rs`] docs.
+ ///
+ /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let content = reqwest::get("http://httpbin.org/range/26")
+ /// .await?
+ /// .text_with_charset("utf-8")
+ /// .await?;
+ ///
+ /// println!("text: {:?}", content);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
+ let content_type = self
+ .headers()
+ .get(crate::header::CONTENT_TYPE)
+ .and_then(|value| value.to_str().ok())
+ .and_then(|value| value.parse::<Mime>().ok());
+ let encoding_name = content_type
+ .as_ref()
+ .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
+ .unwrap_or(default_encoding);
+ let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
+
+ let full = self.bytes().await?;
+
+ let (text, _, _) = encoding.decode(&full);
+ if let Cow::Owned(s) = text {
+ return Ok(s);
+ }
+ unsafe {
+ // decoding returned Cow::Borrowed, meaning these bytes
+ // are already valid utf8
+ Ok(String::from_utf8_unchecked(full.to_vec()))
+ }
+ }
+
+ /// Try to deserialize the response body as JSON.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # extern crate serde;
+ /// #
+ /// # use reqwest::Error;
+ /// # use serde::Deserialize;
+ /// #
+ /// // This `derive` requires the `serde` dependency.
+ /// #[derive(Deserialize)]
+ /// struct Ip {
+ /// origin: String,
+ /// }
+ ///
+ /// # async fn run() -> Result<(), Error> {
+ /// let ip = reqwest::get("http://httpbin.org/ip")
+ /// .await?
+ /// .json::<Ip>()
+ /// .await?;
+ ///
+ /// println!("ip: {}", ip.origin);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() { }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the response body is not in JSON format
+ /// or it cannot be properly deserialized to target type `T`. For more
+ /// details please see [`serde_json::from_reader`].
+ ///
+ /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
+ let full = self.bytes().await?;
+
+ serde_json::from_slice(&full).map_err(crate::error::decode)
+ }
+
+ /// Get the full response body as `Bytes`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let bytes = reqwest::get("http://httpbin.org/ip")
+ /// .await?
+ /// .bytes()
+ /// .await?;
+ ///
+ /// println!("bytes: {:?}", bytes);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn bytes(self) -> crate::Result<Bytes> {
+ hyper::body::to_bytes(self.res.into_body()).await
+ }
+
+ /// Stream a chunk of the response body.
+ ///
+ /// When the response body has been exhausted, this will return `None`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut res = reqwest::get("https://hyper.rs").await?;
+ ///
+ /// while let Some(chunk) = res.chunk().await? {
+ /// println!("Chunk: {:?}", chunk);
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {
+ if let Some(item) = self.res.body_mut().next().await {
+ Ok(Some(item?))
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Convert the response into a `Stream` of `Bytes` from the body.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use futures_util::StreamExt;
+ ///
+ /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let mut stream = reqwest::get("http://httpbin.org/ip")
+ /// .await?
+ /// .bytes_stream();
+ ///
+ /// while let Some(item) = stream.next().await {
+ /// println!("Chunk: {:?}", item?);
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `stream` feature to be enabled.
+ #[cfg(feature = "stream")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
+ pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
+ self.res.into_body()
+ }
+
+ // util methods
+
+ /// Turn a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use reqwest::Response;
+ /// fn on_response(res: Response) {
+ /// match res.error_for_status() {
+ /// Ok(_res) => (),
+ /// Err(err) => {
+ /// // asserting a 400 as an example
+ /// // it could be any status between 400...599
+ /// assert_eq!(
+ /// err.status(),
+ /// Some(reqwest::StatusCode::BAD_REQUEST)
+ /// );
+ /// }
+ /// }
+ /// }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status(self) -> crate::Result<Self> {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url, status))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Turn a reference to a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use reqwest::Response;
+ /// fn on_response(res: &Response) {
+ /// match res.error_for_status_ref() {
+ /// Ok(_res) => (),
+ /// Err(err) => {
+ /// // asserting a 400 as an example
+ /// // it could be any status between 400...599
+ /// assert_eq!(
+ /// err.status(),
+ /// Some(reqwest::StatusCode::BAD_REQUEST)
+ /// );
+ /// }
+ /// }
+ /// }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url.clone(), status))
+ } else {
+ Ok(self)
+ }
+ }
+
+ // private
+
+ // The Response's body is an implementation detail.
+ // You no longer need to get a reference to it, there are async methods
+ // on the `Response` itself.
+ //
+ // This method is just used by the blocking API.
+ #[cfg(feature = "blocking")]
+ pub(crate) fn body_mut(&mut self) -> &mut Decoder {
+ self.res.body_mut()
+ }
+}
+
+impl fmt::Debug for Response {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Response")
+ .field("url", self.url())
+ .field("status", &self.status())
+ .field("headers", self.headers())
+ .finish()
+ }
+}
+
+impl<T: Into<Body>> From<http::Response<T>> for Response {
+ fn from(r: http::Response<T>) -> Response {
+ let (mut parts, body) = r.into_parts();
+ let body = body.into();
+ let decoder = Decoder::detect(&mut parts.headers, body, Accepts::none());
+ let url = parts
+ .extensions
+ .remove::<ResponseUrl>()
+ .unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
+ let url = url.0;
+ let res = hyper::Response::from_parts(parts, decoder);
+ Response {
+ res,
+ url: Box::new(url),
+ }
+ }
+}
+
+/// A `Response` can be piped as the `Body` of another request.
+impl From<Response> for Body {
+ fn from(r: Response) -> Body {
+ Body::stream(r.res.into_body())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Response;
+ use crate::ResponseBuilderExt;
+ use http::response::Builder;
+ use url::Url;
+
+ #[test]
+ fn test_from_http_response() {
+ let url = Url::parse("http://example.com").unwrap();
+ let response = Builder::new()
+ .status(200)
+ .url(url.clone())
+ .body("foo")
+ .unwrap();
+ let response = Response::from(response);
+
+ assert_eq!(response.status(), 200);
+ assert_eq!(*response.url(), url);
+ }
+}
diff --git a/vendor/reqwest/src/async_impl/upgrade.rs b/vendor/reqwest/src/async_impl/upgrade.rs
new file mode 100644
index 000000000..4a69b4db5
--- /dev/null
+++ b/vendor/reqwest/src/async_impl/upgrade.rs
@@ -0,0 +1,73 @@
+use std::pin::Pin;
+use std::task::{self, Poll};
+use std::{fmt, io};
+
+use futures_util::TryFutureExt;
+use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+
+/// An upgraded HTTP connection.
+pub struct Upgraded {
+ inner: hyper::upgrade::Upgraded,
+}
+
+impl AsyncRead for Upgraded {
+ fn poll_read(
+ mut self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ Pin::new(&mut self.inner).poll_read(cx, buf)
+ }
+}
+
+impl AsyncWrite for Upgraded {
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ Pin::new(&mut self.inner).poll_write(cx, buf)
+ }
+
+ fn poll_write_vectored(
+ mut self: Pin<&mut Self>,
+ cx: &mut task::Context<'_>,
+ bufs: &[io::IoSlice<'_>],
+ ) -> Poll<io::Result<usize>> {
+ Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
+ Pin::new(&mut self.inner).poll_flush(cx)
+ }
+
+ fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
+ Pin::new(&mut self.inner).poll_shutdown(cx)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
+}
+
+impl fmt::Debug for Upgraded {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Upgraded").finish()
+ }
+}
+
+impl From<hyper::upgrade::Upgraded> for Upgraded {
+ fn from(inner: hyper::upgrade::Upgraded) -> Self {
+ Upgraded { inner }
+ }
+}
+
+impl super::response::Response {
+ /// Consumes the response and returns a future for a possible HTTP upgrade.
+ pub async fn upgrade(self) -> crate::Result<Upgraded> {
+ hyper::upgrade::on(self.res)
+ .map_ok(Upgraded::from)
+ .map_err(crate::error::upgrade)
+ .await
+ }
+}
diff --git a/vendor/reqwest/src/blocking/body.rs b/vendor/reqwest/src/blocking/body.rs
new file mode 100644
index 000000000..1179a5485
--- /dev/null
+++ b/vendor/reqwest/src/blocking/body.rs
@@ -0,0 +1,352 @@
+use std::fmt;
+use std::fs::File;
+use std::future::Future;
+#[cfg(feature = "multipart")]
+use std::io::Cursor;
+use std::io::{self, Read};
+use std::mem;
+use std::ptr;
+
+use bytes::buf::UninitSlice;
+use bytes::Bytes;
+
+use crate::async_impl;
+
+/// The body of a `Request`.
+///
+/// In most cases, this is not needed directly, as the
+/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
+/// passing many things (like a string or vector of bytes).
+///
+/// [builder]: ./struct.RequestBuilder.html#method.body
+#[derive(Debug)]
+pub struct Body {
+ kind: Kind,
+}
+
+impl Body {
+ /// Instantiate a `Body` from a reader.
+ ///
+ /// # Note
+ ///
+ /// While allowing for many types to be used, these bodies do not have
+ /// a way to reset to the beginning and be reused. This means that when
+ /// encountering a 307 or 308 status code, instead of repeating the
+ /// request at the new location, the `Response` will be returned with
+ /// the redirect status code set.
+ ///
+ /// ```rust
+ /// # use std::fs::File;
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = File::open("national_secrets.txt")?;
+ /// let body = Body::new(file);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// If you have a set of bytes, like `String` or `Vec<u8>`, using the
+ /// `From` implementations for `Body` will store the data in a manner
+ /// it can be reused.
+ ///
+ /// ```rust
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let s = "A stringy body";
+ /// let body = Body::from(s);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn new<R: Read + Send + 'static>(reader: R) -> Body {
+ Body {
+ kind: Kind::Reader(Box::from(reader), None),
+ }
+ }
+
+ /// Create a `Body` from a `Read` where the size is known in advance
+ /// but the data should not be fully loaded into memory. This will
+ /// set the `Content-Length` header and stream from the `Read`.
+ ///
+ /// ```rust
+ /// # use std::fs::File;
+ /// # use reqwest::blocking::Body;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = File::open("a_large_file.txt")?;
+ /// let file_size = file.metadata()?.len();
+ /// let body = Body::sized(file, file_size);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn sized<R: Read + Send + 'static>(reader: R, len: u64) -> Body {
+ Body {
+ kind: Kind::Reader(Box::from(reader), Some(len)),
+ }
+ }
+
+ /// Returns the body as a byte slice if the body is already buffered in
+ /// memory. For streamed requests this method returns `None`.
+ pub fn as_bytes(&self) -> Option<&[u8]> {
+ match self.kind {
+ Kind::Reader(_, _) => None,
+ Kind::Bytes(ref bytes) => Some(bytes.as_ref()),
+ }
+ }
+
+ /// Converts streamed requests to their buffered equivalent and
+ /// returns a reference to the buffer. If the request is already
+ /// buffered, this has no effect.
+ ///
+ /// Be aware that for large requests this method is expensive
+ /// and may cause your program to run out of memory.
+ pub fn buffer(&mut self) -> Result<&[u8], crate::Error> {
+ match self.kind {
+ Kind::Reader(ref mut reader, maybe_len) => {
+ let mut bytes = if let Some(len) = maybe_len {
+ Vec::with_capacity(len as usize)
+ } else {
+ Vec::new()
+ };
+ io::copy(reader, &mut bytes).map_err(crate::error::builder)?;
+ self.kind = Kind::Bytes(bytes.into());
+ self.buffer()
+ }
+ Kind::Bytes(ref bytes) => Ok(bytes.as_ref()),
+ }
+ }
+
+ #[cfg(feature = "multipart")]
+ pub(crate) fn len(&self) -> Option<u64> {
+ match self.kind {
+ Kind::Reader(_, len) => len,
+ Kind::Bytes(ref bytes) => Some(bytes.len() as u64),
+ }
+ }
+
+ #[cfg(feature = "multipart")]
+ pub(crate) fn into_reader(self) -> Reader {
+ match self.kind {
+ Kind::Reader(r, _) => Reader::Reader(r),
+ Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)),
+ }
+ }
+
+ pub(crate) fn into_async(self) -> (Option<Sender>, async_impl::Body, Option<u64>) {
+ match self.kind {
+ Kind::Reader(read, len) => {
+ let (tx, rx) = hyper::Body::channel();
+ let tx = Sender {
+ body: (read, len),
+ tx,
+ };
+ (Some(tx), async_impl::Body::wrap(rx), len)
+ }
+ Kind::Bytes(chunk) => {
+ let len = chunk.len() as u64;
+ (None, async_impl::Body::reusable(chunk), Some(len))
+ }
+ }
+ }
+
+ pub(crate) fn try_clone(&self) -> Option<Body> {
+ self.kind.try_clone().map(|kind| Body { kind })
+ }
+}
+
+enum Kind {
+ Reader(Box<dyn Read + Send>, Option<u64>),
+ Bytes(Bytes),
+}
+
+impl Kind {
+ fn try_clone(&self) -> Option<Kind> {
+ match self {
+ Kind::Reader(..) => None,
+ Kind::Bytes(v) => Some(Kind::Bytes(v.clone())),
+ }
+ }
+}
+
+impl From<Vec<u8>> for Body {
+ #[inline]
+ fn from(v: Vec<u8>) -> Body {
+ Body {
+ kind: Kind::Bytes(v.into()),
+ }
+ }
+}
+
+impl From<String> for Body {
+ #[inline]
+ fn from(s: String) -> Body {
+ s.into_bytes().into()
+ }
+}
+
+impl From<&'static [u8]> for Body {
+ #[inline]
+ fn from(s: &'static [u8]) -> Body {
+ Body {
+ kind: Kind::Bytes(Bytes::from_static(s)),
+ }
+ }
+}
+
+impl From<&'static str> for Body {
+ #[inline]
+ fn from(s: &'static str) -> Body {
+ s.as_bytes().into()
+ }
+}
+
+impl From<File> for Body {
+ #[inline]
+ fn from(f: File) -> Body {
+ let len = f.metadata().map(|m| m.len()).ok();
+ Body {
+ kind: Kind::Reader(Box::new(f), len),
+ }
+ }
+}
+impl From<Bytes> for Body {
+ #[inline]
+ fn from(b: Bytes) -> Body {
+ Body {
+ kind: Kind::Bytes(b),
+ }
+ }
+}
+
+impl fmt::Debug for Kind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Kind::Reader(_, ref v) => f
+ .debug_struct("Reader")
+ .field("length", &DebugLength(v))
+ .finish(),
+ Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
+ }
+ }
+}
+
+struct DebugLength<'a>(&'a Option<u64>);
+
+impl<'a> fmt::Debug for DebugLength<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self.0 {
+ Some(ref len) => fmt::Debug::fmt(len, f),
+ None => f.write_str("Unknown"),
+ }
+ }
+}
+
+#[cfg(feature = "multipart")]
+pub(crate) enum Reader {
+ Reader(Box<dyn Read + Send>),
+ Bytes(Cursor<Bytes>),
+}
+
+#[cfg(feature = "multipart")]
+impl Read for Reader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ match *self {
+ Reader::Reader(ref mut rdr) => rdr.read(buf),
+ Reader::Bytes(ref mut rdr) => rdr.read(buf),
+ }
+ }
+}
+
+pub(crate) struct Sender {
+ body: (Box<dyn Read + Send>, Option<u64>),
+ tx: hyper::body::Sender,
+}
+
+async fn send_future(sender: Sender) -> Result<(), crate::Error> {
+ use bytes::{BufMut, BytesMut};
+ use std::cmp;
+
+ let con_len = sender.body.1;
+ let cap = cmp::min(sender.body.1.unwrap_or(8192), 8192);
+ let mut written = 0;
+ let mut buf = BytesMut::with_capacity(cap as usize);
+ let mut body = sender.body.0;
+ // Put in an option so that it can be consumed on error to call abort()
+ let mut tx = Some(sender.tx);
+
+ loop {
+ if Some(written) == con_len {
+ // Written up to content-length, so stop.
+ return Ok(());
+ }
+
+ // The input stream is read only if the buffer is empty so
+ // that there is only one read in the buffer at any time.
+ //
+ // We need to know whether there is any data to send before
+ // we check the transmission channel (with poll_ready below)
+ // because somestimes the receiver disappears as soon as is
+ // considers the data is completely transmitted, which may
+ // be true.
+ //
+ // The use case is a web server that closes its
+ // input stream as soon as the data received is valid JSON.
+ // This behaviour is questionable, but it exists and the
+ // fact is that there is actually no remaining data to read.
+ if buf.is_empty() {
+ if buf.remaining_mut() == 0 {
+ buf.reserve(8192);
+ // zero out the reserved memory
+ let uninit = buf.chunk_mut();
+ unsafe {
+ ptr::write_bytes(uninit.as_mut_ptr(), 0, uninit.len());
+ }
+ }
+
+ let bytes = unsafe { mem::transmute::<&mut UninitSlice, &mut [u8]>(buf.chunk_mut()) };
+ match body.read(bytes) {
+ Ok(0) => {
+ // The buffer was empty and nothing's left to
+ // read. Return.
+ return Ok(());
+ }
+ Ok(n) => unsafe {
+ buf.advance_mut(n);
+ },
+ Err(e) => {
+ tx.take().expect("tx only taken on error").abort();
+ return Err(crate::error::body(e));
+ }
+ }
+ }
+
+ // The only way to get here is when the buffer is not empty.
+ // We can check the transmission channel
+
+ let buf_len = buf.len() as u64;
+ tx.as_mut()
+ .expect("tx only taken on error")
+ .send_data(buf.split().freeze())
+ .await
+ .map_err(crate::error::body)?;
+
+ written += buf_len;
+ }
+}
+
+impl Sender {
+ // A `Future` that may do blocking read calls.
+ // As a `Future`, this integrates easily with `wait::timeout`.
+ pub(crate) fn send(self) -> impl Future<Output = Result<(), crate::Error>> {
+ send_future(self)
+ }
+}
+
+// useful for tests, but not publicly exposed
+#[cfg(test)]
+pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
+ let mut s = String::new();
+ match body.kind {
+ Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
+ Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
+ }
+ .map(|_| s)
+}
diff --git a/vendor/reqwest/src/blocking/client.rs b/vendor/reqwest/src/blocking/client.rs
new file mode 100644
index 000000000..e6ec6735a
--- /dev/null
+++ b/vendor/reqwest/src/blocking/client.rs
@@ -0,0 +1,1151 @@
+#[cfg(any(feature = "native-tls", feature = "__rustls",))]
+use std::any::Any;
+use std::convert::TryInto;
+use std::fmt;
+use std::future::Future;
+use std::net::IpAddr;
+use std::net::SocketAddr;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+use http::header::HeaderValue;
+use log::{error, trace};
+use tokio::sync::{mpsc, oneshot};
+
+use super::request::{Request, RequestBuilder};
+use super::response::Response;
+use super::wait;
+#[cfg(feature = "__tls")]
+use crate::tls;
+#[cfg(feature = "__tls")]
+use crate::Certificate;
+#[cfg(any(feature = "native-tls", feature = "__rustls"))]
+use crate::Identity;
+use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy};
+
+/// A `Client` to make Requests with.
+///
+/// The Client has various configuration values to tweak, but the defaults
+/// are set to what is usually the most commonly desired value. To configure a
+/// `Client`, use `Client::builder()`.
+///
+/// The `Client` holds a connection pool internally, so it is advised that
+/// you create one and **reuse** it.
+///
+/// # Examples
+///
+/// ```rust
+/// use reqwest::blocking::Client;
+/// #
+/// # fn run() -> Result<(), reqwest::Error> {
+/// let client = Client::new();
+/// let resp = client.get("http://httpbin.org/").send()?;
+/// # drop(resp);
+/// # Ok(())
+/// # }
+///
+/// ```
+#[derive(Clone)]
+pub struct Client {
+ inner: ClientHandle,
+}
+
+/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
+///
+/// # Example
+///
+/// ```
+/// # fn run() -> Result<(), reqwest::Error> {
+/// use std::time::Duration;
+///
+/// let client = reqwest::blocking::Client::builder()
+/// .timeout(Duration::from_secs(10))
+/// .build()?;
+/// # Ok(())
+/// # }
+/// ```
+#[must_use]
+pub struct ClientBuilder {
+ inner: async_impl::ClientBuilder,
+ timeout: Timeout,
+}
+
+impl Default for ClientBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl ClientBuilder {
+ /// Constructs a new `ClientBuilder`.
+ ///
+ /// This is the same as `Client::builder()`.
+ pub fn new() -> ClientBuilder {
+ ClientBuilder {
+ inner: async_impl::ClientBuilder::new(),
+ timeout: Timeout::default(),
+ }
+ }
+
+ /// Returns a `Client` that uses this `ClientBuilder` configuration.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if called from within an async runtime. See docs on
+ /// [`reqwest::blocking`][crate::blocking] for details.
+ pub fn build(self) -> crate::Result<Client> {
+ ClientHandle::new(self).map(|handle| Client { inner: handle })
+ }
+
+ // Higher-level options
+
+ /// Sets the `User-Agent` header to be used by this client.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn doc() -> Result<(), reqwest::Error> {
+ /// // Name your user agent after your app?
+ /// static APP_USER_AGENT: &str = concat!(
+ /// env!("CARGO_PKG_NAME"),
+ /// "/",
+ /// env!("CARGO_PKG_VERSION"),
+ /// );
+ ///
+ /// let client = reqwest::blocking::Client::builder()
+ /// .user_agent(APP_USER_AGENT)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn user_agent<V>(self, value: V) -> ClientBuilder
+ where
+ V: TryInto<HeaderValue>,
+ V::Error: Into<http::Error>,
+ {
+ self.with_inner(move |inner| inner.user_agent(value))
+ }
+
+ /// Sets the default headers for every request.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # fn build_client() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ /// headers.insert(header::AUTHORIZATION, header::HeaderValue::from_static("secret"));
+ ///
+ /// // Consider marking security-sensitive headers with `set_sensitive`.
+ /// let mut auth_value = header::HeaderValue::from_static("secret");
+ /// auth_value.set_sensitive(true);
+ /// headers.insert(header::AUTHORIZATION, auth_value);
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client.get("https://www.rust-lang.org").send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Override the default headers:
+ ///
+ /// ```rust
+ /// use reqwest::header;
+ /// # fn build_client() -> Result<(), reqwest::Error> {
+ /// let mut headers = header::HeaderMap::new();
+ /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value"));
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .default_headers(headers)
+ /// .build()?;
+ /// let res = client
+ /// .get("https://www.rust-lang.org")
+ /// .header("X-MY-HEADER", "new_value")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn default_headers(self, headers: header::HeaderMap) -> ClientBuilder {
+ self.with_inner(move |inner| inner.default_headers(headers))
+ }
+
+ /// Enable a persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be preserved and included in
+ /// additional requests.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_store(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.cookie_store(enable))
+ }
+
+ /// Set the persistent cookie store for the client.
+ ///
+ /// Cookies received in responses will be passed to this store, and
+ /// additional requests will query this store for cookies.
+ ///
+ /// By default, no cookie store is used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookie_provider<C: crate::cookie::CookieStore + 'static>(
+ self,
+ cookie_store: Arc<C>,
+ ) -> ClientBuilder {
+ self.with_inner(|inner| inner.cookie_provider(cookie_store))
+ }
+
+ /// Enable auto gzip decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto gzip decompresson is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `gzip` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `gzip` feature to be enabled
+ #[cfg(feature = "gzip")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
+ pub fn gzip(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.gzip(enable))
+ }
+
+ /// Enable auto brotli decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto brotli decompression is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `br`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `brotli` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `brotli` feature to be enabled
+ #[cfg(feature = "brotli")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))]
+ pub fn brotli(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.brotli(enable))
+ }
+
+ /// Enable auto deflate decompression by checking the `Content-Encoding` response header.
+ ///
+ /// If auto deflate decompresson is turned on:
+ ///
+ /// - When sending a request and if the request's headers do not already contain
+ /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`.
+ /// The request body is **not** automatically compressed.
+ /// - When receiving a response, if it's headers contain a `Content-Encoding` value that
+ /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the
+ /// headers' set. The response body is automatically decompressed.
+ ///
+ /// If the `deflate` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `deflate` feature to be enabled
+ #[cfg(feature = "deflate")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))]
+ pub fn deflate(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.deflate(enable))
+ }
+
+ /// Disable auto response body gzip decompression.
+ ///
+ /// This method exists even if the optional `gzip` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use gzip decompression
+ /// even if another dependency were to enable the optional `gzip` feature.
+ pub fn no_gzip(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_gzip())
+ }
+
+ /// Disable auto response body brotli decompression.
+ ///
+ /// This method exists even if the optional `brotli` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use brotli decompression
+ /// even if another dependency were to enable the optional `brotli` feature.
+ pub fn no_brotli(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_brotli())
+ }
+
+ /// Disable auto response body deflate decompression.
+ ///
+ /// This method exists even if the optional `deflate` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use deflate decompression
+ /// even if another dependency were to enable the optional `deflate` feature.
+ pub fn no_deflate(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_deflate())
+ }
+
+ // Redirect options
+
+ /// Set a `redirect::Policy` for this client.
+ ///
+ /// Default will follow redirects up to a maximum of 10.
+ pub fn redirect(self, policy: redirect::Policy) -> ClientBuilder {
+ self.with_inner(move |inner| inner.redirect(policy))
+ }
+
+ /// Enable or disable automatic setting of the `Referer` header.
+ ///
+ /// Default is `true`.
+ pub fn referer(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.referer(enable))
+ }
+
+ // Proxy options
+
+ /// Add a `Proxy` to the list of proxies the `Client` will use.
+ ///
+ /// # Note
+ ///
+ /// Adding a proxy will disable the automatic usage of the "system" proxy.
+ pub fn proxy(self, proxy: Proxy) -> ClientBuilder {
+ self.with_inner(move |inner| inner.proxy(proxy))
+ }
+
+ /// Clear all `Proxies`, so `Client` will use no proxy anymore.
+ ///
+ /// # Note
+ /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()]
+ /// on all desired proxies instead.
+ ///
+ /// This also disables the automatic usage of the "system" proxy.
+ pub fn no_proxy(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.no_proxy())
+ }
+
+ // Timeout options
+
+ /// Set a timeout for connect, read and write operations of a `Client`.
+ ///
+ /// Default is 30 seconds.
+ ///
+ /// Pass `None` to disable timeout.
+ pub fn timeout<T>(mut self, timeout: T) -> ClientBuilder
+ where
+ T: Into<Option<Duration>>,
+ {
+ self.timeout = Timeout(timeout.into());
+ self
+ }
+
+ /// Set a timeout for only the connect phase of a `Client`.
+ ///
+ /// Default is `None`.
+ pub fn connect_timeout<T>(self, timeout: T) -> ClientBuilder
+ where
+ T: Into<Option<Duration>>,
+ {
+ let timeout = timeout.into();
+ if let Some(dur) = timeout {
+ self.with_inner(|inner| inner.connect_timeout(dur))
+ } else {
+ self
+ }
+ }
+
+ /// Set whether connections should emit verbose logs.
+ ///
+ /// Enabling this option will emit [log][] messages at the `TRACE` level
+ /// for read and write operations on connections.
+ ///
+ /// [log]: https://crates.io/crates/log
+ pub fn connection_verbose(self, verbose: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.connection_verbose(verbose))
+ }
+
+ // HTTP options
+
+ /// Set an optional timeout for idle sockets being kept-alive.
+ ///
+ /// Pass `None` to disable timeout.
+ ///
+ /// Default is 90 seconds.
+ pub fn pool_idle_timeout<D>(self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.with_inner(|inner| inner.pool_idle_timeout(val))
+ }
+
+ /// Sets the maximum idle connection per host allowed in the pool.
+ pub fn pool_max_idle_per_host(self, max: usize) -> ClientBuilder {
+ self.with_inner(move |inner| inner.pool_max_idle_per_host(max))
+ }
+
+ /// Send headers as title case instead of lowercase.
+ pub fn http1_title_case_headers(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_title_case_headers())
+ }
+
+ /// Set whether HTTP/1 connections will accept obsolete line folding for
+ /// header values.
+ ///
+ /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
+ /// parsing.
+ pub fn http1_allow_obsolete_multiline_headers_in_responses(self, value: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_allow_obsolete_multiline_headers_in_responses(value))
+ }
+
+ /// Only use HTTP/1.
+ pub fn http1_only(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http1_only())
+ }
+
+ /// Allow HTTP/0.9 responses
+ pub fn http09_responses(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http09_responses())
+ }
+
+ /// Only use HTTP/2.
+ pub fn http2_prior_knowledge(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_prior_knowledge())
+ }
+
+ /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control.
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_stream_window_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_initial_stream_window_size(sz))
+ }
+
+ /// Sets the max connection-level flow control for HTTP2
+ ///
+ /// Default is currently 65,535 but may change internally to optimize for common uses.
+ pub fn http2_initial_connection_window_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_initial_connection_window_size(sz))
+ }
+
+ /// Sets whether to use an adaptive flow control.
+ ///
+ /// Enabling this will override the limits set in `http2_initial_stream_window_size` and
+ /// `http2_initial_connection_window_size`.
+ pub fn http2_adaptive_window(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_adaptive_window(enabled))
+ }
+
+ /// Sets the maximum frame size to use for HTTP2.
+ ///
+ /// Default is currently 16,384 but may change internally to optimize for common uses.
+ pub fn http2_max_frame_size(self, sz: impl Into<Option<u32>>) -> ClientBuilder {
+ self.with_inner(|inner| inner.http2_max_frame_size(sz))
+ }
+
+ // TCP options
+
+ /// Set whether sockets have `TCP_NODELAY` enabled.
+ ///
+ /// Default is `true`.
+ pub fn tcp_nodelay(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.tcp_nodelay(enabled))
+ }
+
+ /// Bind to a local IP Address.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::net::IpAddr;
+ /// let local_addr = IpAddr::from([12, 4, 1, 8]);
+ /// let client = reqwest::blocking::Client::builder()
+ /// .local_address(local_addr)
+ /// .build().unwrap();
+ /// ```
+ pub fn local_address<T>(self, addr: T) -> ClientBuilder
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ self.with_inner(move |inner| inner.local_address(addr))
+ }
+
+ /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
+ ///
+ /// If `None`, the option will not be set.
+ pub fn tcp_keepalive<D>(self, val: D) -> ClientBuilder
+ where
+ D: Into<Option<Duration>>,
+ {
+ self.with_inner(move |inner| inner.tcp_keepalive(val))
+ }
+
+ // TLS options
+
+ /// Add a custom root certificate.
+ ///
+ /// This allows connecting to a server that has a self-signed
+ /// certificate for example. This **does not** replace the existing
+ /// trusted store.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn build_client() -> Result<(), Box<dyn std::error::Error>> {
+ /// // read a local binary DER encoded certificate
+ /// let der = std::fs::read("my-cert.der")?;
+ ///
+ /// // create a certificate
+ /// let cert = reqwest::Certificate::from_der(&der)?;
+ ///
+ /// // get a client builder
+ /// let client = reqwest::blocking::Client::builder()
+ /// .add_root_certificate(cert)
+ /// .build()?;
+ /// # drop(client);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder {
+ self.with_inner(move |inner| inner.add_root_certificate(cert))
+ }
+
+ /// Controls the use of built-in system certificates during certificate validation.
+ ///
+ /// Defaults to `true` -- built-in system certs will be used.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_built_in_root_certs(self, tls_built_in_root_certs: bool) -> ClientBuilder {
+ self.with_inner(move |inner| inner.tls_built_in_root_certs(tls_built_in_root_certs))
+ }
+
+ /// Sets the identity to be used for client certificate authentication.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be
+ /// enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls"))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn identity(self, identity: Identity) -> ClientBuilder {
+ self.with_inner(move |inner| inner.identity(identity))
+ }
+
+ /// Controls the use of hostname verification.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before you use this method. If
+ /// hostname verification is not used, any valid certificate for any
+ /// site will be trusted for use from any other. This introduces a
+ /// significant vulnerability to man-in-the-middle attacks.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostname: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname))
+ }
+
+ /// Controls the use of certificate validation.
+ ///
+ /// Defaults to `false`.
+ ///
+ /// # Warning
+ ///
+ /// You should think very carefully before using this method. If
+ /// invalid certificates are trusted, *any* certificate for *any* site
+ /// will be trusted for use. This includes expired certificates. This
+ /// introduces significant vulnerabilities, and should only be used
+ /// as a last resort.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs))
+ }
+
+ /// Controls the use of TLS server name indication.
+ ///
+ /// Defaults to `true`.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn tls_sni(self, tls_sni: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.tls_sni(tls_sni))
+ }
+
+ /// Set the minimum required TLS version for connections.
+ ///
+ /// By default the TLS backend's own default is used.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a minimum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn min_tls_version(self, version: tls::Version) -> ClientBuilder {
+ self.with_inner(|inner| inner.min_tls_version(version))
+ }
+
+ /// Set the maximum allowed TLS version for connections.
+ ///
+ /// By default there's no maximum.
+ ///
+ /// # Errors
+ ///
+ /// A value of `tls::Version::TLS_1_3` will cause an error with the
+ /// `native-tls`/`default-tls` backend. This does not mean the version
+ /// isn't supported, just that it can't be set as a maximum due to
+ /// technical limitations.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)`
+ /// feature to be enabled.
+ #[cfg(feature = "__tls")]
+ #[cfg_attr(
+ docsrs,
+ doc(cfg(any(
+ feature = "default-tls",
+ feature = "native-tls",
+ feature = "rustls-tls"
+ )))
+ )]
+ pub fn max_tls_version(self, version: tls::Version) -> ClientBuilder {
+ self.with_inner(|inner| inner.max_tls_version(version))
+ }
+
+ /// Force using the native TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `native-tls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `native-tls` feature to be enabled.
+ #[cfg(feature = "native-tls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
+ pub fn use_native_tls(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_native_tls())
+ }
+
+ /// Force using the Rustls TLS backend.
+ ///
+ /// Since multiple TLS backends can be optionally enabled, this option will
+ /// force the `rustls` backend to be used for this `Client`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `rustls-tls(-...)` feature to be enabled.
+ #[cfg(feature = "__rustls")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
+ pub fn use_rustls_tls(self) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_rustls_tls())
+ }
+
+ /// Use a preconfigured TLS backend.
+ ///
+ /// If the passed `Any` argument is not a TLS backend that reqwest
+ /// understands, the `ClientBuilder` will error when calling `build`.
+ ///
+ /// # Advanced
+ ///
+ /// This is an advanced option, and can be somewhat brittle. Usage requires
+ /// keeping the preconfigured TLS argument version in sync with reqwest,
+ /// since version mismatches will result in an "unknown" TLS backend.
+ ///
+ /// If possible, it's preferable to use the methods on `ClientBuilder`
+ /// to configure reqwest's TLS.
+ ///
+ /// # Optional
+ ///
+ /// This requires one of the optional features `native-tls` or
+ /// `rustls-tls(-...)` to be enabled.
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
+ pub fn use_preconfigured_tls(self, tls: impl Any) -> ClientBuilder {
+ self.with_inner(move |inner| inner.use_preconfigured_tls(tls))
+ }
+
+ /// Enables the [trust-dns](trust_dns_resolver) async resolver instead of a default threadpool using `getaddrinfo`.
+ ///
+ /// If the `trust-dns` feature is turned on, the default option is enabled.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `trust-dns` feature to be enabled
+ #[cfg(feature = "trust-dns")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "trust-dns")))]
+ pub fn trust_dns(self, enable: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.trust_dns(enable))
+ }
+
+ /// Disables the trust-dns async resolver.
+ ///
+ /// This method exists even if the optional `trust-dns` feature is not enabled.
+ /// This can be used to ensure a `Client` doesn't use the trust-dns async resolver
+ /// even if another dependency were to enable the optional `trust-dns` feature.
+ pub fn no_trust_dns(self) -> ClientBuilder {
+ self.with_inner(|inner| inner.no_trust_dns())
+ }
+
+ /// Restrict the Client to be used with HTTPS only requests.
+ ///
+ /// Defaults to false.
+ pub fn https_only(self, enabled: bool) -> ClientBuilder {
+ self.with_inner(|inner| inner.https_only(enabled))
+ }
+
+ /// Override DNS resolution for specific domains to a particular IP address.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addr will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder {
+ self.resolve_to_addrs(domain, &[addr])
+ }
+
+ /// Override DNS resolution for specific domains to particular IP addresses.
+ ///
+ /// Warning
+ ///
+ /// Since the DNS protocol has no notion of ports, if you wish to send
+ /// traffic to a particular port you must include this port in the URL
+ /// itself, any port in the overridden addresses will be ignored and traffic sent
+ /// to the conventional port for the given scheme (e.g. 80 for http).
+ pub fn resolve_to_addrs(self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder {
+ self.with_inner(|inner| inner.resolve_to_addrs(domain, addrs))
+ }
+
+ // private
+
+ fn with_inner<F>(mut self, func: F) -> ClientBuilder
+ where
+ F: FnOnce(async_impl::ClientBuilder) -> async_impl::ClientBuilder,
+ {
+ self.inner = func(self.inner);
+ self
+ }
+}
+
+impl From<async_impl::ClientBuilder> for ClientBuilder {
+ fn from(builder: async_impl::ClientBuilder) -> Self {
+ Self {
+ inner: builder,
+ timeout: Timeout::default(),
+ }
+ }
+}
+
+impl Default for Client {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Client {
+ /// Constructs a new `Client`.
+ ///
+ /// # Panic
+ ///
+ /// This method panics if TLS backend cannot be initialized, or the resolver
+ /// cannot load the system configuration.
+ ///
+ /// Use `Client::builder()` if you wish to handle the failure as an `Error`
+ /// instead of panicking.
+ ///
+ /// This method also panics if called from within an async runtime. See docs
+ /// on [`reqwest::blocking`][crate::blocking] for details.
+ pub fn new() -> Client {
+ ClientBuilder::new().build().expect("Client::new()")
+ }
+
+ /// Creates a `ClientBuilder` to configure a `Client`.
+ ///
+ /// This is the same as `ClientBuilder::new()`.
+ pub fn builder() -> ClientBuilder {
+ ClientBuilder::new()
+ }
+
+ /// Convenience method to make a `GET` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::GET, url)
+ }
+
+ /// Convenience method to make a `POST` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::POST, url)
+ }
+
+ /// Convenience method to make a `PUT` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PUT, url)
+ }
+
+ /// Convenience method to make a `PATCH` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PATCH, url)
+ }
+
+ /// Convenience method to make a `DELETE` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::DELETE, url)
+ }
+
+ /// Convenience method to make a `HEAD` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::HEAD, url)
+ }
+
+ /// Start building a `Request` with the `Method` and `Url`.
+ ///
+ /// Returns a `RequestBuilder`, which will allow setting headers and
+ /// request body before sending.
+ ///
+ /// # Errors
+ ///
+ /// 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));
+ RequestBuilder::new(self.clone(), req)
+ }
+
+ /// Executes a `Request`.
+ ///
+ /// A `Request` can be built manually with `Request::new()` or obtained
+ /// from a RequestBuilder with `RequestBuilder::build()`.
+ ///
+ /// You should prefer to use the `RequestBuilder` and
+ /// `RequestBuilder::send()`.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// or redirect limit was exhausted.
+ pub fn execute(&self, request: Request) -> crate::Result<Response> {
+ self.inner.execute_request(request)
+ }
+}
+
+impl fmt::Debug for Client {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Client")
+ //.field("gzip", &self.inner.gzip)
+ //.field("redirect_policy", &self.inner.redirect_policy)
+ //.field("referer", &self.inner.referer)
+ .finish()
+ }
+}
+
+impl fmt::Debug for ClientBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt(f)
+ }
+}
+
+#[derive(Clone)]
+struct ClientHandle {
+ timeout: Timeout,
+ inner: Arc<InnerClientHandle>,
+}
+
+type OneshotResponse = oneshot::Sender<crate::Result<async_impl::Response>>;
+type ThreadSender = mpsc::UnboundedSender<(async_impl::Request, OneshotResponse)>;
+
+struct InnerClientHandle {
+ tx: Option<ThreadSender>,
+ thread: Option<thread::JoinHandle<()>>,
+}
+
+impl Drop for InnerClientHandle {
+ fn drop(&mut self) {
+ let id = self
+ .thread
+ .as_ref()
+ .map(|h| h.thread().id())
+ .expect("thread not dropped yet");
+
+ trace!("closing runtime thread ({:?})", id);
+ self.tx.take();
+ trace!("signaled close for runtime thread ({:?})", id);
+ self.thread.take().map(|h| h.join());
+ trace!("closed runtime thread ({:?})", id);
+ }
+}
+
+impl ClientHandle {
+ fn new(builder: ClientBuilder) -> crate::Result<ClientHandle> {
+ let timeout = builder.timeout;
+ let builder = builder.inner;
+ let (tx, rx) = mpsc::unbounded_channel::<(async_impl::Request, OneshotResponse)>();
+ let (spawn_tx, spawn_rx) = oneshot::channel::<crate::Result<()>>();
+ let handle = thread::Builder::new()
+ .name("reqwest-internal-sync-runtime".into())
+ .spawn(move || {
+ use tokio::runtime;
+ let rt = match runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .map_err(crate::error::builder)
+ {
+ Err(e) => {
+ if let Err(e) = spawn_tx.send(Err(e)) {
+ error!("Failed to communicate runtime creation failure: {:?}", e);
+ }
+ return;
+ }
+ Ok(v) => v,
+ };
+
+ let f = async move {
+ let client = match builder.build() {
+ Err(e) => {
+ if let Err(e) = spawn_tx.send(Err(e)) {
+ error!("Failed to communicate client creation failure: {:?}", e);
+ }
+ return;
+ }
+ Ok(v) => v,
+ };
+ if let Err(e) = spawn_tx.send(Ok(())) {
+ error!("Failed to communicate successful startup: {:?}", e);
+ return;
+ }
+
+ let mut rx = rx;
+
+ while let Some((req, req_tx)) = rx.recv().await {
+ let req_fut = client.execute(req);
+ tokio::spawn(forward(req_fut, req_tx));
+ }
+
+ trace!("({:?}) Receiver is shutdown", thread::current().id());
+ };
+
+ trace!("({:?}) start runtime::block_on", thread::current().id());
+ rt.block_on(f);
+ trace!("({:?}) end runtime::block_on", thread::current().id());
+ drop(rt);
+ trace!("({:?}) finished", thread::current().id());
+ })
+ .map_err(crate::error::builder)?;
+
+ // Wait for the runtime thread to start up...
+ match wait::timeout(spawn_rx, None) {
+ Ok(Ok(())) => (),
+ Ok(Err(err)) => return Err(err),
+ Err(_canceled) => event_loop_panicked(),
+ }
+
+ let inner_handle = Arc::new(InnerClientHandle {
+ tx: Some(tx),
+ thread: Some(handle),
+ });
+
+ Ok(ClientHandle {
+ timeout,
+ inner: inner_handle,
+ })
+ }
+
+ fn execute_request(&self, req: Request) -> crate::Result<Response> {
+ let (tx, rx) = oneshot::channel();
+ let (req, body) = req.into_async();
+ let url = req.url().clone();
+ let timeout = req.timeout().copied().or(self.timeout.0);
+
+ self.inner
+ .tx
+ .as_ref()
+ .expect("core thread exited early")
+ .send((req, tx))
+ .expect("core thread panicked");
+
+ let result: Result<crate::Result<async_impl::Response>, wait::Waited<crate::Error>> =
+ if let Some(body) = body {
+ let f = async move {
+ body.send().await?;
+ rx.await.map_err(|_canceled| event_loop_panicked())
+ };
+ wait::timeout(f, timeout)
+ } else {
+ let f = async move { rx.await.map_err(|_canceled| event_loop_panicked()) };
+ wait::timeout(f, timeout)
+ };
+
+ match result {
+ Ok(Err(err)) => Err(err.with_url(url)),
+ Ok(Ok(res)) => Ok(Response::new(
+ res,
+ timeout,
+ KeepCoreThreadAlive(Some(self.inner.clone())),
+ )),
+ Err(wait::Waited::TimedOut(e)) => Err(crate::error::request(e).with_url(url)),
+ Err(wait::Waited::Inner(err)) => Err(err.with_url(url)),
+ }
+ }
+}
+
+async fn forward<F>(fut: F, mut tx: OneshotResponse)
+where
+ F: Future<Output = crate::Result<async_impl::Response>>,
+{
+ use std::task::Poll;
+
+ futures_util::pin_mut!(fut);
+
+ // "select" on the sender being canceled, and the future completing
+ let res = futures_util::future::poll_fn(|cx| {
+ match fut.as_mut().poll(cx) {
+ Poll::Ready(val) => Poll::Ready(Some(val)),
+ Poll::Pending => {
+ // check if the callback is canceled
+ futures_core::ready!(tx.poll_closed(cx));
+ Poll::Ready(None)
+ }
+ }
+ })
+ .await;
+
+ if let Some(res) = res {
+ let _ = tx.send(res);
+ }
+ // else request is canceled
+}
+
+#[derive(Clone, Copy)]
+struct Timeout(Option<Duration>);
+
+impl Default for Timeout {
+ fn default() -> Timeout {
+ // default mentioned in ClientBuilder::timeout() doc comment
+ Timeout(Some(Duration::from_secs(30)))
+ }
+}
+
+pub(crate) struct KeepCoreThreadAlive(Option<Arc<InnerClientHandle>>);
+
+impl KeepCoreThreadAlive {
+ pub(crate) fn empty() -> KeepCoreThreadAlive {
+ KeepCoreThreadAlive(None)
+ }
+}
+
+#[cold]
+#[inline(never)]
+fn event_loop_panicked() -> ! {
+ // The only possible reason there would be a Canceled error
+ // is if the thread running the event loop panicked. We could return
+ // an Err here, like a BrokenPipe, but the Client is not
+ // recoverable. Additionally, the panic in the other thread
+ // is not normal, and should likely be propagated.
+ panic!("event loop thread panicked");
+}
diff --git a/vendor/reqwest/src/blocking/mod.rs b/vendor/reqwest/src/blocking/mod.rs
new file mode 100644
index 000000000..487387545
--- /dev/null
+++ b/vendor/reqwest/src/blocking/mod.rs
@@ -0,0 +1,109 @@
+//! A blocking Client API.
+//!
+//! The blocking `Client` will block the current thread to execute, instead
+//! of returning futures that need to be executed on a runtime.
+//!
+//! Conversely, the functionality in `reqwest::blocking` must *not* be executed
+//! within an async runtime, or it will panic when attempting to block. If
+//! calling directly from an async function, consider using an async
+//! [`reqwest::Client`][crate::Client] instead. If the immediate context is only
+//! synchronous, but a transitive caller is async, consider changing that caller
+//! to use [`tokio::task::spawn_blocking`] around the calls that need to block.
+//!
+//! # Optional
+//!
+//! This requires the optional `blocking` feature to be enabled.
+//!
+//! # Making a GET request
+//!
+//! For a single request, you can use the [`get`](get) shortcut method.
+//!
+//! ```rust
+//! # use reqwest::{Error, Response};
+//!
+//! # fn run() -> Result<(), Error> {
+//! let body = reqwest::blocking::get("https://www.rust-lang.org")?
+//! .text()?;
+//!
+//! println!("body = {:?}", body);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Additionally, the blocking [`Response`](Response) struct implements Rust's
+//! `Read` trait, so many useful standard library and third party crates will
+//! have convenience methods that take a `Response` anywhere `T: Read` is
+//! acceptable.
+//!
+//! **NOTE**: If you plan to perform multiple requests, it is best to create a
+//! [`Client`](Client) and reuse it, taking advantage of keep-alive connection
+//! pooling.
+//!
+//! # Making POST requests (or setting request bodies)
+//!
+//! There are several ways you can set the body of a request. The basic one is
+//! by using the `body()` method of a [`RequestBuilder`](RequestBuilder). This lets you set the
+//! exact raw bytes of what the body should be. It accepts various types,
+//! including `String`, `Vec<u8>`, and `File`. If you wish to pass a custom
+//! Reader, you can use the `reqwest::blocking::Body::new()` constructor.
+//!
+//! ```rust
+//! # use reqwest::Error;
+//! #
+//! # fn run() -> Result<(), Error> {
+//! let client = reqwest::blocking::Client::new();
+//! let res = client.post("http://httpbin.org/post")
+//! .body("the exact body that is sent")
+//! .send()?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ## And More
+//!
+//! Most features available to the asynchronous `Client` are also available,
+//! on the blocking `Client`, see those docs for more.
+
+mod body;
+mod client;
+#[cfg(feature = "multipart")]
+pub mod multipart;
+mod request;
+mod response;
+mod wait;
+
+pub use self::body::Body;
+pub use self::client::{Client, ClientBuilder};
+pub use self::request::{Request, RequestBuilder};
+pub use self::response::Response;
+
+/// Shortcut method to quickly make a *blocking* `GET` request.
+///
+/// **NOTE**: This function creates a new internal `Client` on each call,
+/// and so should not be used if making many requests. Create a
+/// [`Client`](./struct.Client.html) instead.
+///
+/// # Examples
+///
+/// ```rust
+/// # fn run() -> Result<(), reqwest::Error> {
+/// let body = reqwest::blocking::get("https://www.rust-lang.org")?
+/// .text()?;
+/// # Ok(())
+/// # }
+/// # fn main() { }
+/// ```
+///
+/// # Errors
+///
+/// This function fails if:
+///
+/// - the native TLS backend cannot be initialized,
+/// - the supplied `Url` cannot be parsed,
+/// - there was an error while sending request,
+/// - a redirect loop was detected,
+/// - the redirect limit was exhausted, or
+/// - the total download time exceeds 30 seconds.
+pub fn get<T: crate::IntoUrl>(url: T) -> crate::Result<Response> {
+ Client::builder().build()?.get(url).send()
+}
diff --git a/vendor/reqwest/src/blocking/multipart.rs b/vendor/reqwest/src/blocking/multipart.rs
new file mode 100644
index 000000000..9e7dfd3c7
--- /dev/null
+++ b/vendor/reqwest/src/blocking/multipart.rs
@@ -0,0 +1,483 @@
+//! multipart/form-data
+//!
+//! To send a `multipart/form-data` body, a [`Form`](crate::blocking::multipart::Form) is built up, adding
+//! fields or customized [`Part`](crate::blocking::multipart::Part)s, and then calling the
+//! [`multipart`][builder] method on the `RequestBuilder`.
+//!
+//! # Example
+//!
+//! ```
+//! use reqwest::blocking::multipart;
+//!
+//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
+//! let form = multipart::Form::new()
+//! // Adding just a simple text field...
+//! .text("username", "seanmonstar")
+//! // And a file...
+//! .file("photo", "/path/to/photo.png")?;
+//!
+//! // Customize all the details of a Part if needed...
+//! let bio = multipart::Part::text("hallo peeps")
+//! .file_name("bio.txt")
+//! .mime_str("text/plain")?;
+//!
+//! // Add the custom part to our form...
+//! let form = form.part("biography", bio);
+//!
+//! // And finally, send the form
+//! let client = reqwest::blocking::Client::new();
+//! let resp = client
+//! .post("http://localhost:8080/user")
+//! .multipart(form)
+//! .send()?;
+//! # Ok(())
+//! # }
+//! # fn main() {}
+//! ```
+//!
+//! [builder]: ../struct.RequestBuilder.html#method.multipart
+use std::borrow::Cow;
+use std::fmt;
+use std::fs::File;
+use std::io::{self, Cursor, Read};
+use std::path::Path;
+
+use mime_guess::{self, Mime};
+
+use super::Body;
+use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
+use crate::header::HeaderMap;
+
+/// A multipart/form-data request.
+pub struct Form {
+ inner: FormParts<Part>,
+}
+
+/// A field in a multipart form.
+pub struct Part {
+ meta: PartMetadata,
+ value: Body,
+}
+
+impl Default for Form {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Form {
+ /// Creates a new Form without any content.
+ pub fn new() -> Form {
+ Form {
+ inner: FormParts::new(),
+ }
+ }
+
+ /// Get the boundary that this form will use.
+ #[inline]
+ pub fn boundary(&self) -> &str {
+ self.inner.boundary()
+ }
+
+ /// Add a data field with supplied name and value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let form = reqwest::blocking::multipart::Form::new()
+ /// .text("username", "seanmonstar")
+ /// .text("password", "secret");
+ /// ```
+ pub fn text<T, U>(self, name: T, value: U) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ U: Into<Cow<'static, str>>,
+ {
+ self.part(name, Part::text(value))
+ }
+
+ /// Adds a file field.
+ ///
+ /// The path will be used to try to guess the filename and mime.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # fn run() -> std::io::Result<()> {
+ /// let files = reqwest::blocking::multipart::Form::new()
+ /// .file("key", "/path/to/file")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Errors when the file cannot be opened.
+ pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
+ where
+ T: Into<Cow<'static, str>>,
+ U: AsRef<Path>,
+ {
+ Ok(self.part(name, Part::file(path)?))
+ }
+
+ /// Adds a customized Part.
+ pub fn part<T>(self, name: T, part: Part) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.part(name, part))
+ }
+
+ /// Configure this `Form` to percent-encode using the `path-segment` rules.
+ pub fn percent_encode_path_segment(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_path_segment())
+ }
+
+ /// Configure this `Form` to percent-encode using the `attr-char` rules.
+ pub fn percent_encode_attr_chars(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_attr_chars())
+ }
+
+ /// Configure this `Form` to skip percent-encoding
+ pub fn percent_encode_noop(self) -> Form {
+ self.with_inner(|inner| inner.percent_encode_noop())
+ }
+
+ pub(crate) fn reader(self) -> Reader {
+ Reader::new(self)
+ }
+
+ // If predictable, computes the length the request will have
+ // The length should be preditable if only String and file fields have been added,
+ // but not if a generic reader has been added;
+ pub(crate) fn compute_length(&mut self) -> Option<u64> {
+ self.inner.compute_length()
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(FormParts<Part>) -> FormParts<Part>,
+ {
+ Form {
+ inner: func(self.inner),
+ }
+ }
+}
+
+impl fmt::Debug for Form {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt_fields("Form", f)
+ }
+}
+
+impl Part {
+ /// Makes a text parameter.
+ pub fn text<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(string) => Body::from(string),
+ };
+ Part::new(body)
+ }
+
+ /// Makes a new parameter from arbitrary bytes.
+ pub fn bytes<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, [u8]>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(vec) => Body::from(vec),
+ };
+ Part::new(body)
+ }
+
+ /// Adds a generic reader.
+ ///
+ /// Does not set filename or mime.
+ pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
+ Part::new(Body::new(value))
+ }
+
+ /// Adds a generic reader with known length.
+ ///
+ /// Does not set filename or mime.
+ pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
+ Part::new(Body::sized(value, length))
+ }
+
+ /// Makes a file parameter.
+ ///
+ /// # Errors
+ ///
+ /// Errors when the file cannot be opened.
+ pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
+ let path = path.as_ref();
+ let file_name = path
+ .file_name()
+ .map(|filename| filename.to_string_lossy().into_owned());
+ let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
+ let mime = mime_guess::from_ext(ext).first_or_octet_stream();
+ let file = File::open(path)?;
+ let field = Part::new(Body::from(file)).mime(mime);
+
+ Ok(if let Some(file_name) = file_name {
+ field.file_name(file_name)
+ } else {
+ field
+ })
+ }
+
+ fn new(value: Body) -> Part {
+ Part {
+ meta: PartMetadata::new(),
+ value,
+ }
+ }
+
+ /// Tries to set the mime of this part.
+ pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
+ Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
+ }
+
+ // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
+ fn mime(self, mime: Mime) -> Part {
+ self.with_inner(move |inner| inner.mime(mime))
+ }
+
+ /// Sets the filename, builder style.
+ pub fn file_name<T>(self, filename: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.file_name(filename))
+ }
+
+ /// Sets custom headers for the part.
+ pub fn headers(self, headers: HeaderMap) -> Part {
+ self.with_inner(move |inner| inner.headers(headers))
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(PartMetadata) -> PartMetadata,
+ {
+ Part {
+ meta: func(self.meta),
+ value: self.value,
+ }
+ }
+}
+
+impl fmt::Debug for Part {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut dbg = f.debug_struct("Part");
+ dbg.field("value", &self.value);
+ self.meta.fmt_fields(&mut dbg);
+ dbg.finish()
+ }
+}
+
+impl PartProps for Part {
+ fn value_len(&self) -> Option<u64> {
+ self.value.len()
+ }
+
+ fn metadata(&self) -> &PartMetadata {
+ &self.meta
+ }
+}
+
+pub(crate) struct Reader {
+ form: Form,
+ active_reader: Option<Box<dyn Read + Send>>,
+}
+
+impl fmt::Debug for Reader {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Reader").field("form", &self.form).finish()
+ }
+}
+
+impl Reader {
+ fn new(form: Form) -> Reader {
+ let mut reader = Reader {
+ form,
+ active_reader: None,
+ };
+ reader.next_reader();
+ reader
+ }
+
+ fn next_reader(&mut self) {
+ self.active_reader = if !self.form.inner.fields.is_empty() {
+ // We need to move out of the vector here because we are consuming the field's reader
+ let (name, field) = self.form.inner.fields.remove(0);
+ let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
+ let header = Cursor::new({
+ // Try to use cached headers created by compute_length
+ let mut h = if !self.form.inner.computed_headers.is_empty() {
+ self.form.inner.computed_headers.remove(0)
+ } else {
+ self.form
+ .inner
+ .percent_encoding
+ .encode_headers(&name, field.metadata())
+ };
+ h.extend_from_slice(b"\r\n\r\n");
+ h
+ });
+ let reader = boundary
+ .chain(header)
+ .chain(field.value.into_reader())
+ .chain(Cursor::new("\r\n"));
+ // According to https://tools.ietf.org/html/rfc2046#section-5.1.1
+ // the very last field has a special boundary
+ if !self.form.inner.fields.is_empty() {
+ Some(Box::new(reader))
+ } else {
+ Some(Box::new(reader.chain(Cursor::new(format!(
+ "--{}--\r\n",
+ self.form.boundary()
+ )))))
+ }
+ } else {
+ None
+ }
+ }
+}
+
+impl Read for Reader {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let mut total_bytes_read = 0usize;
+ let mut last_read_bytes;
+ loop {
+ match self.active_reader {
+ Some(ref mut reader) => {
+ last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
+ total_bytes_read += last_read_bytes;
+ if total_bytes_read == buf.len() {
+ return Ok(total_bytes_read);
+ }
+ }
+ None => return Ok(total_bytes_read),
+ };
+ if last_read_bytes == 0 && !buf.is_empty() {
+ self.next_reader();
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn form_empty() {
+ let mut output = Vec::new();
+ let mut form = Form::new();
+ let length = form.compute_length();
+ form.reader().read_to_end(&mut output).unwrap();
+ assert_eq!(output, b"");
+ assert_eq!(length.unwrap(), 0);
+ }
+
+ #[test]
+ fn read_to_end() {
+ let mut output = Vec::new();
+ let mut form = Form::new()
+ .part("reader1", Part::reader(std::io::empty()))
+ .part("key1", Part::text("value1"))
+ .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
+ .part("reader2", Part::reader(std::io::empty()))
+ .part("key3", Part::text("value3").file_name("filename"));
+ form.inner.boundary = "boundary".to_string();
+ let length = form.compute_length();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
+ \r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
+ value1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\r\n\
+ value2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
+ \r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
+ value3\r\n--boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ assert!(length.is_none());
+ }
+
+ #[test]
+ fn read_to_end_with_length() {
+ let mut output = Vec::new();
+ let mut form = Form::new()
+ .text("key1", "value1")
+ .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
+ .part("key3", Part::text("value3").file_name("filename"));
+ form.inner.boundary = "boundary".to_string();
+ let length = form.compute_length();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
+ value1\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\r\n\
+ value2\r\n\
+ --boundary\r\n\
+ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
+ value3\r\n--boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ assert_eq!(length.unwrap(), expected.len() as u64);
+ }
+
+ #[test]
+ fn read_to_end_with_header() {
+ let mut output = Vec::new();
+ let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
+ let mut headers = HeaderMap::new();
+ headers.insert("Hdr3", "/a/b/c".parse().unwrap());
+ part = part.headers(headers);
+ let mut form = Form::new().part("key2", part);
+ form.inner.boundary = "boundary".to_string();
+ let expected = "--boundary\r\n\
+ Content-Disposition: form-data; name=\"key2\"\r\n\
+ Content-Type: image/bmp\r\n\
+ hdr3: /a/b/c\r\n\
+ \r\n\
+ value2\r\n\
+ --boundary--\r\n";
+ form.reader().read_to_end(&mut output).unwrap();
+ // These prints are for debug purposes in case the test fails
+ println!(
+ "START REAL\n{}\nEND REAL",
+ std::str::from_utf8(&output).unwrap()
+ );
+ println!("START EXPECTED\n{}\nEND EXPECTED", expected);
+ assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
+ }
+}
diff --git a/vendor/reqwest/src/blocking/request.rs b/vendor/reqwest/src/blocking/request.rs
new file mode 100644
index 000000000..71bbdc932
--- /dev/null
+++ b/vendor/reqwest/src/blocking/request.rs
@@ -0,0 +1,1063 @@
+use std::convert::TryFrom;
+use std::fmt;
+use std::time::Duration;
+
+use http::{request::Parts, Request as HttpRequest, Version};
+use serde::Serialize;
+#[cfg(feature = "json")]
+use serde_json;
+use serde_urlencoded;
+
+use super::body::{self, Body};
+#[cfg(feature = "multipart")]
+use super::multipart;
+use super::Client;
+use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
+use crate::{async_impl, Method, Url};
+
+/// A request which can be executed with `Client::execute()`.
+pub struct Request {
+ body: Option<Body>,
+ inner: async_impl::Request,
+}
+
+/// A builder to construct the properties of a `Request`.
+///
+/// To construct a `RequestBuilder`, refer to the `Client` documentation.
+#[derive(Debug)]
+#[must_use = "RequestBuilder does nothing until you 'send' it"]
+pub struct RequestBuilder {
+ client: Client,
+ request: crate::Result<Request>,
+}
+
+impl Request {
+ /// Constructs a new request.
+ #[inline]
+ pub fn new(method: Method, url: Url) -> Self {
+ Request {
+ body: None,
+ inner: async_impl::Request::new(method, url),
+ }
+ }
+
+ /// Get the method.
+ #[inline]
+ pub fn method(&self) -> &Method {
+ self.inner.method()
+ }
+
+ /// Get a mutable reference to the method.
+ #[inline]
+ pub fn method_mut(&mut self) -> &mut Method {
+ self.inner.method_mut()
+ }
+
+ /// Get the url.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ self.inner.url()
+ }
+
+ /// Get a mutable reference to the url.
+ #[inline]
+ pub fn url_mut(&mut self) -> &mut Url {
+ self.inner.url_mut()
+ }
+
+ /// Get the headers.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.inner.headers()
+ }
+
+ /// Get a mutable reference to the headers.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.inner.headers_mut()
+ }
+
+ /// Get the http version.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.inner.version()
+ }
+
+ /// Get a mutable reference to the http version.
+ #[inline]
+ pub fn version_mut(&mut self) -> &mut Version {
+ self.inner.version_mut()
+ }
+
+ /// Get the body.
+ #[inline]
+ pub fn body(&self) -> Option<&Body> {
+ self.body.as_ref()
+ }
+
+ /// Get a mutable reference to the body.
+ #[inline]
+ pub fn body_mut(&mut self) -> &mut Option<Body> {
+ &mut self.body
+ }
+
+ /// Get the timeout.
+ #[inline]
+ pub fn timeout(&self) -> Option<&Duration> {
+ self.inner.timeout()
+ }
+
+ /// Get a mutable reference to the timeout.
+ #[inline]
+ pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
+ self.inner.timeout_mut()
+ }
+
+ /// Attempts to clone the `Request`.
+ ///
+ /// None is returned if a body is which can not be cloned. This can be because the body is a
+ /// stream.
+ pub fn try_clone(&self) -> Option<Request> {
+ let body = if let Some(ref body) = self.body.as_ref() {
+ if let Some(body) = body.try_clone() {
+ Some(body)
+ } else {
+ return None;
+ }
+ } else {
+ None
+ };
+ let mut req = Request::new(self.method().clone(), self.url().clone());
+ *req.headers_mut() = self.headers().clone();
+ *req.version_mut() = self.version().clone();
+ req.body = body;
+ Some(req)
+ }
+
+ pub(crate) fn into_async(self) -> (async_impl::Request, Option<body::Sender>) {
+ use crate::header::CONTENT_LENGTH;
+
+ let mut req_async = self.inner;
+ let body = self.body.and_then(|body| {
+ let (tx, body, len) = body.into_async();
+ if let Some(len) = len {
+ req_async.headers_mut().insert(CONTENT_LENGTH, len.into());
+ }
+ *req_async.body_mut() = Some(body);
+ tx
+ });
+ (req_async, body)
+ }
+}
+
+impl RequestBuilder {
+ pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
+ let mut builder = RequestBuilder { client, request };
+
+ let auth = builder
+ .request
+ .as_mut()
+ .ok()
+ .and_then(|req| async_impl::request::extract_authority(req.url_mut()));
+
+ if let Some((username, password)) = auth {
+ builder.basic_auth(username, password)
+ } else {
+ builder
+ }
+ }
+
+ /// Add a `Header` to this Request.
+ ///
+ /// ```rust
+ /// use reqwest::header::USER_AGENT;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.get("https://www.rust-lang.org")
+ /// .header(USER_AGENT, "foo")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ self.header_sensitive(key, value, false)
+ }
+
+ /// Add a `Header` to this Request with ability to define if header_value is sensitive.
+ fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match <HeaderName as TryFrom<K>>::try_from(key) {
+ Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(mut value) => {
+ value.set_sensitive(sensitive);
+ req.headers_mut().append(key, value);
+ }
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ },
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ };
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Add a set of Headers to the existing ones on this Request.
+ ///
+ /// The headers will be merged in to any already set.
+ ///
+ /// ```rust
+ /// use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE};
+ /// # use std::fs;
+ ///
+ /// fn construct_headers() -> HeaderMap {
+ /// let mut headers = HeaderMap::new();
+ /// headers.insert(USER_AGENT, HeaderValue::from_static("reqwest"));
+ /// headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png"));
+ /// headers
+ /// }
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = fs::File::open("much_beauty.png")?;
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .headers(construct_headers())
+ /// .body(file)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ crate::util::replace_headers(req.headers_mut(), headers);
+ }
+ self
+ }
+
+ /// Enable HTTP basic authentication.
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let resp = client.delete("http://httpbin.org/delete")
+ /// .basic_auth("admin", Some("good password"))
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
+ where
+ U: fmt::Display,
+ P: fmt::Display,
+ {
+ let header_value = crate::util::basic_auth(username, password);
+ self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
+ }
+
+ /// Enable HTTP bearer authentication.
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let resp = client.delete("http://httpbin.org/delete")
+ /// .bearer_auth("token")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
+ where
+ T: fmt::Display,
+ {
+ let header_value = format!("Bearer {}", token);
+ self.header_sensitive(crate::header::AUTHORIZATION, &*header_value, true)
+ }
+
+ /// Set the request body.
+ ///
+ /// # Examples
+ ///
+ /// Using a string:
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body("from a &str!")
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Using a `File`:
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let file = std::fs::File::open("from_a_file.txt")?;
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body(file)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Using arbitrary bytes:
+ ///
+ /// ```rust
+ /// # use std::fs;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// // from bytes!
+ /// let bytes: Vec<u8> = vec![1, 10, 100];
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org/post")
+ /// .body(bytes)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.body_mut() = Some(body.into());
+ }
+ self
+ }
+
+ /// Enables a request timeout.
+ ///
+ /// The timeout is applied from when the request starts connecting until the
+ /// response body has finished. It affects only this request and overrides
+ /// the timeout configured using `ClientBuilder::timeout()`.
+ pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.timeout_mut() = Some(timeout);
+ }
+ self
+ }
+
+ /// Modify the query string of the URL.
+ ///
+ /// Modifies the URL of this request, adding the parameters provided.
+ /// This method appends and does not overwrite. This means that it can
+ /// be called multiple times and that existing query parameters are not
+ /// overwritten if the same key is used. The key will simply show up
+ /// twice in the query string.
+ /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.get("http://httpbin.org")
+ /// .query(&[("lang", "rust")])
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Note
+ /// This method does not support serializing a single key-value
+ /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
+ /// as `.query(&[("key", "val")])`. It's also possible to serialize structs
+ /// and maps into a key-value pair.
+ ///
+ /// # Errors
+ /// This method will fail if the object you provide cannot be serialized
+ /// into a query string.
+ pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ let url = req.url_mut();
+ let mut pairs = url.query_pairs_mut();
+ let serializer = serde_urlencoded::Serializer::new(&mut pairs);
+
+ if let Err(err) = query.serialize(serializer) {
+ error = Some(crate::error::builder(err));
+ }
+ }
+ if let Ok(ref mut req) = self.request {
+ if let Some("") = req.url().query() {
+ req.url_mut().set_query(None);
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Set HTTP version
+ pub fn version(mut self, version: Version) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.version_mut() = version;
+ }
+ self
+ }
+
+ /// Send a form body.
+ ///
+ /// Sets the body to the url encoded serialization of the passed value,
+ /// and also sets the `Content-Type: application/x-www-form-urlencoded`
+ /// header.
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// # use std::collections::HashMap;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let mut params = HashMap::new();
+ /// params.insert("lang", "rust");
+ ///
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org")
+ /// .form(&params)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails if the passed value cannot be serialized into
+ /// url encoded format
+ pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_urlencoded::to_string(form) {
+ Ok(body) => {
+ req.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("application/x-www-form-urlencoded"),
+ );
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Send a JSON body.
+ ///
+ /// Sets the body to the JSON serialization of the passed value, and
+ /// also sets the `Content-Type: application/json` header.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use reqwest::Error;
+ /// # use std::collections::HashMap;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let mut map = HashMap::new();
+ /// map.insert("lang", "rust");
+ ///
+ /// let client = reqwest::blocking::Client::new();
+ /// let res = client.post("http://httpbin.org")
+ /// .json(&map)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// Serialization can fail if `T`'s implementation of `Serialize` decides to
+ /// fail, or if `T` contains a map with non-string keys.
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_json::to_vec(json) {
+ Ok(body) => {
+ if !req.headers().contains_key(CONTENT_TYPE) {
+ req.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ }
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Sends a multipart/form-data body.
+ ///
+ /// ```
+ /// # use reqwest::Error;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let form = reqwest::blocking::multipart::Form::new()
+ /// .text("key3", "value3")
+ /// .file("file", "/path/to/field")?;
+ ///
+ /// let response = client.post("your url")
+ /// .multipart(form)
+ /// .send()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// See [`multipart`](multipart/) for more examples.
+ #[cfg(feature = "multipart")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+ pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
+ let mut builder = self.header(
+ CONTENT_TYPE,
+ format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
+ );
+ if let Ok(ref mut req) = builder.request {
+ *req.body_mut() = Some(match multipart.compute_length() {
+ Some(length) => Body::sized(multipart.reader(), length),
+ None => Body::new(multipart.reader()),
+ })
+ }
+ builder
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ pub fn build(self) -> crate::Result<Request> {
+ self.request
+ }
+
+ /// Constructs the Request and sends it the target URL, returning a Response.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ pub fn send(self) -> crate::Result<super::Response> {
+ self.client.execute(self.request?)
+ }
+
+ /// Attempts to clone the `RequestBuilder`.
+ ///
+ /// None is returned if a body is which can not be cloned. This can be because the body is a
+ /// stream.
+ ///
+ /// # Examples
+ ///
+ /// With a static body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.post("http://httpbin.org/post")
+ /// .body("from a &str!");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Without a body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.get("http://httpbin.org/get");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// With a non-clonable body
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::blocking::Client::new();
+ /// let builder = client.get("http://httpbin.org/get")
+ /// .body(reqwest::blocking::Body::new(std::io::empty()));
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_none());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn try_clone(&self) -> Option<RequestBuilder> {
+ self.request
+ .as_ref()
+ .ok()
+ .and_then(|req| req.try_clone())
+ .map(|req| RequestBuilder {
+ client: self.client.clone(),
+ request: Ok(req),
+ })
+ }
+}
+
+impl<T> TryFrom<HttpRequest<T>> for Request
+where
+ T: Into<Body>,
+{
+ type Error = crate::Error;
+
+ fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
+ let (parts, body) = req.into_parts();
+ let Parts {
+ method,
+ uri,
+ headers,
+ ..
+ } = parts;
+ let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
+ let mut inner = async_impl::Request::new(method, url);
+ crate::util::replace_headers(inner.headers_mut(), headers);
+ Ok(Request {
+ body: Some(body.into()),
+ inner,
+ })
+ }
+}
+
+impl fmt::Debug for Request {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
+ }
+}
+
+fn fmt_request_fields<'a, 'b>(
+ f: &'a mut fmt::DebugStruct<'a, 'b>,
+ req: &Request,
+) -> &'a mut fmt::DebugStruct<'a, 'b> {
+ f.field("method", req.method())
+ .field("url", req.url())
+ .field("headers", req.headers())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::{body, Client};
+ use super::{HttpRequest, Request, Version};
+ use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST};
+ use crate::Method;
+ use serde::Serialize;
+ #[cfg(feature = "json")]
+ use serde_json;
+ use serde_urlencoded;
+ use std::collections::{BTreeMap, HashMap};
+ use std::convert::TryFrom;
+
+ #[test]
+ fn basic_get_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.get(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::GET);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_head_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.head(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::HEAD);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_post_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::POST);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_put_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.put(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::PUT);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_patch_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.patch(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::PATCH);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn basic_delete_request() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.delete(some_url).build().unwrap();
+
+ assert_eq!(r.method(), &Method::DELETE);
+ assert_eq!(r.url().as_str(), some_url);
+ }
+
+ #[test]
+ fn add_header() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header = HeaderValue::from_static("google.com");
+
+ // Add a copy of the header to the request builder
+ let r = r.header(HOST, header.clone()).build().unwrap();
+
+ // then check it was actually added
+ assert_eq!(r.headers().get(HOST), Some(&header));
+ }
+
+ #[test]
+ fn add_headers() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header = HeaderValue::from_static("google.com");
+
+ let mut headers = HeaderMap::new();
+ headers.insert(HOST, header);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build().unwrap();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers(), &headers);
+ }
+
+ #[test]
+ fn add_headers_multi() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let header_json = HeaderValue::from_static("application/json");
+ let header_xml = HeaderValue::from_static("application/xml");
+
+ let mut headers = HeaderMap::new();
+ headers.append(ACCEPT, header_json);
+ headers.append(ACCEPT, header_xml);
+
+ // Add a copy of the headers to the request builder
+ let r = r.headers(headers.clone()).build().unwrap();
+
+ // then make sure they were added correctly
+ assert_eq!(r.headers(), &headers);
+ let mut all_values = r.headers().get_all(ACCEPT).iter();
+ assert_eq!(all_values.next().unwrap(), &"application/json");
+ assert_eq!(all_values.next().unwrap(), &"application/xml");
+ assert_eq!(all_values.next(), None);
+ }
+
+ #[test]
+ fn add_body() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let body = "Some interesting content";
+
+ let mut r = r.body(body).build().unwrap();
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ assert_eq!(buf, body);
+ }
+
+ #[test]
+ fn add_query_append() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&[("foo", "bar")]);
+ r = r.query(&[("qux", 3)]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_append_same() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&[("foo", "a"), ("foo", "b")]);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=a&foo=b"));
+ }
+
+ #[test]
+ fn add_query_struct() {
+ #[derive(Serialize)]
+ struct Params {
+ foo: String,
+ qux: i32,
+ }
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ let params = Params {
+ foo: "bar".into(),
+ qux: 3,
+ };
+
+ r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
+ }
+
+ #[test]
+ fn add_query_map() {
+ let mut params = BTreeMap::new();
+ params.insert("foo", "bar");
+ params.insert("qux", "three");
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let mut r = client.get(some_url);
+
+ r = r.query(&params);
+
+ let req = r.build().expect("request is valid");
+ assert_eq!(req.url().query(), Some("foo=bar&qux=three"));
+ }
+
+ #[test]
+ fn add_form() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let mut form_data = HashMap::new();
+ form_data.insert("foo", "bar");
+
+ let mut r = r.form(&form_data).build().unwrap();
+
+ // Make sure the content type was set
+ assert_eq!(
+ r.headers().get(CONTENT_TYPE).unwrap(),
+ &"application/x-www-form-urlencoded"
+ );
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ let body_should_be = serde_urlencoded::to_string(&form_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ #[cfg(feature = "json")]
+ fn add_json() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+
+ let mut json_data = HashMap::new();
+ json_data.insert("foo", "bar");
+
+ let mut r = r.json(&json_data).build().unwrap();
+
+ // Make sure the content type was set
+ assert_eq!(r.headers().get(CONTENT_TYPE).unwrap(), &"application/json");
+
+ let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
+
+ let body_should_be = serde_json::to_string(&json_data).unwrap();
+ assert_eq!(buf, body_should_be);
+ }
+
+ #[test]
+ #[cfg(feature = "json")]
+ fn add_json_fail() {
+ use serde::ser::Error as _;
+ use serde::{Serialize, Serializer};
+ use std::error::Error as _;
+ struct MyStruct;
+ impl Serialize for MyStruct {
+ fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ Err(S::Error::custom("nope"))
+ }
+ }
+
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let r = client.post(some_url);
+ let json_data = MyStruct;
+ let err = r.json(&json_data).build().unwrap_err();
+ assert!(err.is_builder()); // well, duh ;)
+ assert!(err.source().unwrap().is::<serde_json::Error>());
+ }
+
+ #[test]
+ fn test_replace_headers() {
+ use http::HeaderMap;
+
+ let mut headers = HeaderMap::new();
+ headers.insert("foo", "bar".parse().unwrap());
+ headers.append("foo", "baz".parse().unwrap());
+
+ let client = Client::new();
+ let req = client
+ .get("https://hyper.rs")
+ .header("im-a", "keeper")
+ .header("foo", "pop me")
+ .headers(headers)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.headers()["im-a"], "keeper");
+
+ let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
+ assert_eq!(foo.len(), 2);
+ assert_eq!(foo[0], "bar");
+ assert_eq!(foo[1], "baz");
+ }
+
+ #[test]
+ fn normalize_empty_query() {
+ let client = Client::new();
+ let some_url = "https://google.com/";
+ let empty_query: &[(&str, &str)] = &[];
+
+ let req = client
+ .get(some_url)
+ .query(empty_query)
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().query(), None);
+ assert_eq!(req.url().as_str(), "https://google.com/");
+ }
+
+ #[test]
+ fn convert_url_authority_into_basic_auth() {
+ let client = Client::new();
+ let some_url = "https://Aladdin:open sesame@localhost/";
+
+ let req = client.get(some_url).build().expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ }
+
+ #[test]
+ fn convert_from_http_request() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert_eq!(req.body().is_none(), false);
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ }
+
+ #[test]
+ fn set_http_request_version() {
+ let http_request = HttpRequest::builder()
+ .method("GET")
+ .uri("http://localhost/")
+ .header("User-Agent", "my-awesome-agent/1.0")
+ .version(Version::HTTP_11)
+ .body("test test test")
+ .unwrap();
+ let req: Request = Request::try_from(http_request).unwrap();
+ assert_eq!(req.body().is_none(), false);
+ let test_data = b"test test test";
+ assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
+ let headers = req.headers();
+ assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.url().as_str(), "http://localhost/");
+ assert_eq!(req.version(), Version::HTTP_11);
+ }
+
+ #[test]
+ fn test_basic_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .basic_auth("Aladdin", Some("open sesame"))
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(
+ req.headers()["authorization"],
+ "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+ );
+ assert_eq!(req.headers()["authorization"].is_sensitive(), true);
+ }
+
+ #[test]
+ fn test_bearer_auth_sensitive_header() {
+ let client = Client::new();
+ let some_url = "https://localhost/";
+
+ let req = client
+ .get(some_url)
+ .bearer_auth("Hold my bear")
+ .build()
+ .expect("request build");
+
+ assert_eq!(req.url().as_str(), "https://localhost/");
+ assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
+ assert_eq!(req.headers()["authorization"].is_sensitive(), true);
+ }
+}
diff --git a/vendor/reqwest/src/blocking/response.rs b/vendor/reqwest/src/blocking/response.rs
new file mode 100644
index 000000000..8d0e9b9cf
--- /dev/null
+++ b/vendor/reqwest/src/blocking/response.rs
@@ -0,0 +1,425 @@
+use std::fmt;
+use std::io::{self, Read};
+use std::mem;
+use std::net::SocketAddr;
+use std::pin::Pin;
+use std::time::Duration;
+
+use bytes::Bytes;
+use http;
+use hyper::header::HeaderMap;
+#[cfg(feature = "json")]
+use serde::de::DeserializeOwned;
+
+use super::client::KeepCoreThreadAlive;
+use super::wait;
+#[cfg(feature = "cookies")]
+use crate::cookie;
+use crate::{async_impl, StatusCode, Url, Version};
+
+/// A Response to a submitted `Request`.
+pub struct Response {
+ inner: async_impl::Response,
+ body: Option<Pin<Box<dyn futures_util::io::AsyncRead + Send + Sync>>>,
+ timeout: Option<Duration>,
+ _thread_handle: KeepCoreThreadAlive,
+}
+
+impl fmt::Debug for Response {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl Response {
+ pub(crate) fn new(
+ res: async_impl::Response,
+ timeout: Option<Duration>,
+ thread: KeepCoreThreadAlive,
+ ) -> Response {
+ Response {
+ inner: res,
+ body: None,
+ timeout,
+ _thread_handle: thread,
+ }
+ }
+
+ /// Get the `StatusCode` of this `Response`.
+ ///
+ /// # Examples
+ ///
+ /// Checking for general status class:
+ ///
+ /// ```rust
+ /// # #[cfg(feature = "json")]
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/get")?;
+ /// if resp.status().is_success() {
+ /// println!("success!");
+ /// } else if resp.status().is_server_error() {
+ /// println!("server error!");
+ /// } else {
+ /// println!("Something else happened. Status: {:?}", resp.status());
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// Checking for specific status codes:
+ ///
+ /// ```rust
+ /// use reqwest::blocking::Client;
+ /// use reqwest::StatusCode;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = Client::new();
+ ///
+ /// let resp = client.post("http://httpbin.org/post")
+ /// .body("possibly too large")
+ /// .send()?;
+ ///
+ /// match resp.status() {
+ /// StatusCode::OK => println!("success!"),
+ /// StatusCode::PAYLOAD_TOO_LARGE => {
+ /// println!("Request payload is too large!");
+ /// }
+ /// s => println!("Received response status: {:?}", s),
+ /// };
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn status(&self) -> StatusCode {
+ self.inner.status()
+ }
+
+ /// Get the `Headers` of this `Response`.
+ ///
+ /// # Example
+ ///
+ /// Saving an etag when caching a file:
+ ///
+ /// ```
+ /// use reqwest::blocking::Client;
+ /// use reqwest::header::ETAG;
+ ///
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = Client::new();
+ ///
+ /// let mut resp = client.get("http://httpbin.org/cache").send()?;
+ /// if resp.status().is_success() {
+ /// if let Some(etag) = resp.headers().get(ETAG) {
+ /// std::fs::write("etag", etag.as_bytes());
+ /// }
+ /// let mut file = std::fs::File::create("file")?;
+ /// resp.copy_to(&mut file)?;
+ /// }
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.inner.headers()
+ }
+
+ /// Get a mutable reference to the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.inner.headers_mut()
+ }
+
+ /// Retrieve the cookies contained in the response.
+ ///
+ /// Note that invalid 'Set-Cookie' headers will be ignored.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `cookies` feature to be enabled.
+ #[cfg(feature = "cookies")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
+ pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
+ cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
+ }
+
+ /// Get the HTTP `Version` of this `Response`.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.inner.version()
+ }
+
+ /// Get the final `Url` of this `Response`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
+ /// assert_eq!(resp.url().as_str(), "http://httpbin.org/get");
+ /// # Ok(())
+ /// # }
+ /// ```
+ #[inline]
+ pub fn url(&self) -> &Url {
+ self.inner.url()
+ }
+
+ /// Get the remote address used to get this `Response`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
+ /// println!("httpbin.org address: {:?}", resp.remote_addr());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn remote_addr(&self) -> Option<SocketAddr> {
+ self.inner.remote_addr()
+ }
+
+ /// Returns a reference to the associated extensions.
+ pub fn extensions(&self) -> &http::Extensions {
+ self.inner.extensions()
+ }
+
+ /// Returns a mutable reference to the associated extensions.
+ pub fn extensions_mut(&mut self) -> &mut http::Extensions {
+ self.inner.extensions_mut()
+ }
+
+ /// Get the content-length of the response, if it is known.
+ ///
+ /// Reasons it may not be known:
+ ///
+ /// - The server didn't send a `content-length` header.
+ /// - The response is gzipped and automatically decoded (thus changing
+ /// the actual decoded length).
+ pub fn content_length(&self) -> Option<u64> {
+ self.inner.content_length()
+ }
+
+ /// Try and deserialize the response body as JSON using `serde`.
+ ///
+ /// # Optional
+ ///
+ /// This requires the optional `json` feature enabled.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # extern crate serde;
+ /// #
+ /// # use reqwest::Error;
+ /// # use serde::Deserialize;
+ /// #
+ /// // This `derive` requires the `serde` dependency.
+ /// #[derive(Deserialize)]
+ /// struct Ip {
+ /// origin: String,
+ /// }
+ ///
+ /// # fn run() -> Result<(), Error> {
+ /// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # fn main() { }
+ /// ```
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever the response body is not in JSON format
+ /// or it cannot be properly deserialized to target type `T`. For more
+ /// details please see [`serde_json::from_reader`].
+ ///
+ /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
+ wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+
+ /// Get the full response body as `Bytes`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
+ /// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
+ ///
+ /// println!("bytes: {:?}", bytes);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn bytes(self) -> crate::Result<Bytes> {
+ wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+
+ /// Get the response text.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// Encoding is determined from the `charset` parameter of `Content-Type` header,
+ /// and defaults to `utf-8` if not presented.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn text(self) -> crate::Result<String> {
+ self.text_with_charset("utf-8")
+ }
+
+ /// Get the response text given a specific encoding.
+ ///
+ /// This method decodes the response body with BOM sniffing
+ /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
+ /// You can provide a default encoding for decoding the raw message, while the
+ /// `charset` parameter of `Content-Type` header is still prioritized. For more information
+ /// about the possible encoding name, please go to [`encoding_rs`] docs.
+ ///
+ /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
+ /// .text_with_charset("utf-8")?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
+ wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
+ match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e),
+ wait::Waited::Inner(e) => e,
+ }
+ })
+ }
+
+ /// Copy the response body into a writer.
+ ///
+ /// This function internally uses [`std::io::copy`] and hence will continuously read data from
+ /// the body and then write it into writer in a streaming fashion until EOF is met.
+ ///
+ /// On success, the total number of bytes that were copied to `writer` is returned.
+ ///
+ /// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
+ /// let mut buf: Vec<u8> = vec![];
+ /// resp.copy_to(&mut buf)?;
+ /// assert_eq!(b"abcde", buf.as_slice());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
+ where
+ W: io::Write,
+ {
+ io::copy(self, w).map_err(crate::error::decode_io)
+ }
+
+ /// Turn a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
+ /// .error_for_status();
+ /// if let Err(err) = res {
+ /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
+ /// }
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status(self) -> crate::Result<Self> {
+ let Response {
+ body,
+ inner,
+ timeout,
+ _thread_handle,
+ } = self;
+ inner.error_for_status().map(move |inner| Response {
+ inner,
+ body,
+ timeout,
+ _thread_handle,
+ })
+ }
+
+ /// Turn a reference to a response into an error if the server returned an error.
+ ///
+ /// # Example
+ ///
+ /// ```rust,no_run
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
+ /// let res = res.error_for_status_ref();
+ /// if let Err(err) = res {
+ /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
+ /// }
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
+ self.inner.error_for_status_ref().and_then(|_| Ok(self))
+ }
+
+ // private
+
+ fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
+ use futures_util::TryStreamExt;
+ if self.body.is_none() {
+ let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty());
+
+ let body = body.map_err(crate::error::into_io).into_async_read();
+
+ self.body = Some(Box::pin(body));
+ }
+ self.body.as_mut().expect("body was init").as_mut()
+ }
+}
+
+impl Read for Response {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ use futures_util::io::AsyncReadExt;
+
+ let timeout = self.timeout;
+ wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
+ wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
+ wait::Waited::Inner(e) => e,
+ })
+ }
+}
+
+impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
+ fn from(r: http::Response<T>) -> Response {
+ let response = async_impl::Response::from(r);
+ Response::new(response, None, KeepCoreThreadAlive::empty())
+ }
+}
diff --git a/vendor/reqwest/src/blocking/wait.rs b/vendor/reqwest/src/blocking/wait.rs
new file mode 100644
index 000000000..3c903f8bf
--- /dev/null
+++ b/vendor/reqwest/src/blocking/wait.rs
@@ -0,0 +1,78 @@
+use std::future::Future;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use std::thread::{self, Thread};
+use std::time::Duration;
+
+use tokio::time::Instant;
+
+pub(crate) fn timeout<F, I, E>(fut: F, timeout: Option<Duration>) -> Result<I, Waited<E>>
+where
+ F: Future<Output = Result<I, E>>,
+{
+ enter();
+
+ let deadline = timeout.map(|d| {
+ log::trace!("wait at most {:?}", d);
+ Instant::now() + d
+ });
+
+ let thread = ThreadWaker(thread::current());
+ // Arc shouldn't be necessary, since `Thread` is reference counted internally,
+ // but let's just stay safe for now.
+ let waker = futures_util::task::waker(Arc::new(thread));
+ let mut cx = Context::from_waker(&waker);
+
+ futures_util::pin_mut!(fut);
+
+ loop {
+ match fut.as_mut().poll(&mut cx) {
+ Poll::Ready(Ok(val)) => return Ok(val),
+ Poll::Ready(Err(err)) => return Err(Waited::Inner(err)),
+ Poll::Pending => (), // fallthrough
+ }
+
+ if let Some(deadline) = deadline {
+ let now = Instant::now();
+ if now >= deadline {
+ log::trace!("wait timeout exceeded");
+ return Err(Waited::TimedOut(crate::error::TimedOut));
+ }
+
+ log::trace!(
+ "({:?}) park timeout {:?}",
+ thread::current().id(),
+ deadline - now
+ );
+ thread::park_timeout(deadline - now);
+ } else {
+ log::trace!("({:?}) park without timeout", thread::current().id());
+ thread::park();
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum Waited<E> {
+ TimedOut(crate::error::TimedOut),
+ Inner(E),
+}
+
+struct ThreadWaker(Thread);
+
+impl futures_util::task::ArcWake for ThreadWaker {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ arc_self.0.unpark();
+ }
+}
+
+fn enter() {
+ // Check we aren't already in a runtime
+ #[cfg(debug_assertions)]
+ {
+ let _enter = tokio::runtime::Builder::new_current_thread()
+ .build()
+ .expect("build shell runtime")
+ .enter();
+ }
+}
diff --git a/vendor/reqwest/src/connect.rs b/vendor/reqwest/src/connect.rs
new file mode 100644
index 000000000..b80ffc767
--- /dev/null
+++ b/vendor/reqwest/src/connect.rs
@@ -0,0 +1,1156 @@
+#[cfg(feature = "__tls")]
+use http::header::HeaderValue;
+use http::uri::{Authority, Scheme};
+use http::Uri;
+use hyper::client::connect::{Connected, Connection};
+use hyper::service::Service;
+#[cfg(feature = "native-tls-crate")]
+use native_tls_crate::{TlsConnector, TlsConnectorBuilder};
+use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+
+use pin_project_lite::pin_project;
+use std::future::Future;
+use std::io::{self, IoSlice};
+use std::net::IpAddr;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use std::time::Duration;
+
+#[cfg(feature = "default-tls")]
+use self::native_tls_conn::NativeTlsConn;
+#[cfg(feature = "__rustls")]
+use self::rustls_tls_conn::RustlsTlsConn;
+use crate::dns::DynResolver;
+use crate::error::BoxError;
+use crate::proxy::{Proxy, ProxyScheme};
+
+pub(crate) type HttpConnector = hyper::client::HttpConnector<DynResolver>;
+
+#[derive(Clone)]
+pub(crate) struct Connector {
+ inner: Inner,
+ proxies: Arc<Vec<Proxy>>,
+ verbose: verbose::Wrapper,
+ timeout: Option<Duration>,
+ #[cfg(feature = "__tls")]
+ nodelay: bool,
+ #[cfg(feature = "__tls")]
+ user_agent: Option<HeaderValue>,
+}
+
+#[derive(Clone)]
+enum Inner {
+ #[cfg(not(feature = "__tls"))]
+ Http(HttpConnector),
+ #[cfg(feature = "default-tls")]
+ DefaultTls(HttpConnector, TlsConnector),
+ #[cfg(feature = "__rustls")]
+ RustlsTls {
+ http: HttpConnector,
+ tls: Arc<rustls::ClientConfig>,
+ tls_proxy: Arc<rustls::ClientConfig>,
+ },
+}
+
+impl Connector {
+ #[cfg(not(feature = "__tls"))]
+ pub(crate) fn new<T>(
+ mut http: HttpConnector,
+ proxies: Arc<Vec<Proxy>>,
+ local_addr: T,
+ nodelay: bool,
+ ) -> Connector
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ http.set_local_address(local_addr.into());
+ http.set_nodelay(nodelay);
+ Connector {
+ inner: Inner::Http(http),
+ verbose: verbose::OFF,
+ proxies,
+ timeout: None,
+ }
+ }
+
+ #[cfg(feature = "default-tls")]
+ pub(crate) fn new_default_tls<T>(
+ http: HttpConnector,
+ tls: TlsConnectorBuilder,
+ proxies: Arc<Vec<Proxy>>,
+ user_agent: Option<HeaderValue>,
+ local_addr: T,
+ nodelay: bool,
+ ) -> crate::Result<Connector>
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ let tls = tls.build().map_err(crate::error::builder)?;
+ Ok(Self::from_built_default_tls(
+ http, tls, proxies, user_agent, local_addr, nodelay,
+ ))
+ }
+
+ #[cfg(feature = "default-tls")]
+ pub(crate) fn from_built_default_tls<T>(
+ mut http: HttpConnector,
+ tls: TlsConnector,
+ proxies: Arc<Vec<Proxy>>,
+ user_agent: Option<HeaderValue>,
+ local_addr: T,
+ nodelay: bool,
+ ) -> Connector
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ http.set_local_address(local_addr.into());
+ http.enforce_http(false);
+
+ Connector {
+ inner: Inner::DefaultTls(http, tls),
+ proxies,
+ verbose: verbose::OFF,
+ timeout: None,
+ nodelay,
+ user_agent,
+ }
+ }
+
+ #[cfg(feature = "__rustls")]
+ pub(crate) fn new_rustls_tls<T>(
+ mut http: HttpConnector,
+ tls: rustls::ClientConfig,
+ proxies: Arc<Vec<Proxy>>,
+ user_agent: Option<HeaderValue>,
+ local_addr: T,
+ nodelay: bool,
+ ) -> Connector
+ where
+ T: Into<Option<IpAddr>>,
+ {
+ http.set_local_address(local_addr.into());
+ http.enforce_http(false);
+
+ let (tls, tls_proxy) = if proxies.is_empty() {
+ let tls = Arc::new(tls);
+ (tls.clone(), tls)
+ } else {
+ let mut tls_proxy = tls.clone();
+ tls_proxy.alpn_protocols.clear();
+ (Arc::new(tls), Arc::new(tls_proxy))
+ };
+
+ Connector {
+ inner: Inner::RustlsTls {
+ http,
+ tls,
+ tls_proxy,
+ },
+ proxies,
+ verbose: verbose::OFF,
+ timeout: None,
+ nodelay,
+ user_agent,
+ }
+ }
+
+ pub(crate) fn set_timeout(&mut self, timeout: Option<Duration>) {
+ self.timeout = timeout;
+ }
+
+ pub(crate) fn set_verbose(&mut self, enabled: bool) {
+ self.verbose.0 = enabled;
+ }
+
+ #[cfg(feature = "socks")]
+ async fn connect_socks(&self, dst: Uri, proxy: ProxyScheme) -> Result<Conn, BoxError> {
+ let dns = match proxy {
+ ProxyScheme::Socks5 {
+ remote_dns: false, ..
+ } => socks::DnsResolve::Local,
+ ProxyScheme::Socks5 {
+ remote_dns: true, ..
+ } => socks::DnsResolve::Proxy,
+ ProxyScheme::Http { .. } | ProxyScheme::Https { .. } => {
+ unreachable!("connect_socks is only called for socks proxies");
+ }
+ };
+
+ match &self.inner {
+ #[cfg(feature = "default-tls")]
+ Inner::DefaultTls(_http, tls) => {
+ if dst.scheme() == Some(&Scheme::HTTPS) {
+ let host = dst.host().ok_or("no host in url")?.to_string();
+ let conn = socks::connect(proxy, dst, dns).await?;
+ let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone());
+ let io = tls_connector.connect(&host, conn).await?;
+ return Ok(Conn {
+ inner: self.verbose.wrap(NativeTlsConn { inner: io }),
+ is_proxy: false,
+ });
+ }
+ }
+ #[cfg(feature = "__rustls")]
+ Inner::RustlsTls { tls_proxy, .. } => {
+ if dst.scheme() == Some(&Scheme::HTTPS) {
+ use std::convert::TryFrom;
+ use tokio_rustls::TlsConnector as RustlsConnector;
+
+ let tls = tls_proxy.clone();
+ let host = dst.host().ok_or("no host in url")?.to_string();
+ let conn = socks::connect(proxy, dst, dns).await?;
+ let server_name = rustls::ServerName::try_from(host.as_str())
+ .map_err(|_| "Invalid Server Name")?;
+ let io = RustlsConnector::from(tls)
+ .connect(server_name, conn)
+ .await?;
+ return Ok(Conn {
+ inner: self.verbose.wrap(RustlsTlsConn { inner: io }),
+ is_proxy: false,
+ });
+ }
+ }
+ #[cfg(not(feature = "__tls"))]
+ Inner::Http(_) => (),
+ }
+
+ socks::connect(proxy, dst, dns).await.map(|tcp| Conn {
+ inner: self.verbose.wrap(tcp),
+ is_proxy: false,
+ })
+ }
+
+ async fn connect_with_maybe_proxy(self, dst: Uri, is_proxy: bool) -> Result<Conn, BoxError> {
+ match self.inner {
+ #[cfg(not(feature = "__tls"))]
+ Inner::Http(mut http) => {
+ let io = http.call(dst).await?;
+ Ok(Conn {
+ inner: self.verbose.wrap(io),
+ is_proxy,
+ })
+ }
+ #[cfg(feature = "default-tls")]
+ Inner::DefaultTls(http, tls) => {
+ let mut http = http.clone();
+
+ // Disable Nagle's algorithm for TLS handshake
+ //
+ // https://www.openssl.org/docs/man1.1.1/man3/SSL_connect.html#NOTES
+ if !self.nodelay && (dst.scheme() == Some(&Scheme::HTTPS)) {
+ http.set_nodelay(true);
+ }
+
+ let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone());
+ let mut http = hyper_tls::HttpsConnector::from((http, tls_connector));
+ let io = http.call(dst).await?;
+
+ if let hyper_tls::MaybeHttpsStream::Https(stream) = io {
+ if !self.nodelay {
+ stream.get_ref().get_ref().get_ref().set_nodelay(false)?;
+ }
+ Ok(Conn {
+ inner: self.verbose.wrap(NativeTlsConn { inner: stream }),
+ is_proxy,
+ })
+ } else {
+ Ok(Conn {
+ inner: self.verbose.wrap(io),
+ is_proxy,
+ })
+ }
+ }
+ #[cfg(feature = "__rustls")]
+ Inner::RustlsTls { http, tls, .. } => {
+ let mut http = http.clone();
+
+ // Disable Nagle's algorithm for TLS handshake
+ //
+ // https://www.openssl.org/docs/man1.1.1/man3/SSL_connect.html#NOTES
+ if !self.nodelay && (dst.scheme() == Some(&Scheme::HTTPS)) {
+ http.set_nodelay(true);
+ }
+
+ let mut http = hyper_rustls::HttpsConnector::from((http, tls.clone()));
+ let io = http.call(dst).await?;
+
+ if let hyper_rustls::MaybeHttpsStream::Https(stream) = io {
+ if !self.nodelay {
+ let (io, _) = stream.get_ref();
+ io.set_nodelay(false)?;
+ }
+ Ok(Conn {
+ inner: self.verbose.wrap(RustlsTlsConn { inner: stream }),
+ is_proxy,
+ })
+ } else {
+ Ok(Conn {
+ inner: self.verbose.wrap(io),
+ is_proxy,
+ })
+ }
+ }
+ }
+ }
+
+ async fn connect_via_proxy(
+ self,
+ dst: Uri,
+ proxy_scheme: ProxyScheme,
+ ) -> Result<Conn, BoxError> {
+ log::debug!("proxy({:?}) intercepts '{:?}'", proxy_scheme, dst);
+
+ let (proxy_dst, _auth) = match proxy_scheme {
+ ProxyScheme::Http { host, auth } => (into_uri(Scheme::HTTP, host), auth),
+ ProxyScheme::Https { host, auth } => (into_uri(Scheme::HTTPS, host), auth),
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 { .. } => return self.connect_socks(dst, proxy_scheme).await,
+ };
+
+ #[cfg(feature = "__tls")]
+ let auth = _auth;
+
+ match &self.inner {
+ #[cfg(feature = "default-tls")]
+ Inner::DefaultTls(http, tls) => {
+ if dst.scheme() == Some(&Scheme::HTTPS) {
+ let host = dst.host().to_owned();
+ let port = dst.port().map(|p| p.as_u16()).unwrap_or(443);
+ let http = http.clone();
+ let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone());
+ let mut http = hyper_tls::HttpsConnector::from((http, tls_connector));
+ let conn = http.call(proxy_dst).await?;
+ log::trace!("tunneling HTTPS over proxy");
+ let tunneled = tunnel(
+ conn,
+ host.ok_or("no host in url")?.to_string(),
+ port,
+ self.user_agent.clone(),
+ auth,
+ )
+ .await?;
+ let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone());
+ let io = tls_connector
+ .connect(host.ok_or("no host in url")?, tunneled)
+ .await?;
+ return Ok(Conn {
+ inner: self.verbose.wrap(NativeTlsConn { inner: io }),
+ is_proxy: false,
+ });
+ }
+ }
+ #[cfg(feature = "__rustls")]
+ Inner::RustlsTls {
+ http,
+ tls,
+ tls_proxy,
+ } => {
+ if dst.scheme() == Some(&Scheme::HTTPS) {
+ use rustls::ServerName;
+ use std::convert::TryFrom;
+ use tokio_rustls::TlsConnector as RustlsConnector;
+
+ let host = dst.host().ok_or("no host in url")?.to_string();
+ let port = dst.port().map(|r| r.as_u16()).unwrap_or(443);
+ let http = http.clone();
+ let mut http = hyper_rustls::HttpsConnector::from((http, tls_proxy.clone()));
+ let tls = tls.clone();
+ let conn = http.call(proxy_dst).await?;
+ log::trace!("tunneling HTTPS over proxy");
+ let maybe_server_name =
+ ServerName::try_from(host.as_str()).map_err(|_| "Invalid Server Name");
+ let tunneled = tunnel(conn, host, port, self.user_agent.clone(), auth).await?;
+ let server_name = maybe_server_name?;
+ let io = RustlsConnector::from(tls)
+ .connect(server_name, tunneled)
+ .await?;
+
+ return Ok(Conn {
+ inner: self.verbose.wrap(RustlsTlsConn { inner: io }),
+ is_proxy: false,
+ });
+ }
+ }
+ #[cfg(not(feature = "__tls"))]
+ Inner::Http(_) => (),
+ }
+
+ self.connect_with_maybe_proxy(proxy_dst, true).await
+ }
+
+ pub fn set_keepalive(&mut self, dur: Option<Duration>) {
+ match &mut self.inner {
+ #[cfg(feature = "default-tls")]
+ Inner::DefaultTls(http, _tls) => http.set_keepalive(dur),
+ #[cfg(feature = "__rustls")]
+ Inner::RustlsTls { http, .. } => http.set_keepalive(dur),
+ #[cfg(not(feature = "__tls"))]
+ Inner::Http(http) => http.set_keepalive(dur),
+ }
+ }
+}
+
+fn into_uri(scheme: Scheme, host: Authority) -> Uri {
+ // TODO: Should the `http` crate get `From<(Scheme, Authority)> for Uri`?
+ http::Uri::builder()
+ .scheme(scheme)
+ .authority(host)
+ .path_and_query(http::uri::PathAndQuery::from_static("/"))
+ .build()
+ .expect("scheme and authority is valid Uri")
+}
+
+async fn with_timeout<T, F>(f: F, timeout: Option<Duration>) -> Result<T, BoxError>
+where
+ F: Future<Output = Result<T, BoxError>>,
+{
+ if let Some(to) = timeout {
+ match tokio::time::timeout(to, f).await {
+ Err(_elapsed) => Err(Box::new(crate::error::TimedOut) as BoxError),
+ Ok(Ok(try_res)) => Ok(try_res),
+ Ok(Err(e)) => Err(e),
+ }
+ } else {
+ f.await
+ }
+}
+
+impl Service<Uri> for Connector {
+ type Response = Conn;
+ type Error = BoxError;
+ type Future = Connecting;
+
+ fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, dst: Uri) -> Self::Future {
+ log::debug!("starting new connection: {:?}", dst);
+ let timeout = self.timeout;
+ for prox in self.proxies.iter() {
+ if let Some(proxy_scheme) = prox.intercept(&dst) {
+ return Box::pin(with_timeout(
+ self.clone().connect_via_proxy(dst, proxy_scheme),
+ timeout,
+ ));
+ }
+ }
+
+ Box::pin(with_timeout(
+ self.clone().connect_with_maybe_proxy(dst, false),
+ timeout,
+ ))
+ }
+}
+
+pub(crate) trait AsyncConn:
+ AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static
+{
+}
+
+impl<T: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static> AsyncConn for T {}
+
+type BoxConn = Box<dyn AsyncConn>;
+
+pin_project! {
+ /// Note: the `is_proxy` member means *is plain text HTTP proxy*.
+ /// This tells hyper whether the URI should be written in
+ /// * origin-form (`GET /just/a/path HTTP/1.1`), when `is_proxy == false`, or
+ /// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise.
+ pub(crate) struct Conn {
+ #[pin]
+ inner: BoxConn,
+ is_proxy: bool,
+ }
+}
+
+impl Connection for Conn {
+ fn connected(&self) -> Connected {
+ self.inner.connected().proxy(self.is_proxy)
+ }
+}
+
+impl AsyncRead for Conn {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ let this = self.project();
+ AsyncRead::poll_read(this.inner, cx, buf)
+ }
+}
+
+impl AsyncWrite for Conn {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write(this.inner, cx, buf)
+ }
+
+ fn poll_write_vectored(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ bufs: &[IoSlice<'_>],
+ ) -> Poll<Result<usize, io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write_vectored(this.inner, cx, bufs)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
+
+ fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_flush(this.inner, cx)
+ }
+
+ fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_shutdown(this.inner, cx)
+ }
+}
+
+pub(crate) type Connecting = Pin<Box<dyn Future<Output = Result<Conn, BoxError>> + Send>>;
+
+#[cfg(feature = "__tls")]
+async fn tunnel<T>(
+ mut conn: T,
+ host: String,
+ port: u16,
+ user_agent: Option<HeaderValue>,
+ auth: Option<HeaderValue>,
+) -> Result<T, BoxError>
+where
+ T: AsyncRead + AsyncWrite + Unpin,
+{
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+ let mut buf = format!(
+ "\
+ CONNECT {0}:{1} HTTP/1.1\r\n\
+ Host: {0}:{1}\r\n\
+ ",
+ host, port
+ )
+ .into_bytes();
+
+ // user-agent
+ if let Some(user_agent) = user_agent {
+ buf.extend_from_slice(b"User-Agent: ");
+ buf.extend_from_slice(user_agent.as_bytes());
+ buf.extend_from_slice(b"\r\n");
+ }
+
+ // proxy-authorization
+ if let Some(value) = auth {
+ log::debug!("tunnel to {}:{} using basic auth", host, port);
+ buf.extend_from_slice(b"Proxy-Authorization: ");
+ buf.extend_from_slice(value.as_bytes());
+ buf.extend_from_slice(b"\r\n");
+ }
+
+ // headers end
+ buf.extend_from_slice(b"\r\n");
+
+ conn.write_all(&buf).await?;
+
+ let mut buf = [0; 8192];
+ let mut pos = 0;
+
+ loop {
+ let n = conn.read(&mut buf[pos..]).await?;
+
+ if n == 0 {
+ return Err(tunnel_eof());
+ }
+ pos += n;
+
+ let recvd = &buf[..pos];
+ if recvd.starts_with(b"HTTP/1.1 200") || recvd.starts_with(b"HTTP/1.0 200") {
+ if recvd.ends_with(b"\r\n\r\n") {
+ return Ok(conn);
+ }
+ if pos == buf.len() {
+ return Err("proxy headers too long for tunnel".into());
+ }
+ // else read more
+ } else if recvd.starts_with(b"HTTP/1.1 407") {
+ return Err("proxy authentication required".into());
+ } else {
+ return Err("unsuccessful tunnel".into());
+ }
+ }
+}
+
+#[cfg(feature = "__tls")]
+fn tunnel_eof() -> BoxError {
+ "unexpected eof while tunneling".into()
+}
+
+#[cfg(feature = "default-tls")]
+mod native_tls_conn {
+ use hyper::client::connect::{Connected, Connection};
+ use pin_project_lite::pin_project;
+ use std::{
+ io::{self, IoSlice},
+ pin::Pin,
+ task::{Context, Poll},
+ };
+ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+ use tokio_native_tls::TlsStream;
+
+ pin_project! {
+ pub(super) struct NativeTlsConn<T> {
+ #[pin] pub(super) inner: TlsStream<T>,
+ }
+ }
+
+ impl<T: Connection + AsyncRead + AsyncWrite + Unpin> Connection for NativeTlsConn<T> {
+ #[cfg(feature = "native-tls-alpn")]
+ fn connected(&self) -> Connected {
+ match self.inner.get_ref().negotiated_alpn().ok() {
+ Some(Some(alpn_protocol)) if alpn_protocol == b"h2" => self
+ .inner
+ .get_ref()
+ .get_ref()
+ .get_ref()
+ .connected()
+ .negotiated_h2(),
+ _ => self.inner.get_ref().get_ref().get_ref().connected(),
+ }
+ }
+
+ #[cfg(not(feature = "native-tls-alpn"))]
+ fn connected(&self) -> Connected {
+ self.inner.get_ref().get_ref().get_ref().connected()
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for NativeTlsConn<T> {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<tokio::io::Result<()>> {
+ let this = self.project();
+ AsyncRead::poll_read(this.inner, cx, buf)
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for NativeTlsConn<T> {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write(this.inner, cx, buf)
+ }
+
+ fn poll_write_vectored(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ bufs: &[IoSlice<'_>],
+ ) -> Poll<Result<usize, io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write_vectored(this.inner, cx, bufs)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
+
+ fn poll_flush(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_flush(this.inner, cx)
+ }
+
+ fn poll_shutdown(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_shutdown(this.inner, cx)
+ }
+ }
+}
+
+#[cfg(feature = "__rustls")]
+mod rustls_tls_conn {
+ use hyper::client::connect::{Connected, Connection};
+ use pin_project_lite::pin_project;
+ use std::{
+ io::{self, IoSlice},
+ pin::Pin,
+ task::{Context, Poll},
+ };
+ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+ use tokio_rustls::client::TlsStream;
+
+ pin_project! {
+ pub(super) struct RustlsTlsConn<T> {
+ #[pin] pub(super) inner: TlsStream<T>,
+ }
+ }
+
+ impl<T: Connection + AsyncRead + AsyncWrite + Unpin> Connection for RustlsTlsConn<T> {
+ fn connected(&self) -> Connected {
+ if self.inner.get_ref().1.alpn_protocol() == Some(b"h2") {
+ self.inner.get_ref().0.connected().negotiated_h2()
+ } else {
+ self.inner.get_ref().0.connected()
+ }
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for RustlsTlsConn<T> {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<tokio::io::Result<()>> {
+ let this = self.project();
+ AsyncRead::poll_read(this.inner, cx, buf)
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for RustlsTlsConn<T> {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write(this.inner, cx, buf)
+ }
+
+ fn poll_write_vectored(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ bufs: &[IoSlice<'_>],
+ ) -> Poll<Result<usize, io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_write_vectored(this.inner, cx, bufs)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
+
+ fn poll_flush(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_flush(this.inner, cx)
+ }
+
+ fn poll_shutdown(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), tokio::io::Error>> {
+ let this = self.project();
+ AsyncWrite::poll_shutdown(this.inner, cx)
+ }
+ }
+}
+
+#[cfg(feature = "socks")]
+mod socks {
+ use std::io;
+ use std::net::ToSocketAddrs;
+
+ use http::Uri;
+ use tokio::net::TcpStream;
+ use tokio_socks::tcp::Socks5Stream;
+
+ use super::{BoxError, Scheme};
+ use crate::proxy::ProxyScheme;
+
+ pub(super) enum DnsResolve {
+ Local,
+ Proxy,
+ }
+
+ pub(super) async fn connect(
+ proxy: ProxyScheme,
+ dst: Uri,
+ dns: DnsResolve,
+ ) -> Result<TcpStream, BoxError> {
+ let https = dst.scheme() == Some(&Scheme::HTTPS);
+ let original_host = dst
+ .host()
+ .ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?;
+ let mut host = original_host.to_owned();
+ let port = match dst.port() {
+ Some(p) => p.as_u16(),
+ None if https => 443u16,
+ _ => 80u16,
+ };
+
+ if let DnsResolve::Local = dns {
+ let maybe_new_target = (host.as_str(), port).to_socket_addrs()?.next();
+ if let Some(new_target) = maybe_new_target {
+ host = new_target.ip().to_string();
+ }
+ }
+
+ let (socket_addr, auth) = match proxy {
+ ProxyScheme::Socks5 { addr, auth, .. } => (addr, auth),
+ _ => unreachable!(),
+ };
+
+ // Get a Tokio TcpStream
+ let stream = if let Some((username, password)) = auth {
+ Socks5Stream::connect_with_password(
+ socket_addr,
+ (host.as_str(), port),
+ &username,
+ &password,
+ )
+ .await
+ .map_err(|e| format!("socks connect error: {}", e))?
+ } else {
+ Socks5Stream::connect(socket_addr, (host.as_str(), port))
+ .await
+ .map_err(|e| format!("socks connect error: {}", e))?
+ };
+
+ Ok(stream.into_inner())
+ }
+}
+
+mod verbose {
+ use hyper::client::connect::{Connected, Connection};
+ use std::cmp::min;
+ use std::fmt;
+ use std::io::{self, IoSlice};
+ use std::pin::Pin;
+ use std::task::{Context, Poll};
+ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+
+ pub(super) const OFF: Wrapper = Wrapper(false);
+
+ #[derive(Clone, Copy)]
+ pub(super) struct Wrapper(pub(super) bool);
+
+ impl Wrapper {
+ pub(super) fn wrap<T: super::AsyncConn>(&self, conn: T) -> super::BoxConn {
+ if self.0 && log::log_enabled!(log::Level::Trace) {
+ Box::new(Verbose {
+ // truncate is fine
+ id: crate::util::fast_random() as u32,
+ inner: conn,
+ })
+ } else {
+ Box::new(conn)
+ }
+ }
+ }
+
+ struct Verbose<T> {
+ id: u32,
+ inner: T,
+ }
+
+ impl<T: Connection + AsyncRead + AsyncWrite + Unpin> Connection for Verbose<T> {
+ fn connected(&self) -> Connected {
+ self.inner.connected()
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncRead for Verbose<T> {
+ fn poll_read(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<std::io::Result<()>> {
+ match Pin::new(&mut self.inner).poll_read(cx, buf) {
+ Poll::Ready(Ok(())) => {
+ log::trace!("{:08x} read: {:?}", self.id, Escape(buf.filled()));
+ Poll::Ready(Ok(()))
+ }
+ Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
+ Poll::Pending => Poll::Pending,
+ }
+ }
+ }
+
+ impl<T: AsyncRead + AsyncWrite + Unpin> AsyncWrite for Verbose<T> {
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, std::io::Error>> {
+ match Pin::new(&mut self.inner).poll_write(cx, buf) {
+ Poll::Ready(Ok(n)) => {
+ log::trace!("{:08x} write: {:?}", self.id, Escape(&buf[..n]));
+ Poll::Ready(Ok(n))
+ }
+ Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
+ Poll::Pending => Poll::Pending,
+ }
+ }
+
+ fn poll_write_vectored(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ bufs: &[IoSlice<'_>],
+ ) -> Poll<Result<usize, io::Error>> {
+ match Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) {
+ Poll::Ready(Ok(nwritten)) => {
+ log::trace!(
+ "{:08x} write (vectored): {:?}",
+ self.id,
+ Vectored { bufs, nwritten }
+ );
+ Poll::Ready(Ok(nwritten))
+ }
+ Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
+ Poll::Pending => Poll::Pending,
+ }
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner.is_write_vectored()
+ }
+
+ fn poll_flush(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), std::io::Error>> {
+ Pin::new(&mut self.inner).poll_flush(cx)
+ }
+
+ fn poll_shutdown(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context,
+ ) -> Poll<Result<(), std::io::Error>> {
+ Pin::new(&mut self.inner).poll_shutdown(cx)
+ }
+ }
+
+ struct Escape<'a>(&'a [u8]);
+
+ impl fmt::Debug for Escape<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "b\"")?;
+ for &c in self.0 {
+ // https://doc.rust-lang.org/reference.html#byte-escapes
+ if c == b'\n' {
+ write!(f, "\\n")?;
+ } else if c == b'\r' {
+ write!(f, "\\r")?;
+ } else if c == b'\t' {
+ write!(f, "\\t")?;
+ } else if c == b'\\' || c == b'"' {
+ write!(f, "\\{}", c as char)?;
+ } else if c == b'\0' {
+ write!(f, "\\0")?;
+ // ASCII printable
+ } else if c >= 0x20 && c < 0x7f {
+ write!(f, "{}", c as char)?;
+ } else {
+ write!(f, "\\x{:02x}", c)?;
+ }
+ }
+ write!(f, "\"")?;
+ Ok(())
+ }
+ }
+
+ struct Vectored<'a, 'b> {
+ bufs: &'a [IoSlice<'b>],
+ nwritten: usize,
+ }
+
+ impl fmt::Debug for Vectored<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut left = self.nwritten;
+ for buf in self.bufs.iter() {
+ if left == 0 {
+ break;
+ }
+ let n = min(left, buf.len());
+ Escape(&buf[..n]).fmt(f)?;
+ left -= n;
+ }
+ Ok(())
+ }
+ }
+}
+
+#[cfg(feature = "__tls")]
+#[cfg(test)]
+mod tests {
+ use super::tunnel;
+ use crate::proxy;
+ use std::io::{Read, Write};
+ use std::net::TcpListener;
+ use std::thread;
+ use tokio::net::TcpStream;
+ use tokio::runtime;
+
+ static TUNNEL_UA: &str = "tunnel-test/x.y";
+ static TUNNEL_OK: &[u8] = b"\
+ HTTP/1.1 200 OK\r\n\
+ \r\n\
+ ";
+
+ macro_rules! mock_tunnel {
+ () => {{
+ mock_tunnel!(TUNNEL_OK)
+ }};
+ ($write:expr) => {{
+ mock_tunnel!($write, "")
+ }};
+ ($write:expr, $auth:expr) => {{
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = listener.local_addr().unwrap();
+ let connect_expected = format!(
+ "\
+ CONNECT {0}:{1} HTTP/1.1\r\n\
+ Host: {0}:{1}\r\n\
+ User-Agent: {2}\r\n\
+ {3}\
+ \r\n\
+ ",
+ addr.ip(),
+ addr.port(),
+ TUNNEL_UA,
+ $auth
+ )
+ .into_bytes();
+
+ thread::spawn(move || {
+ let (mut sock, _) = listener.accept().unwrap();
+ let mut buf = [0u8; 4096];
+ let n = sock.read(&mut buf).unwrap();
+ assert_eq!(&buf[..n], &connect_expected[..]);
+
+ sock.write_all($write).unwrap();
+ });
+ addr
+ }};
+ }
+
+ fn ua() -> Option<http::header::HeaderValue> {
+ Some(http::header::HeaderValue::from_static(TUNNEL_UA))
+ }
+
+ #[test]
+ fn test_tunnel() {
+ let addr = mock_tunnel!();
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let f = async move {
+ let tcp = TcpStream::connect(&addr).await?;
+ let host = addr.ip().to_string();
+ let port = addr.port();
+ tunnel(tcp, host, port, ua(), None).await
+ };
+
+ rt.block_on(f).unwrap();
+ }
+
+ #[test]
+ fn test_tunnel_eof() {
+ let addr = mock_tunnel!(b"HTTP/1.1 200 OK");
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let f = async move {
+ let tcp = TcpStream::connect(&addr).await?;
+ let host = addr.ip().to_string();
+ let port = addr.port();
+ tunnel(tcp, host, port, ua(), None).await
+ };
+
+ rt.block_on(f).unwrap_err();
+ }
+
+ #[test]
+ fn test_tunnel_non_http_response() {
+ let addr = mock_tunnel!(b"foo bar baz hallo");
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let f = async move {
+ let tcp = TcpStream::connect(&addr).await?;
+ let host = addr.ip().to_string();
+ let port = addr.port();
+ tunnel(tcp, host, port, ua(), None).await
+ };
+
+ rt.block_on(f).unwrap_err();
+ }
+
+ #[test]
+ fn test_tunnel_proxy_unauthorized() {
+ let addr = mock_tunnel!(
+ b"\
+ HTTP/1.1 407 Proxy Authentication Required\r\n\
+ Proxy-Authenticate: Basic realm=\"nope\"\r\n\
+ \r\n\
+ "
+ );
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let f = async move {
+ let tcp = TcpStream::connect(&addr).await?;
+ let host = addr.ip().to_string();
+ let port = addr.port();
+ tunnel(tcp, host, port, ua(), None).await
+ };
+
+ let error = rt.block_on(f).unwrap_err();
+ assert_eq!(error.to_string(), "proxy authentication required");
+ }
+
+ #[test]
+ fn test_tunnel_basic_auth() {
+ let addr = mock_tunnel!(
+ TUNNEL_OK,
+ "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n"
+ );
+
+ let rt = runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("new rt");
+ let f = async move {
+ let tcp = TcpStream::connect(&addr).await?;
+ let host = addr.ip().to_string();
+ let port = addr.port();
+ tunnel(
+ tcp,
+ host,
+ port,
+ ua(),
+ Some(proxy::encode_basic_auth("Aladdin", "open sesame")),
+ )
+ .await
+ };
+
+ rt.block_on(f).unwrap();
+ }
+}
diff --git a/vendor/reqwest/src/cookie.rs b/vendor/reqwest/src/cookie.rs
new file mode 100644
index 000000000..4363301ea
--- /dev/null
+++ b/vendor/reqwest/src/cookie.rs
@@ -0,0 +1,191 @@
+//! HTTP Cookies
+
+use std::convert::TryInto;
+use std::fmt;
+use std::sync::RwLock;
+use std::time::SystemTime;
+
+use crate::header::{HeaderValue, SET_COOKIE};
+use bytes::Bytes;
+
+/// Actions for a persistent cookie store providing session support.
+pub trait CookieStore: Send + Sync {
+ /// Store a set of Set-Cookie header values received from `url`
+ fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url);
+ /// Get any Cookie values in the store for `url`
+ fn cookies(&self, url: &url::Url) -> Option<HeaderValue>;
+}
+
+/// A single HTTP cookie.
+pub struct Cookie<'a>(cookie_crate::Cookie<'a>);
+
+/// A good default `CookieStore` implementation.
+///
+/// This is the implementation used when simply calling `cookie_store(true)`.
+/// This type is exposed to allow creating one and filling it with some
+/// existing cookies more easily, before creating a `Client`.
+///
+/// For more advanced scenarios, such as needing to serialize the store or
+/// manipulate it between requests, you may refer to the
+/// [reqwest_cookie_store crate](https://crates.io/crates/reqwest_cookie_store).
+#[derive(Debug, Default)]
+pub struct Jar(RwLock<cookie_store::CookieStore>);
+
+// ===== impl Cookie =====
+
+impl<'a> Cookie<'a> {
+ fn parse(value: &'a HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
+ std::str::from_utf8(value.as_bytes())
+ .map_err(cookie_crate::ParseError::from)
+ .and_then(cookie_crate::Cookie::parse)
+ .map_err(CookieParseError)
+ .map(Cookie)
+ }
+
+ /// The name of the cookie.
+ pub fn name(&self) -> &str {
+ self.0.name()
+ }
+
+ /// The value of the cookie.
+ pub fn value(&self) -> &str {
+ self.0.value()
+ }
+
+ /// Returns true if the 'HttpOnly' directive is enabled.
+ pub fn http_only(&self) -> bool {
+ self.0.http_only().unwrap_or(false)
+ }
+
+ /// Returns true if the 'Secure' directive is enabled.
+ pub fn secure(&self) -> bool {
+ self.0.secure().unwrap_or(false)
+ }
+
+ /// Returns true if 'SameSite' directive is 'Lax'.
+ pub fn same_site_lax(&self) -> bool {
+ self.0.same_site() == Some(cookie_crate::SameSite::Lax)
+ }
+
+ /// Returns true if 'SameSite' directive is 'Strict'.
+ pub fn same_site_strict(&self) -> bool {
+ self.0.same_site() == Some(cookie_crate::SameSite::Strict)
+ }
+
+ /// Returns the path directive of the cookie, if set.
+ pub fn path(&self) -> Option<&str> {
+ self.0.path()
+ }
+
+ /// Returns the domain directive of the cookie, if set.
+ pub fn domain(&self) -> Option<&str> {
+ self.0.domain()
+ }
+
+ /// Get the Max-Age information.
+ pub fn max_age(&self) -> Option<std::time::Duration> {
+ self.0.max_age().map(|d| {
+ d.try_into()
+ .expect("time::Duration into std::time::Duration")
+ })
+ }
+
+ /// The cookie expiration time.
+ pub fn expires(&self) -> Option<SystemTime> {
+ match self.0.expires() {
+ Some(cookie_crate::Expiration::DateTime(offset)) => Some(SystemTime::from(offset)),
+ None | Some(cookie_crate::Expiration::Session) => None,
+ }
+ }
+}
+
+impl<'a> fmt::Debug for Cookie<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+pub(crate) fn extract_response_cookie_headers<'a>(
+ headers: &'a hyper::HeaderMap,
+) -> impl Iterator<Item = &'a HeaderValue> + 'a {
+ headers.get_all(SET_COOKIE).iter()
+}
+
+pub(crate) fn extract_response_cookies<'a>(
+ headers: &'a hyper::HeaderMap,
+) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
+ headers
+ .get_all(SET_COOKIE)
+ .iter()
+ .map(|value| Cookie::parse(value))
+}
+
+/// Error representing a parse failure of a 'Set-Cookie' header.
+pub(crate) struct CookieParseError(cookie_crate::ParseError);
+
+impl<'a> fmt::Debug for CookieParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<'a> fmt::Display for CookieParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl std::error::Error for CookieParseError {}
+
+// ===== impl Jar =====
+
+impl Jar {
+ /// Add a cookie to this jar.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use reqwest::{cookie::Jar, Url};
+ ///
+ /// let cookie = "foo=bar; Domain=yolo.local";
+ /// let url = "https://yolo.local".parse::<Url>().unwrap();
+ ///
+ /// let jar = Jar::default();
+ /// jar.add_cookie_str(cookie, &url);
+ ///
+ /// // and now add to a `ClientBuilder`?
+ /// ```
+ pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) {
+ let cookies = cookie_crate::Cookie::parse(cookie)
+ .ok()
+ .map(|c| c.into_owned())
+ .into_iter();
+ self.0.write().unwrap().store_response_cookies(cookies, url);
+ }
+}
+
+impl CookieStore for Jar {
+ fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
+ let iter =
+ cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok());
+
+ self.0.write().unwrap().store_response_cookies(iter, url);
+ }
+
+ fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
+ let s = self
+ .0
+ .read()
+ .unwrap()
+ .get_request_values(url)
+ .map(|(name, value)| format!("{}={}", name, value))
+ .collect::<Vec<_>>()
+ .join("; ");
+
+ if s.is_empty() {
+ return None;
+ }
+
+ HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
+ }
+}
diff --git a/vendor/reqwest/src/dns/gai.rs b/vendor/reqwest/src/dns/gai.rs
new file mode 100644
index 000000000..f32f3b0e0
--- /dev/null
+++ b/vendor/reqwest/src/dns/gai.rs
@@ -0,0 +1,32 @@
+use futures_util::future::FutureExt;
+use hyper::client::connect::dns::{GaiResolver as HyperGaiResolver, Name};
+use hyper::service::Service;
+
+use crate::dns::{Addrs, Resolve, Resolving};
+use crate::error::BoxError;
+
+#[derive(Debug)]
+pub struct GaiResolver(HyperGaiResolver);
+
+impl GaiResolver {
+ pub fn new() -> Self {
+ Self(HyperGaiResolver::new())
+ }
+}
+
+impl Default for GaiResolver {
+ fn default() -> Self {
+ GaiResolver::new()
+ }
+}
+
+impl Resolve for GaiResolver {
+ fn resolve(&self, name: Name) -> Resolving {
+ let this = &mut self.0.clone();
+ Box::pin(Service::<Name>::call(this, name).map(|result| {
+ result
+ .map(|addrs| -> Addrs { Box::new(addrs) })
+ .map_err(|err| -> BoxError { Box::new(err) })
+ }))
+ }
+}
diff --git a/vendor/reqwest/src/dns/mod.rs b/vendor/reqwest/src/dns/mod.rs
new file mode 100644
index 000000000..40cdabf9e
--- /dev/null
+++ b/vendor/reqwest/src/dns/mod.rs
@@ -0,0 +1,9 @@
+//! DNS resolution
+
+pub use resolve::{Addrs, Resolve, Resolving};
+pub(crate) use resolve::{DnsResolverWithOverrides, DynResolver};
+
+pub(crate) mod gai;
+pub(crate) mod resolve;
+#[cfg(feature = "trust-dns")]
+pub(crate) mod trust_dns;
diff --git a/vendor/reqwest/src/dns/resolve.rs b/vendor/reqwest/src/dns/resolve.rs
new file mode 100644
index 000000000..3686765a0
--- /dev/null
+++ b/vendor/reqwest/src/dns/resolve.rs
@@ -0,0 +1,84 @@
+use hyper::client::connect::dns::Name;
+use hyper::service::Service;
+
+use std::collections::HashMap;
+use std::future::Future;
+use std::net::SocketAddr;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+
+use crate::error::BoxError;
+
+/// Alias for an `Iterator` trait object over `SocketAddr`.
+pub type Addrs = Box<dyn Iterator<Item = SocketAddr> + Send>;
+
+/// Alias for the `Future` type returned by a DNS resolver.
+pub type Resolving = Pin<Box<dyn Future<Output = Result<Addrs, BoxError>> + Send>>;
+
+/// Trait for customizing DNS resolution in reqwest.
+pub trait Resolve: Send + Sync {
+ /// Performs DNS resolution on a `Name`.
+ /// The return type is a future containing an iterator of `SocketAddr`.
+ ///
+ /// It differs from `tower_service::Service<Name>` in several ways:
+ /// * It is assumed that `resolve` will always be ready to poll.
+ /// * It does not need a mutable reference to `self`.
+ /// * Since trait objects cannot make use of associated types, it requires
+ /// wrapping the returned `Future` and its contained `Iterator` with `Box`.
+ fn resolve(&self, name: Name) -> Resolving;
+}
+
+#[derive(Clone)]
+pub(crate) struct DynResolver {
+ resolver: Arc<dyn Resolve>,
+}
+
+impl DynResolver {
+ pub(crate) fn new(resolver: Arc<dyn Resolve>) -> Self {
+ Self { resolver }
+ }
+}
+
+impl Service<Name> for DynResolver {
+ type Response = Addrs;
+ type Error = BoxError;
+ type Future = Resolving;
+
+ fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, name: Name) -> Self::Future {
+ self.resolver.resolve(name)
+ }
+}
+
+pub(crate) struct DnsResolverWithOverrides {
+ dns_resolver: Arc<dyn Resolve>,
+ overrides: Arc<HashMap<String, Vec<SocketAddr>>>,
+}
+
+impl DnsResolverWithOverrides {
+ pub(crate) fn new(
+ dns_resolver: Arc<dyn Resolve>,
+ overrides: HashMap<String, Vec<SocketAddr>>,
+ ) -> Self {
+ DnsResolverWithOverrides {
+ dns_resolver,
+ overrides: Arc::new(overrides),
+ }
+ }
+}
+
+impl Resolve for DnsResolverWithOverrides {
+ fn resolve(&self, name: Name) -> Resolving {
+ match self.overrides.get(name.as_str()) {
+ Some(dest) => {
+ let addrs: Addrs = Box::new(dest.clone().into_iter());
+ Box::pin(futures_util::future::ready(Ok(addrs)))
+ }
+ None => self.dns_resolver.resolve(name),
+ }
+ }
+}
diff --git a/vendor/reqwest/src/dns/trust_dns.rs b/vendor/reqwest/src/dns/trust_dns.rs
new file mode 100644
index 000000000..129000c8b
--- /dev/null
+++ b/vendor/reqwest/src/dns/trust_dns.rs
@@ -0,0 +1,108 @@
+//! DNS resolution via the [trust_dns_resolver](https://github.com/bluejekyll/trust-dns) crate
+
+use hyper::client::connect::dns::Name;
+use once_cell::sync::Lazy;
+use tokio::sync::Mutex;
+pub use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
+use trust_dns_resolver::{
+ lookup_ip::LookupIpIntoIter, system_conf, AsyncResolver, TokioConnection,
+ TokioConnectionProvider, TokioHandle,
+};
+
+use std::io;
+use std::net::SocketAddr;
+use std::sync::Arc;
+
+use super::{Addrs, Resolve, Resolving};
+
+use crate::error::BoxError;
+
+type SharedResolver = Arc<AsyncResolver<TokioConnection, TokioConnectionProvider>>;
+
+static SYSTEM_CONF: Lazy<io::Result<(ResolverConfig, ResolverOpts)>> =
+ Lazy::new(|| system_conf::read_system_conf().map_err(io::Error::from));
+
+/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
+#[derive(Debug, Clone)]
+pub(crate) struct TrustDnsResolver {
+ state: Arc<Mutex<State>>,
+}
+
+struct SocketAddrs {
+ iter: LookupIpIntoIter,
+}
+
+#[derive(Debug)]
+enum State {
+ Init,
+ Ready(SharedResolver),
+}
+
+impl TrustDnsResolver {
+ /// Create a new resolver with the default configuration,
+ /// which reads from `/etc/resolve.conf`.
+ pub fn new() -> io::Result<Self> {
+ SYSTEM_CONF.as_ref().map_err(|e| {
+ io::Error::new(e.kind(), format!("error reading DNS system conf: {}", e))
+ })?;
+
+ // At this stage, we might not have been called in the context of a
+ // Tokio Runtime, so we must delay the actual construction of the
+ // resolver.
+ Ok(TrustDnsResolver {
+ state: Arc::new(Mutex::new(State::Init)),
+ })
+ }
+}
+
+impl Resolve for TrustDnsResolver {
+ fn resolve(&self, name: Name) -> Resolving {
+ let resolver = self.clone();
+ Box::pin(async move {
+ let mut lock = resolver.state.lock().await;
+
+ let resolver = match &*lock {
+ State::Init => {
+ let resolver = new_resolver().await?;
+ *lock = State::Ready(resolver.clone());
+ resolver
+ }
+ State::Ready(resolver) => resolver.clone(),
+ };
+
+ // Don't keep lock once the resolver is constructed, otherwise
+ // only one lookup could be done at a time.
+ drop(lock);
+
+ let lookup = resolver.lookup_ip(name.as_str()).await?;
+ let addrs: Addrs = Box::new(SocketAddrs {
+ iter: lookup.into_iter(),
+ });
+ Ok(addrs)
+ })
+ }
+}
+
+impl Iterator for SocketAddrs {
+ type Item = SocketAddr;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
+ }
+}
+
+async fn new_resolver() -> Result<SharedResolver, BoxError> {
+ let (config, opts) = SYSTEM_CONF
+ .as_ref()
+ .expect("can't construct TrustDnsResolver if SYSTEM_CONF is error")
+ .clone();
+ new_resolver_with_config(config, opts)
+}
+
+fn new_resolver_with_config(
+ config: ResolverConfig,
+ opts: ResolverOpts,
+) -> Result<SharedResolver, BoxError> {
+ let resolver = AsyncResolver::new(config, opts, TokioHandle)?;
+ Ok(Arc::new(resolver))
+}
diff --git a/vendor/reqwest/src/error.rs b/vendor/reqwest/src/error.rs
new file mode 100644
index 000000000..0e6bd247d
--- /dev/null
+++ b/vendor/reqwest/src/error.rs
@@ -0,0 +1,383 @@
+#![cfg_attr(target_arch = "wasm32", allow(unused))]
+use std::error::Error as StdError;
+use std::fmt;
+use std::io;
+
+use crate::{StatusCode, Url};
+
+/// A `Result` alias where the `Err` case is `reqwest::Error`.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// The Errors that may occur when processing a `Request`.
+///
+/// Note: Errors may include the full URL used to make the `Request`. If the URL
+/// contains sensitive information (e.g. an API key as a query parameter), be
+/// sure to remove it ([`without_url`](Error::without_url))
+pub struct Error {
+ inner: Box<Inner>,
+}
+
+pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
+
+struct Inner {
+ kind: Kind,
+ source: Option<BoxError>,
+ url: Option<Url>,
+}
+
+impl Error {
+ pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
+ where
+ E: Into<BoxError>,
+ {
+ Error {
+ inner: Box::new(Inner {
+ kind,
+ source: source.map(Into::into),
+ url: None,
+ }),
+ }
+ }
+
+ /// Returns a possible URL related to this error.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # async fn run() {
+ /// // displays last stop of a redirect loop
+ /// let response = reqwest::get("http://site.with.redirect.loop").await;
+ /// if let Err(e) = response {
+ /// if e.is_redirect() {
+ /// if let Some(final_stop) = e.url() {
+ /// println!("redirect loop at {}", final_stop);
+ /// }
+ /// }
+ /// }
+ /// # }
+ /// ```
+ pub fn url(&self) -> Option<&Url> {
+ self.inner.url.as_ref()
+ }
+
+ /// Returns a mutable reference to the URL related to this error
+ ///
+ /// This is useful if you need to remove sensitive information from the URL
+ /// (e.g. an API key in the query), but do not want to remove the URL
+ /// entirely.
+ pub fn url_mut(&mut self) -> Option<&mut Url> {
+ self.inner.url.as_mut()
+ }
+
+ /// Add a url related to this error (overwriting any existing)
+ pub fn with_url(mut self, url: Url) -> Self {
+ self.inner.url = Some(url);
+ self
+ }
+
+ /// Strip the related url from this error (if, for example, it contains
+ /// sensitive information)
+ pub fn without_url(mut self) -> Self {
+ self.inner.url = None;
+ self
+ }
+
+ /// Returns true if the error is from a type Builder.
+ pub fn is_builder(&self) -> bool {
+ matches!(self.inner.kind, Kind::Builder)
+ }
+
+ /// Returns true if the error is from a `RedirectPolicy`.
+ pub fn is_redirect(&self) -> bool {
+ matches!(self.inner.kind, Kind::Redirect)
+ }
+
+ /// Returns true if the error is from `Response::error_for_status`.
+ pub fn is_status(&self) -> bool {
+ matches!(self.inner.kind, Kind::Status(_))
+ }
+
+ /// Returns true if the error is related to a timeout.
+ pub fn is_timeout(&self) -> bool {
+ let mut source = self.source();
+
+ while let Some(err) = source {
+ if err.is::<TimedOut>() {
+ return true;
+ }
+ source = err.source();
+ }
+
+ false
+ }
+
+ /// Returns true if the error is related to the request
+ pub fn is_request(&self) -> bool {
+ matches!(self.inner.kind, Kind::Request)
+ }
+
+ #[cfg(not(target_arch = "wasm32"))]
+ /// Returns true if the error is related to connect
+ pub fn is_connect(&self) -> bool {
+ let mut source = self.source();
+
+ while let Some(err) = source {
+ if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
+ if hyper_err.is_connect() {
+ return true;
+ }
+ }
+
+ source = err.source();
+ }
+
+ false
+ }
+
+ /// Returns true if the error is related to the request or response body
+ pub fn is_body(&self) -> bool {
+ matches!(self.inner.kind, Kind::Body)
+ }
+
+ /// Returns true if the error is related to decoding the response's body
+ pub fn is_decode(&self) -> bool {
+ matches!(self.inner.kind, Kind::Decode)
+ }
+
+ /// Returns the status code, if the error was generated from a response.
+ pub fn status(&self) -> Option<StatusCode> {
+ match self.inner.kind {
+ Kind::Status(code) => Some(code),
+ _ => None,
+ }
+ }
+
+ // private
+
+ #[allow(unused)]
+ pub(crate) fn into_io(self) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, self)
+ }
+}
+
+impl fmt::Debug for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("reqwest::Error");
+
+ builder.field("kind", &self.inner.kind);
+
+ if let Some(ref url) = self.inner.url {
+ builder.field("url", url);
+ }
+ if let Some(ref source) = self.inner.source {
+ builder.field("source", source);
+ }
+
+ builder.finish()
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.inner.kind {
+ Kind::Builder => f.write_str("builder error")?,
+ Kind::Request => f.write_str("error sending request")?,
+ Kind::Body => f.write_str("request or response body error")?,
+ Kind::Decode => f.write_str("error decoding response body")?,
+ Kind::Redirect => f.write_str("error following redirect")?,
+ Kind::Upgrade => f.write_str("error upgrading connection")?,
+ Kind::Status(ref code) => {
+ let prefix = if code.is_client_error() {
+ "HTTP status client error"
+ } else {
+ debug_assert!(code.is_server_error());
+ "HTTP status server error"
+ };
+ write!(f, "{} ({})", prefix, code)?;
+ }
+ };
+
+ if let Some(url) = &self.inner.url {
+ write!(f, " for url ({})", url.as_str())?;
+ }
+
+ if let Some(e) = &self.inner.source {
+ write!(f, ": {}", e)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl StdError for Error {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ self.inner.source.as_ref().map(|e| &**e as _)
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl From<crate::error::Error> for wasm_bindgen::JsValue {
+ fn from(err: Error) -> wasm_bindgen::JsValue {
+ js_sys::Error::from(err).into()
+ }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl From<crate::error::Error> for js_sys::Error {
+ fn from(err: Error) -> js_sys::Error {
+ js_sys::Error::new(&format!("{}", err))
+ }
+}
+
+#[derive(Debug)]
+pub(crate) enum Kind {
+ Builder,
+ Request,
+ Redirect,
+ Status(StatusCode),
+ Body,
+ Decode,
+ Upgrade,
+}
+
+// constructors
+
+pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
+ Error::new(Kind::Builder, Some(e))
+}
+
+pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
+ Error::new(Kind::Body, Some(e))
+}
+
+pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
+ Error::new(Kind::Decode, Some(e))
+}
+
+pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
+ Error::new(Kind::Request, Some(e))
+}
+
+pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
+ Error::new(Kind::Redirect, Some(e)).with_url(url)
+}
+
+pub(crate) fn status_code(url: Url, status: StatusCode) -> Error {
+ Error::new(Kind::Status(status), None::<Error>).with_url(url)
+}
+
+pub(crate) fn url_bad_scheme(url: Url) -> Error {
+ Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
+}
+
+if_wasm! {
+ pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
+ format!("{:?}", js_val).into()
+ }
+}
+
+pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
+ Error::new(Kind::Upgrade, Some(e))
+}
+
+// io::Error helpers
+
+#[allow(unused)]
+pub(crate) fn into_io(e: Error) -> io::Error {
+ e.into_io()
+}
+
+#[allow(unused)]
+pub(crate) fn decode_io(e: io::Error) -> Error {
+ if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
+ *e.into_inner()
+ .expect("io::Error::get_ref was Some(_)")
+ .downcast::<Error>()
+ .expect("StdError::is() was true")
+ } else {
+ decode(e)
+ }
+}
+
+// internal Error "sources"
+
+#[derive(Debug)]
+pub(crate) struct TimedOut;
+
+impl fmt::Display for TimedOut {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("operation timed out")
+ }
+}
+
+impl StdError for TimedOut {}
+
+#[derive(Debug)]
+pub(crate) struct BadScheme;
+
+impl fmt::Display for BadScheme {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("URL scheme is not allowed")
+ }
+}
+
+impl StdError for BadScheme {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn assert_send<T: Send>() {}
+ fn assert_sync<T: Sync>() {}
+
+ #[test]
+ fn test_source_chain() {
+ let root = Error::new(Kind::Request, None::<Error>);
+ assert!(root.source().is_none());
+
+ let link = super::body(root);
+ assert!(link.source().is_some());
+ assert_send::<Error>();
+ assert_sync::<Error>();
+ }
+
+ #[test]
+ fn mem_size_of() {
+ use std::mem::size_of;
+ assert_eq!(size_of::<Error>(), size_of::<usize>());
+ }
+
+ #[test]
+ fn roundtrip_io_error() {
+ let orig = super::request("orig");
+ // Convert reqwest::Error into an io::Error...
+ let io = orig.into_io();
+ // Convert that io::Error back into a reqwest::Error...
+ let err = super::decode_io(io);
+ // It should have pulled out the original, not nested it...
+ match err.inner.kind {
+ Kind::Request => (),
+ _ => panic!("{:?}", err),
+ }
+ }
+
+ #[test]
+ fn from_unknown_io_error() {
+ let orig = io::Error::new(io::ErrorKind::Other, "orly");
+ let err = super::decode_io(orig);
+ match err.inner.kind {
+ Kind::Decode => (),
+ _ => panic!("{:?}", err),
+ }
+ }
+
+ #[test]
+ fn is_timeout() {
+ let err = super::request(super::TimedOut);
+ assert!(err.is_timeout());
+
+ let io = io::Error::new(io::ErrorKind::Other, err);
+ let nested = super::request(io);
+ assert!(nested.is_timeout());
+ }
+}
diff --git a/vendor/reqwest/src/into_url.rs b/vendor/reqwest/src/into_url.rs
new file mode 100644
index 000000000..d60f5c952
--- /dev/null
+++ b/vendor/reqwest/src/into_url.rs
@@ -0,0 +1,120 @@
+use url::Url;
+
+/// A trait to try to convert some type into a `Url`.
+///
+/// This trait is "sealed", such that only types within reqwest can
+/// implement it.
+pub trait IntoUrl: IntoUrlSealed {}
+
+impl IntoUrl for Url {}
+impl IntoUrl for String {}
+impl<'a> IntoUrl for &'a str {}
+impl<'a> IntoUrl for &'a String {}
+
+pub trait IntoUrlSealed {
+ // Besides parsing as a valid `Url`, the `Url` must be a valid
+ // `http::Uri`, in that it makes sense to use in a network request.
+ fn into_url(self) -> crate::Result<Url>;
+
+ fn as_str(&self) -> &str;
+}
+
+impl IntoUrlSealed for Url {
+ fn into_url(self) -> crate::Result<Url> {
+ // With blob url the `self.has_host()` check is always false, so we
+ // remove the `blob:` scheme and check again if the url is valid.
+ #[cfg(target_arch = "wasm32")]
+ if self.scheme() == "blob"
+ && self.path().starts_with("http") // Check if the path starts with http or https to avoid validating a `blob:blob:...` url.
+ && self.as_str()[5..].into_url().is_ok()
+ {
+ return Ok(self);
+ }
+
+ if self.has_host() {
+ Ok(self)
+ } else {
+ Err(crate::error::url_bad_scheme(self))
+ }
+ }
+
+ fn as_str(&self) -> &str {
+ self.as_ref()
+ }
+}
+
+impl<'a> IntoUrlSealed for &'a str {
+ fn into_url(self) -> crate::Result<Url> {
+ Url::parse(self).map_err(crate::error::builder)?.into_url()
+ }
+
+ fn as_str(&self) -> &str {
+ self
+ }
+}
+
+impl<'a> IntoUrlSealed for &'a String {
+ fn into_url(self) -> crate::Result<Url> {
+ (&**self).into_url()
+ }
+
+ fn as_str(&self) -> &str {
+ self.as_ref()
+ }
+}
+
+impl<'a> IntoUrlSealed for String {
+ fn into_url(self) -> crate::Result<Url> {
+ (&*self).into_url()
+ }
+
+ fn as_str(&self) -> &str {
+ self.as_ref()
+ }
+}
+
+if_hyper! {
+ pub(crate) fn expect_uri(url: &Url) -> http::Uri {
+ url.as_str()
+ .parse()
+ .expect("a parsed Url should always be a valid Uri")
+ }
+
+ pub(crate) fn try_uri(url: &Url) -> Option<http::Uri> {
+ url.as_str().parse().ok()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn into_url_file_scheme() {
+ let err = "file:///etc/hosts".into_url().unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "builder error for url (file:///etc/hosts): URL scheme is not allowed"
+ );
+ }
+
+ #[test]
+ fn into_url_blob_scheme() {
+ let err = "blob:https://example.com".into_url().unwrap_err();
+ assert_eq!(
+ err.to_string(),
+ "builder error for url (blob:https://example.com): URL scheme is not allowed"
+ );
+ }
+
+ if_wasm! {
+ use wasm_bindgen_test::*;
+
+ #[wasm_bindgen_test]
+ fn into_url_blob_scheme_wasm() {
+ let url = "blob:http://example.com".into_url().unwrap();
+
+ assert_eq!(url.as_str(), "blob:http://example.com");
+ }
+ }
+}
diff --git a/vendor/reqwest/src/lib.rs b/vendor/reqwest/src/lib.rs
new file mode 100644
index 000000000..92f845caf
--- /dev/null
+++ b/vendor/reqwest/src/lib.rs
@@ -0,0 +1,351 @@
+#![deny(missing_docs)]
+#![deny(missing_debug_implementations)]
+#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(test, deny(warnings))]
+#![doc(html_root_url = "https://docs.rs/reqwest/0.11.18")]
+
+//! # reqwest
+//!
+//! The `reqwest` crate provides a convenient, higher-level HTTP
+//! [`Client`][client].
+//!
+//! It handles many of the things that most people just expect an HTTP client
+//! to do for them.
+//!
+//! - Async and [blocking](blocking) Clients
+//! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart](multipart)
+//! - Customizable [redirect policy](#redirect-policies)
+//! - HTTP [Proxies](#proxies)
+//! - Uses system-native [TLS](#tls)
+//! - Cookies
+//!
+//! The [`reqwest::Client`][client] is asynchronous. For applications wishing
+//! to only make a few HTTP requests, the [`reqwest::blocking`](blocking) API
+//! may be more convenient.
+//!
+//! Additional learning resources include:
+//!
+//! - [The Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/web/clients.html)
+//! - [Reqwest Repository Examples](https://github.com/seanmonstar/reqwest/tree/master/examples)
+//!
+//! ## Making a GET request
+//!
+//! For a single request, you can use the [`get`][get] shortcut method.
+//!
+//! ```rust
+//! # async fn run() -> Result<(), reqwest::Error> {
+//! let body = reqwest::get("https://www.rust-lang.org")
+//! .await?
+//! .text()
+//! .await?;
+//!
+//! println!("body = {:?}", body);
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! **NOTE**: If you plan to perform multiple requests, it is best to create a
+//! [`Client`][client] and reuse it, taking advantage of keep-alive connection
+//! pooling.
+//!
+//! ## Making POST requests (or setting request bodies)
+//!
+//! There are several ways you can set the body of a request. The basic one is
+//! by using the `body()` method of a [`RequestBuilder`][builder]. This lets you set the
+//! exact raw bytes of what the body should be. It accepts various types,
+//! including `String` and `Vec<u8>`. If you wish to pass a custom
+//! type, you can use the `reqwest::Body` constructors.
+//!
+//! ```rust
+//! # use reqwest::Error;
+//! #
+//! # async fn run() -> Result<(), Error> {
+//! let client = reqwest::Client::new();
+//! let res = client.post("http://httpbin.org/post")
+//! .body("the exact body that is sent")
+//! .send()
+//! .await?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ### Forms
+//!
+//! It's very common to want to send form data in a request body. This can be
+//! done with any type that can be serialized into form data.
+//!
+//! This can be an array of tuples, or a `HashMap`, or a custom type that
+//! implements [`Serialize`][serde].
+//!
+//! ```rust
+//! # use reqwest::Error;
+//! #
+//! # async fn run() -> Result<(), Error> {
+//! // This will POST a body of `foo=bar&baz=quux`
+//! let params = [("foo", "bar"), ("baz", "quux")];
+//! let client = reqwest::Client::new();
+//! let res = client.post("http://httpbin.org/post")
+//! .form(&params)
+//! .send()
+//! .await?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ### JSON
+//!
+//! There is also a `json` method helper on the [`RequestBuilder`][builder] that works in
+//! a similar fashion the `form` method. It can take any value that can be
+//! serialized into JSON. The feature `json` is required.
+//!
+//! ```rust
+//! # use reqwest::Error;
+//! # use std::collections::HashMap;
+//! #
+//! # #[cfg(feature = "json")]
+//! # async fn run() -> Result<(), Error> {
+//! // This will POST a body of `{"lang":"rust","body":"json"}`
+//! let mut map = HashMap::new();
+//! map.insert("lang", "rust");
+//! map.insert("body", "json");
+//!
+//! let client = reqwest::Client::new();
+//! let res = client.post("http://httpbin.org/post")
+//! .json(&map)
+//! .send()
+//! .await?;
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! ## Redirect Policies
+//!
+//! By default, a `Client` will automatically handle HTTP redirects, having a
+//! maximum redirect chain of 10 hops. To customize this behavior, a
+//! [`redirect::Policy`][redirect] can be used with a `ClientBuilder`.
+//!
+//! ## Cookies
+//!
+//! The automatic storing and sending of session cookies can be enabled with
+//! the [`cookie_store`][ClientBuilder::cookie_store] method on `ClientBuilder`.
+//!
+//! ## Proxies
+//!
+//! **NOTE**: System proxies are enabled by default.
+//!
+//! System proxies look in environment variables to set HTTP or HTTPS proxies.
+//!
+//! `HTTP_PROXY` or `http_proxy` provide http proxies for http connections while
+//! `HTTPS_PROXY` or `https_proxy` provide HTTPS proxies for HTTPS connections.
+//!
+//! These can be overwritten by adding a [`Proxy`](Proxy) to `ClientBuilder`
+//! i.e. `let proxy = reqwest::Proxy::http("https://secure.example")?;`
+//! or disabled by calling `ClientBuilder::no_proxy()`.
+//!
+//! `socks` feature is required if you have configured socks proxy like this:
+//!
+//! ```bash
+//! export https_proxy=socks5://127.0.0.1:1086
+//! ```
+//!
+//! ## TLS
+//!
+//! By default, a `Client` will make use of system-native transport layer
+//! security to connect to HTTPS destinations. This means schannel on Windows,
+//! Security-Framework on macOS, and OpenSSL on Linux.
+//!
+//! - Additional X509 certificates can be configured on a `ClientBuilder` with the
+//! [`Certificate`](Certificate) type.
+//! - Client certificates can be added to a `ClientBuilder` with the
+//! [`Identity`][Identity] type.
+//! - Various parts of TLS can also be configured or even disabled on the
+//! `ClientBuilder`.
+//!
+//! ## Optional Features
+//!
+//! The following are a list of [Cargo features][cargo-features] that can be
+//! enabled or disabled:
+//!
+//! - **default-tls** *(enabled by default)*: Provides TLS support to connect
+//! over HTTPS.
+//! - **native-tls**: Enables TLS functionality provided by `native-tls`.
+//! - **native-tls-vendored**: Enables the `vendored` feature of `native-tls`.
+//! - **native-tls-alpn**: Enables the `alpn` feature of `native-tls`.
+//! - **rustls-tls**: Enables TLS functionality provided by `rustls`.
+//! Equivalent to `rustls-tls-webpki-roots`.
+//! - **rustls-tls-manual-roots**: Enables TLS functionality provided by `rustls`,
+//! without setting any root certificates. Roots have to be specified manually.
+//! - **rustls-tls-webpki-roots**: Enables TLS functionality provided by `rustls`,
+//! while using root certificates from the `webpki-roots` crate.
+//! - **rustls-tls-native-roots**: Enables TLS functionality provided by `rustls`,
+//! while using root certificates from the `rustls-native-certs` crate.
+//! - **blocking**: Provides the [blocking][] client API.
+//! - **cookies**: Provides cookie session support.
+//! - **gzip**: Provides response body gzip decompression.
+//! - **brotli**: Provides response body brotli decompression.
+//! - **deflate**: Provides response body deflate decompression.
+//! - **json**: Provides serialization and deserialization for JSON bodies.
+//! - **multipart**: Provides functionality for multipart forms.
+//! - **stream**: Adds support for `futures::Stream`.
+//! - **socks**: Provides SOCKS5 proxy support.
+//! - **trust-dns**: Enables a trust-dns async resolver instead of default
+//! threadpool using `getaddrinfo`.
+//!
+//! ## Unstable Features
+//!
+//! Some feature flags require additional opt-in by the application, by setting
+//! a `reqwest_unstable` flag.
+//!
+//! - **http3** *(unstable)*: Enables support for sending HTTP/3 requests.
+//!
+//! These features are unstable, and experimental. Details about them may be
+//! changed in patch releases.
+//!
+//! You can pass such a flag to the compiler via `.cargo/config`, or
+//! environment variables, such as:
+//!
+//! ```notrust
+//! RUSTFLAGS="--cfg reqwest_unstable" cargo build
+//! ```
+//!
+//! [hyper]: http://hyper.rs
+//! [blocking]: ./blocking/index.html
+//! [client]: ./struct.Client.html
+//! [response]: ./struct.Response.html
+//! [get]: ./fn.get.html
+//! [builder]: ./struct.RequestBuilder.html
+//! [serde]: http://serde.rs
+//! [redirect]: crate::redirect
+//! [Proxy]: ./struct.Proxy.html
+//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
+
+#[cfg(all(feature = "http3", not(reqwest_unstable)))]
+compile_error!(
+ "\
+ The `http3` feature is unstable, and requires the \
+ `RUSTFLAGS='--cfg reqwest_unstable'` environment variable to be set.\
+"
+);
+
+macro_rules! if_wasm {
+ ($($item:item)*) => {$(
+ #[cfg(target_arch = "wasm32")]
+ $item
+ )*}
+}
+
+macro_rules! if_hyper {
+ ($($item:item)*) => {$(
+ #[cfg(not(target_arch = "wasm32"))]
+ $item
+ )*}
+}
+
+pub use http::header;
+pub use http::Method;
+pub use http::{StatusCode, Version};
+pub use url::Url;
+
+// universal mods
+#[macro_use]
+mod error;
+mod into_url;
+mod response;
+
+pub use self::error::{Error, Result};
+pub use self::into_url::IntoUrl;
+pub use self::response::ResponseBuilderExt;
+
+/// Shortcut method to quickly make a `GET` request.
+///
+/// See also the methods on the [`reqwest::Response`](./struct.Response.html)
+/// type.
+///
+/// **NOTE**: This function creates a new internal `Client` on each call,
+/// and so should not be used if making many requests. Create a
+/// [`Client`](./struct.Client.html) instead.
+///
+/// # Examples
+///
+/// ```rust
+/// # async fn run() -> Result<(), reqwest::Error> {
+/// let body = reqwest::get("https://www.rust-lang.org").await?
+/// .text().await?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// # Errors
+///
+/// This function fails if:
+///
+/// - native TLS backend cannot be initialized
+/// - supplied `Url` cannot be parsed
+/// - there was an error while sending request
+/// - redirect limit was exhausted
+pub async fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
+ Client::builder().build()?.get(url).send().await
+}
+
+fn _assert_impls() {
+ fn assert_send<T: Send>() {}
+ fn assert_sync<T: Sync>() {}
+ fn assert_clone<T: Clone>() {}
+
+ assert_send::<Client>();
+ assert_sync::<Client>();
+ assert_clone::<Client>();
+
+ assert_send::<Request>();
+ assert_send::<RequestBuilder>();
+
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ assert_send::<Response>();
+ }
+
+ assert_send::<Error>();
+ assert_sync::<Error>();
+}
+
+if_hyper! {
+ #[cfg(test)]
+ #[macro_use]
+ extern crate doc_comment;
+
+ #[cfg(test)]
+ doctest!("../README.md");
+
+ pub use self::async_impl::{
+ Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded,
+ };
+ pub use self::proxy::{Proxy,NoProxy};
+ #[cfg(feature = "__tls")]
+ // Re-exports, to be removed in a future release
+ pub use tls::{Certificate, Identity};
+ #[cfg(feature = "multipart")]
+ pub use self::async_impl::multipart;
+
+
+ mod async_impl;
+ #[cfg(feature = "blocking")]
+ pub mod blocking;
+ mod connect;
+ #[cfg(feature = "cookies")]
+ pub mod cookie;
+ pub mod dns;
+ mod proxy;
+ pub mod redirect;
+ #[cfg(feature = "__tls")]
+ pub mod tls;
+ mod util;
+}
+
+if_wasm! {
+ mod wasm;
+ mod util;
+
+ pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};
+ #[cfg(feature = "multipart")]
+ pub use self::wasm::multipart;
+}
diff --git a/vendor/reqwest/src/proxy.rs b/vendor/reqwest/src/proxy.rs
new file mode 100644
index 000000000..cfb4f047c
--- /dev/null
+++ b/vendor/reqwest/src/proxy.rs
@@ -0,0 +1,1857 @@
+use std::fmt;
+#[cfg(feature = "socks")]
+use std::net::SocketAddr;
+use std::sync::Arc;
+
+use crate::into_url::{IntoUrl, IntoUrlSealed};
+use crate::Url;
+use http::{header::HeaderValue, Uri};
+use ipnet::IpNet;
+use once_cell::sync::Lazy;
+use percent_encoding::percent_decode;
+use std::collections::HashMap;
+use std::env;
+use std::error::Error;
+use std::net::IpAddr;
+#[cfg(target_os = "windows")]
+use winreg::enums::HKEY_CURRENT_USER;
+#[cfg(target_os = "windows")]
+use winreg::RegKey;
+
+/// Configuration of a proxy that a `Client` should pass requests to.
+///
+/// A `Proxy` has a couple pieces to it:
+///
+/// - a URL of how to talk to the proxy
+/// - rules on what `Client` requests should be directed to the proxy
+///
+/// For instance, let's look at `Proxy::http`:
+///
+/// ```rust
+/// # fn run() -> Result<(), Box<std::error::Error>> {
+/// let proxy = reqwest::Proxy::http("https://secure.example")?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// This proxy will intercept all HTTP requests, and make use of the proxy
+/// at `https://secure.example`. A request to `http://hyper.rs` will talk
+/// to your proxy. A request to `https://hyper.rs` will not.
+///
+/// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
+/// check each `Proxy` in the order it was added. This could mean that a
+/// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
+/// would prevent a `Proxy` later in the list from ever working, so take care.
+///
+/// By enabling the `"socks"` feature it is possible to use a socks proxy:
+/// ```rust
+/// # fn run() -> Result<(), Box<std::error::Error>> {
+/// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Clone)]
+pub struct Proxy {
+ intercept: Intercept,
+ no_proxy: Option<NoProxy>,
+}
+
+/// Represents a possible matching entry for an IP address
+#[derive(Clone, Debug)]
+enum Ip {
+ Address(IpAddr),
+ Network(IpNet),
+}
+
+/// A wrapper around a list of IP cidr blocks or addresses with a [IpMatcher::contains] method for
+/// checking if an IP address is contained within the matcher
+#[derive(Clone, Debug, Default)]
+struct IpMatcher(Vec<Ip>);
+
+/// A wrapper around a list of domains with a [DomainMatcher::contains] method for checking if a
+/// domain is contained within the matcher
+#[derive(Clone, Debug, Default)]
+struct DomainMatcher(Vec<String>);
+
+/// A configuration for filtering out requests that shouldn't be proxied
+#[derive(Clone, Debug, Default)]
+pub struct NoProxy {
+ ips: IpMatcher,
+ domains: DomainMatcher,
+}
+
+/// A particular scheme used for proxying requests.
+///
+/// For example, HTTP vs SOCKS5
+#[derive(Clone)]
+pub enum ProxyScheme {
+ Http {
+ auth: Option<HeaderValue>,
+ host: http::uri::Authority,
+ },
+ Https {
+ auth: Option<HeaderValue>,
+ host: http::uri::Authority,
+ },
+ #[cfg(feature = "socks")]
+ Socks5 {
+ addr: SocketAddr,
+ auth: Option<(String, String)>,
+ remote_dns: bool,
+ },
+}
+
+impl ProxyScheme {
+ fn maybe_http_auth(&self) -> Option<&HeaderValue> {
+ match self {
+ ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
+ #[cfg(feature = "socks")]
+ _ => None,
+ }
+ }
+}
+
+/// Trait used for converting into a proxy scheme. This trait supports
+/// parsing from a URL-like type, whilst also supporting proxy schemes
+/// built directly using the factory methods.
+pub trait IntoProxyScheme {
+ fn into_proxy_scheme(self) -> crate::Result<ProxyScheme>;
+}
+
+impl<S: IntoUrl> IntoProxyScheme for S {
+ fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
+ // validate the URL
+ let url = match self.as_str().into_url() {
+ Ok(ok) => ok,
+ Err(e) => {
+ let mut presumed_to_have_scheme = true;
+ let mut source = e.source();
+ while let Some(err) = source {
+ if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
+ match parse_error {
+ url::ParseError::RelativeUrlWithoutBase => {
+ presumed_to_have_scheme = false;
+ break;
+ }
+ _ => {}
+ }
+ } else if let Some(_) = err.downcast_ref::<crate::error::BadScheme>() {
+ presumed_to_have_scheme = false;
+ break;
+ }
+ source = err.source();
+ }
+ if !presumed_to_have_scheme {
+ // the issue could have been caused by a missing scheme, so we try adding http://
+ let try_this = format!("http://{}", self.as_str());
+ try_this.into_url().map_err(|_| {
+ // return the original error
+ crate::error::builder(e)
+ })?
+ } else {
+ return Err(crate::error::builder(e));
+ }
+ }
+ };
+ ProxyScheme::parse(url)
+ }
+}
+
+// These bounds are accidentally leaked by the blanket impl of IntoProxyScheme
+// for all types that implement IntoUrl. So, this function exists to detect
+// if we were to break those bounds for a user.
+fn _implied_bounds() {
+ fn prox<T: IntoProxyScheme>(_t: T) {}
+
+ fn url<T: IntoUrl>(t: T) {
+ prox(t);
+ }
+}
+
+impl IntoProxyScheme for ProxyScheme {
+ fn into_proxy_scheme(self) -> crate::Result<ProxyScheme> {
+ Ok(self)
+ }
+}
+
+impl Proxy {
+ /// Proxy all HTTP traffic to the passed URL.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::Client::builder()
+ /// .proxy(reqwest::Proxy::http("https://my.prox")?)
+ /// .build()?;
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn http<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
+ Ok(Proxy::new(Intercept::Http(
+ proxy_scheme.into_proxy_scheme()?,
+ )))
+ }
+
+ /// Proxy all HTTPS traffic to the passed URL.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::Client::builder()
+ /// .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
+ /// .build()?;
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn https<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
+ Ok(Proxy::new(Intercept::Https(
+ proxy_scheme.into_proxy_scheme()?,
+ )))
+ }
+
+ /// Proxy **all** traffic to the passed URL.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let client = reqwest::Client::builder()
+ /// .proxy(reqwest::Proxy::all("http://pro.xy")?)
+ /// .build()?;
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn all<U: IntoProxyScheme>(proxy_scheme: U) -> crate::Result<Proxy> {
+ Ok(Proxy::new(Intercept::All(
+ proxy_scheme.into_proxy_scheme()?,
+ )))
+ }
+
+ /// Provide a custom function to determine what traffic to proxy to where.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let target = reqwest::Url::parse("https://my.prox")?;
+ /// let client = reqwest::Client::builder()
+ /// .proxy(reqwest::Proxy::custom(move |url| {
+ /// if url.host_str() == Some("hyper.rs") {
+ /// Some(target.clone())
+ /// } else {
+ /// None
+ /// }
+ /// }))
+ /// .build()?;
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn custom<F, U: IntoProxyScheme>(fun: F) -> Proxy
+ where
+ F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
+ {
+ Proxy::new(Intercept::Custom(Custom {
+ auth: None,
+ func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)),
+ }))
+ }
+
+ pub(crate) fn system() -> Proxy {
+ let mut proxy = if cfg!(feature = "__internal_proxy_sys_no_cache") {
+ Proxy::new(Intercept::System(Arc::new(get_sys_proxies(
+ get_from_registry(),
+ ))))
+ } else {
+ Proxy::new(Intercept::System(SYS_PROXIES.clone()))
+ };
+ proxy.no_proxy = NoProxy::from_env();
+ proxy
+ }
+
+ fn new(intercept: Intercept) -> Proxy {
+ Proxy {
+ intercept,
+ no_proxy: None,
+ }
+ }
+
+ /// Set the `Proxy-Authorization` header using Basic auth.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
+ /// .basic_auth("Aladdin", "open sesame");
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
+ self.intercept.set_basic_auth(username, password);
+ self
+ }
+
+ /// Adds a `No Proxy` exclusion list to this Proxy
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate reqwest;
+ /// # fn run() -> Result<(), Box<std::error::Error>> {
+ /// let proxy = reqwest::Proxy::https("http://localhost:1234")?
+ /// .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld"));
+ /// # Ok(())
+ /// # }
+ /// # fn main() {}
+ /// ```
+ pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
+ self.no_proxy = no_proxy;
+ self
+ }
+
+ pub(crate) fn maybe_has_http_auth(&self) -> bool {
+ match &self.intercept {
+ Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
+ // Custom *may* match 'http', so assume so.
+ Intercept::Custom(_) => true,
+ Intercept::System(system) => system
+ .get("http")
+ .and_then(|s| s.maybe_http_auth())
+ .is_some(),
+ _ => false,
+ }
+ }
+
+ pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
+ match &self.intercept {
+ Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
+ Intercept::System(system) => system
+ .get("http")
+ .and_then(|s| s.maybe_http_auth().cloned()),
+ Intercept::Custom(custom) => {
+ custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
+ }
+ _ => None,
+ }
+ }
+
+ pub(crate) fn intercept<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
+ let in_no_proxy = self
+ .no_proxy
+ .as_ref()
+ .map_or(false, |np| np.contains(uri.host()));
+ match self.intercept {
+ Intercept::All(ref u) => {
+ if !in_no_proxy {
+ Some(u.clone())
+ } else {
+ None
+ }
+ }
+ Intercept::Http(ref u) => {
+ if !in_no_proxy && uri.scheme() == "http" {
+ Some(u.clone())
+ } else {
+ None
+ }
+ }
+ Intercept::Https(ref u) => {
+ if !in_no_proxy && uri.scheme() == "https" {
+ Some(u.clone())
+ } else {
+ None
+ }
+ }
+ Intercept::System(ref map) => {
+ if in_no_proxy {
+ None
+ } else {
+ map.get(uri.scheme()).cloned()
+ }
+ }
+ Intercept::Custom(ref custom) => {
+ if !in_no_proxy {
+ custom.call(uri)
+ } else {
+ None
+ }
+ }
+ }
+ }
+
+ pub(crate) fn is_match<D: Dst>(&self, uri: &D) -> bool {
+ match self.intercept {
+ Intercept::All(_) => true,
+ Intercept::Http(_) => uri.scheme() == "http",
+ Intercept::Https(_) => uri.scheme() == "https",
+ Intercept::System(ref map) => map.contains_key(uri.scheme()),
+ Intercept::Custom(ref custom) => custom.call(uri).is_some(),
+ }
+ }
+}
+
+impl fmt::Debug for Proxy {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("Proxy")
+ .field(&self.intercept)
+ .field(&self.no_proxy)
+ .finish()
+ }
+}
+
+impl NoProxy {
+ /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set)
+ /// see [self::NoProxy::from_string()] for the string format
+ pub fn from_env() -> Option<NoProxy> {
+ let raw = env::var("NO_PROXY")
+ .or_else(|_| env::var("no_proxy"))
+ .unwrap_or_default();
+
+ Self::from_string(&raw)
+ }
+
+ /// Returns a new no-proxy configuration based on a no_proxy string (or `None` if no variables
+ /// are set)
+ /// The rules are as follows:
+ /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
+ /// * If neither environment variable is set, `None` is returned
+ /// * Entries are expected to be comma-separated (whitespace between entries is ignored)
+ /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
+ /// for example "`192.168.1.0/24`").
+ /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
+ /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
+ /// and `.google.com` are equivalent) and would match both that domain AND all subdomains.
+ ///
+ /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all of the following would match
+ /// (and therefore would bypass the proxy):
+ /// * `http://google.com/`
+ /// * `http://www.google.com/`
+ /// * `http://192.168.1.42/`
+ ///
+ /// The URL `http://notgoogle.com/` would not match.
+ pub fn from_string(no_proxy_list: &str) -> Option<Self> {
+ if no_proxy_list.is_empty() {
+ return None;
+ }
+ let mut ips = Vec::new();
+ let mut domains = Vec::new();
+ let parts = no_proxy_list.split(',').map(str::trim);
+ for part in parts {
+ match part.parse::<IpNet>() {
+ // If we can parse an IP net or address, then use it, otherwise, assume it is a domain
+ Ok(ip) => ips.push(Ip::Network(ip)),
+ Err(_) => match part.parse::<IpAddr>() {
+ Ok(addr) => ips.push(Ip::Address(addr)),
+ Err(_) => domains.push(part.to_owned()),
+ },
+ }
+ }
+ Some(NoProxy {
+ ips: IpMatcher(ips),
+ domains: DomainMatcher(domains),
+ })
+ }
+
+ fn contains(&self, host: &str) -> bool {
+ // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off
+ // the end in order to parse correctly
+ let host = if host.starts_with('[') {
+ let x: &[_] = &['[', ']'];
+ host.trim_matches(x)
+ } else {
+ host
+ };
+ match host.parse::<IpAddr>() {
+ // If we can parse an IP addr, then use it, otherwise, assume it is a domain
+ Ok(ip) => self.ips.contains(ip),
+ Err(_) => self.domains.contains(host),
+ }
+ }
+}
+
+impl IpMatcher {
+ fn contains(&self, addr: IpAddr) -> bool {
+ for ip in self.0.iter() {
+ match ip {
+ Ip::Address(address) => {
+ if &addr == address {
+ return true;
+ }
+ }
+ Ip::Network(net) => {
+ if net.contains(&addr) {
+ return true;
+ }
+ }
+ }
+ }
+ false
+ }
+}
+
+impl DomainMatcher {
+ // The following links may be useful to understand the origin of these rules:
+ // * https://curl.se/libcurl/c/CURLOPT_NOPROXY.html
+ // * https://github.com/curl/curl/issues/1208
+ fn contains(&self, domain: &str) -> bool {
+ let domain_len = domain.len();
+ for d in self.0.iter() {
+ if d == domain || d.strip_prefix('.') == Some(domain) {
+ return true;
+ } else if domain.ends_with(d) {
+ if d.starts_with('.') {
+ // If the first character of d is a dot, that means the first character of domain
+ // must also be a dot, so we are looking at a subdomain of d and that matches
+ return true;
+ } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') {
+ // Given that d is a prefix of domain, if the prior character in domain is a dot
+ // then that means we must be matching a subdomain of d, and that matches
+ return true;
+ }
+ } else if d == "*" {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+impl ProxyScheme {
+ // To start conservative, keep builders private for now.
+
+ /// Proxy traffic via the specified URL over HTTP
+ fn http(host: &str) -> crate::Result<Self> {
+ Ok(ProxyScheme::Http {
+ auth: None,
+ host: host.parse().map_err(crate::error::builder)?,
+ })
+ }
+
+ /// Proxy traffic via the specified URL over HTTPS
+ fn https(host: &str) -> crate::Result<Self> {
+ Ok(ProxyScheme::Https {
+ auth: None,
+ host: host.parse().map_err(crate::error::builder)?,
+ })
+ }
+
+ /// Proxy traffic via the specified socket address over SOCKS5
+ ///
+ /// # Note
+ ///
+ /// Current SOCKS5 support is provided via blocking IO.
+ #[cfg(feature = "socks")]
+ fn socks5(addr: SocketAddr) -> crate::Result<Self> {
+ Ok(ProxyScheme::Socks5 {
+ addr,
+ auth: None,
+ remote_dns: false,
+ })
+ }
+
+ /// Proxy traffic via the specified socket address over SOCKS5H
+ ///
+ /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy.
+ ///
+ /// # Note
+ ///
+ /// Current SOCKS5 support is provided via blocking IO.
+ #[cfg(feature = "socks")]
+ fn socks5h(addr: SocketAddr) -> crate::Result<Self> {
+ Ok(ProxyScheme::Socks5 {
+ addr,
+ auth: None,
+ remote_dns: true,
+ })
+ }
+
+ /// Use a username and password when connecting to the proxy server
+ fn with_basic_auth<T: Into<String>, U: Into<String>>(
+ mut self,
+ username: T,
+ password: U,
+ ) -> Self {
+ self.set_basic_auth(username, password);
+ self
+ }
+
+ fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
+ match *self {
+ ProxyScheme::Http { ref mut auth, .. } => {
+ let header = encode_basic_auth(&username.into(), &password.into());
+ *auth = Some(header);
+ }
+ ProxyScheme::Https { ref mut auth, .. } => {
+ let header = encode_basic_auth(&username.into(), &password.into());
+ *auth = Some(header);
+ }
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 { ref mut auth, .. } => {
+ *auth = Some((username.into(), password.into()));
+ }
+ }
+ }
+
+ fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
+ match self {
+ ProxyScheme::Http { ref mut auth, .. } => {
+ if auth.is_none() {
+ *auth = update.clone();
+ }
+ }
+ ProxyScheme::Https { ref mut auth, .. } => {
+ if auth.is_none() {
+ *auth = update.clone();
+ }
+ }
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 { .. } => {}
+ }
+
+ self
+ }
+
+ /// Convert a URL into a proxy scheme
+ ///
+ /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled).
+ // Private for now...
+ fn parse(url: Url) -> crate::Result<Self> {
+ use url::Position;
+
+ // Resolve URL to a host and port
+ #[cfg(feature = "socks")]
+ let to_addr = || {
+ let addrs = url
+ .socket_addrs(|| match url.scheme() {
+ "socks5" | "socks5h" => Some(1080),
+ _ => None,
+ })
+ .map_err(crate::error::builder)?;
+ addrs
+ .into_iter()
+ .next()
+ .ok_or_else(|| crate::error::builder("unknown proxy scheme"))
+ };
+
+ let mut scheme = match url.scheme() {
+ "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
+ "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
+ #[cfg(feature = "socks")]
+ "socks5" => Self::socks5(to_addr()?)?,
+ #[cfg(feature = "socks")]
+ "socks5h" => Self::socks5h(to_addr()?)?,
+ _ => return Err(crate::error::builder("unknown proxy scheme")),
+ };
+
+ if let Some(pwd) = url.password() {
+ let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
+ let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
+ scheme = scheme.with_basic_auth(decoded_username, decoded_password);
+ }
+
+ Ok(scheme)
+ }
+
+ #[cfg(test)]
+ fn scheme(&self) -> &str {
+ match self {
+ ProxyScheme::Http { .. } => "http",
+ ProxyScheme::Https { .. } => "https",
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 { .. } => "socks5",
+ }
+ }
+
+ #[cfg(test)]
+ fn host(&self) -> &str {
+ match self {
+ ProxyScheme::Http { host, .. } => host.as_str(),
+ ProxyScheme::Https { host, .. } => host.as_str(),
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 { .. } => panic!("socks5"),
+ }
+ }
+}
+
+impl fmt::Debug for ProxyScheme {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{}", host),
+ ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{}", host),
+ #[cfg(feature = "socks")]
+ ProxyScheme::Socks5 {
+ addr,
+ auth: _auth,
+ remote_dns,
+ } => {
+ let h = if *remote_dns { "h" } else { "" };
+ write!(f, "socks5{}://{}", h, addr)
+ }
+ }
+ }
+}
+
+type SystemProxyMap = HashMap<String, ProxyScheme>;
+type RegistryProxyValues = (u32, String);
+
+#[derive(Clone, Debug)]
+enum Intercept {
+ All(ProxyScheme),
+ Http(ProxyScheme),
+ Https(ProxyScheme),
+ System(Arc<SystemProxyMap>),
+ Custom(Custom),
+}
+
+impl Intercept {
+ fn set_basic_auth(&mut self, username: &str, password: &str) {
+ match self {
+ Intercept::All(ref mut s)
+ | Intercept::Http(ref mut s)
+ | Intercept::Https(ref mut s) => s.set_basic_auth(username, password),
+ Intercept::System(_) => unimplemented!(),
+ Intercept::Custom(ref mut custom) => {
+ let header = encode_basic_auth(username, password);
+ custom.auth = Some(header);
+ }
+ }
+ }
+}
+
+#[derive(Clone)]
+struct Custom {
+ // This auth only applies if the returned ProxyScheme doesn't have an auth...
+ auth: Option<HeaderValue>,
+ func: Arc<dyn Fn(&Url) -> Option<crate::Result<ProxyScheme>> + Send + Sync + 'static>,
+}
+
+impl Custom {
+ fn call<D: Dst>(&self, uri: &D) -> Option<ProxyScheme> {
+ let url = format!(
+ "{}://{}{}{}",
+ uri.scheme(),
+ uri.host(),
+ uri.port().map(|_| ":").unwrap_or(""),
+ uri.port().map(|p| p.to_string()).unwrap_or_default()
+ )
+ .parse()
+ .expect("should be valid Url");
+
+ (self.func)(&url)
+ .and_then(|result| result.ok())
+ .map(|scheme| scheme.if_no_auth(&self.auth))
+ }
+}
+
+impl fmt::Debug for Custom {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("_")
+ }
+}
+
+pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
+ crate::util::basic_auth(username, Some(password))
+}
+
+/// A helper trait to allow testing `Proxy::intercept` without having to
+/// construct `hyper::client::connect::Destination`s.
+pub(crate) trait Dst {
+ fn scheme(&self) -> &str;
+ fn host(&self) -> &str;
+ fn port(&self) -> Option<u16>;
+}
+
+#[doc(hidden)]
+impl Dst for Uri {
+ fn scheme(&self) -> &str {
+ self.scheme().expect("Uri should have a scheme").as_str()
+ }
+
+ fn host(&self) -> &str {
+ Uri::host(self).expect("<Uri as Dst>::host should have a str")
+ }
+
+ fn port(&self) -> Option<u16> {
+ self.port().map(|p| p.as_u16())
+ }
+}
+
+static SYS_PROXIES: Lazy<Arc<SystemProxyMap>> =
+ Lazy::new(|| Arc::new(get_sys_proxies(get_from_registry())));
+
+/// Get system proxies information.
+///
+/// It can only support Linux, Unix like, and windows system. Note that it will always
+/// return a HashMap, even if something runs into error when find registry information in
+/// Windows system. Note that invalid proxy url in the system setting will be ignored.
+///
+/// Returns:
+/// System proxies information as a hashmap like
+/// {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")}
+fn get_sys_proxies(
+ #[cfg_attr(not(target_os = "windows"), allow(unused_variables))] registry_values: Option<
+ RegistryProxyValues,
+ >,
+) -> SystemProxyMap {
+ let proxies = get_from_environment();
+
+ // TODO: move the following #[cfg] to `if expression` when attributes on `if` expressions allowed
+ #[cfg(target_os = "windows")]
+ {
+ if proxies.is_empty() {
+ // don't care errors if can't get proxies from registry, just return an empty HashMap.
+ if let Some(registry_values) = registry_values {
+ return parse_registry_values(registry_values);
+ }
+ }
+ }
+ proxies
+}
+
+fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into<String>, addr: String) -> bool {
+ if addr.trim().is_empty() {
+ // do not accept empty or whitespace proxy address
+ false
+ } else if let Ok(valid_addr) = addr.into_proxy_scheme() {
+ proxies.insert(scheme.into(), valid_addr);
+ true
+ } else {
+ false
+ }
+}
+
+fn get_from_environment() -> SystemProxyMap {
+ let mut proxies = HashMap::new();
+
+ if is_cgi() {
+ if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() {
+ log::warn!("HTTP_PROXY environment variable ignored in CGI");
+ }
+ } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") {
+ insert_from_env(&mut proxies, "http", "http_proxy");
+ }
+
+ if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") {
+ insert_from_env(&mut proxies, "https", "https_proxy");
+ }
+
+ proxies
+}
+
+fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool {
+ if let Ok(val) = env::var(var) {
+ insert_proxy(proxies, scheme, val)
+ } else {
+ false
+ }
+}
+
+/// Check if we are being executed in a CGI context.
+///
+/// If so, a malicious client can send the `Proxy:` header, and it will
+/// be in the `HTTP_PROXY` env var. So we don't use it :)
+fn is_cgi() -> bool {
+ env::var_os("REQUEST_METHOD").is_some()
+}
+
+#[cfg(target_os = "windows")]
+fn get_from_registry_impl() -> Result<RegistryProxyValues, Box<dyn Error>> {
+ let hkcu = RegKey::predef(HKEY_CURRENT_USER);
+ let internet_setting: RegKey =
+ hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
+ // ensure the proxy is enable, if the value doesn't exist, an error will returned.
+ let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?;
+ let proxy_server: String = internet_setting.get_value("ProxyServer")?;
+
+ Ok((proxy_enable, proxy_server))
+}
+
+#[cfg(target_os = "windows")]
+fn get_from_registry() -> Option<RegistryProxyValues> {
+ get_from_registry_impl().ok()
+}
+
+#[cfg(not(target_os = "windows"))]
+fn get_from_registry() -> Option<RegistryProxyValues> {
+ None
+}
+
+#[cfg(target_os = "windows")]
+fn parse_registry_values_impl(
+ registry_values: RegistryProxyValues,
+) -> Result<SystemProxyMap, Box<dyn Error>> {
+ let (proxy_enable, proxy_server) = registry_values;
+
+ if proxy_enable == 0 {
+ return Ok(HashMap::new());
+ }
+
+ let mut proxies = HashMap::new();
+ if proxy_server.contains("=") {
+ // per-protocol settings.
+ for p in proxy_server.split(";") {
+ let protocol_parts: Vec<&str> = p.split("=").collect();
+ match protocol_parts.as_slice() {
+ [protocol, address] => {
+ // If address doesn't specify an explicit protocol as protocol://address
+ // then default to HTTP
+ let address = if extract_type_prefix(*address).is_some() {
+ String::from(*address)
+ } else {
+ format!("http://{}", address)
+ };
+
+ insert_proxy(&mut proxies, *protocol, address);
+ }
+ _ => {
+ // Contains invalid protocol setting, just break the loop
+ // And make proxies to be empty.
+ proxies.clear();
+ break;
+ }
+ }
+ }
+ } else {
+ if let Some(scheme) = extract_type_prefix(&proxy_server) {
+ // Explicit protocol has been specified
+ insert_proxy(&mut proxies, scheme, proxy_server.to_owned());
+ } else {
+ // No explicit protocol has been specified, default to HTTP
+ insert_proxy(&mut proxies, "http", format!("http://{}", proxy_server));
+ insert_proxy(&mut proxies, "https", format!("http://{}", proxy_server));
+ }
+ }
+ Ok(proxies)
+}
+
+/// Extract the protocol from the given address, if present
+/// For example, "https://example.com" will return Some("https")
+#[cfg(target_os = "windows")]
+fn extract_type_prefix(address: &str) -> Option<&str> {
+ if let Some(indice) = address.find("://") {
+ if indice == 0 {
+ None
+ } else {
+ let prefix = &address[..indice];
+ let contains_banned = prefix.contains(|c| c == ':' || c == '/');
+
+ if !contains_banned {
+ Some(prefix)
+ } else {
+ None
+ }
+ }
+ } else {
+ None
+ }
+}
+
+#[cfg(target_os = "windows")]
+fn parse_registry_values(registry_values: RegistryProxyValues) -> SystemProxyMap {
+ parse_registry_values_impl(registry_values).unwrap_or(HashMap::new())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use once_cell::sync::Lazy;
+ use std::sync::Mutex;
+
+ impl Dst for Url {
+ fn scheme(&self) -> &str {
+ Url::scheme(self)
+ }
+
+ fn host(&self) -> &str {
+ Url::host_str(self).expect("<Url as Dst>::host should have a str")
+ }
+
+ fn port(&self) -> Option<u16> {
+ Url::port(self)
+ }
+ }
+
+ fn url(s: &str) -> Url {
+ s.parse().unwrap()
+ }
+
+ fn intercepted_uri(p: &Proxy, s: &str) -> Uri {
+ let (scheme, host) = match p.intercept(&url(s)).unwrap() {
+ ProxyScheme::Http { host, .. } => ("http", host),
+ ProxyScheme::Https { host, .. } => ("https", host),
+ #[cfg(feature = "socks")]
+ _ => panic!("intercepted as socks"),
+ };
+ http::Uri::builder()
+ .scheme(scheme)
+ .authority(host)
+ .path_and_query("/")
+ .build()
+ .expect("intercepted_uri")
+ }
+
+ #[test]
+ fn test_http() {
+ let target = "http://example.domain/";
+ let p = Proxy::http(target).unwrap();
+
+ let http = "http://hyper.rs";
+ let other = "https://hyper.rs";
+
+ assert_eq!(intercepted_uri(&p, http), target);
+ assert!(p.intercept(&url(other)).is_none());
+ }
+
+ #[test]
+ fn test_https() {
+ let target = "http://example.domain/";
+ let p = Proxy::https(target).unwrap();
+
+ let http = "http://hyper.rs";
+ let other = "https://hyper.rs";
+
+ assert!(p.intercept(&url(http)).is_none());
+ assert_eq!(intercepted_uri(&p, other), target);
+ }
+
+ #[test]
+ fn test_all() {
+ let target = "http://example.domain/";
+ let p = Proxy::all(target).unwrap();
+
+ let http = "http://hyper.rs";
+ let https = "https://hyper.rs";
+ let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
+
+ assert_eq!(intercepted_uri(&p, http), target);
+ assert_eq!(intercepted_uri(&p, https), target);
+ assert_eq!(intercepted_uri(&p, other), target);
+ }
+
+ #[test]
+ fn test_custom() {
+ let target1 = "http://example.domain/";
+ let target2 = "https://example.domain/";
+ let p = Proxy::custom(move |url| {
+ if url.host_str() == Some("hyper.rs") {
+ target1.parse().ok()
+ } else if url.scheme() == "http" {
+ target2.parse().ok()
+ } else {
+ None::<Url>
+ }
+ });
+
+ let http = "http://seanmonstar.com";
+ let https = "https://hyper.rs";
+ let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
+
+ assert_eq!(intercepted_uri(&p, http), target2);
+ assert_eq!(intercepted_uri(&p, https), target1);
+ assert!(p.intercept(&url(other)).is_none());
+ }
+
+ #[test]
+ fn test_proxy_scheme_parse() {
+ let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap();
+
+ match ps {
+ ProxyScheme::Http { auth, host } => {
+ assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
+ assert_eq!(host, "localhost:1239");
+ }
+ other => panic!("unexpected: {:?}", other),
+ }
+ }
+
+ #[test]
+ fn test_proxy_scheme_ip_address_default_http() {
+ let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap();
+
+ match ps {
+ ProxyScheme::Http { auth, host } => {
+ assert!(auth.is_none());
+ assert_eq!(host, "192.168.1.1:8888");
+ }
+ other => panic!("unexpected: {:?}", other),
+ }
+ }
+
+ #[test]
+ fn test_proxy_scheme_parse_default_http_with_auth() {
+ // this should fail because `foo` is interpreted as the scheme and no host can be found
+ let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap();
+
+ match ps {
+ ProxyScheme::Http { auth, host } => {
+ assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar"));
+ assert_eq!(host, "localhost:1239");
+ }
+ other => panic!("unexpected: {:?}", other),
+ }
+ }
+
+ #[test]
+ fn test_domain_matcher() {
+ let domains = vec![".foo.bar".into(), "bar.foo".into()];
+ let matcher = DomainMatcher(domains);
+
+ // domains match with leading `.`
+ assert!(matcher.contains("foo.bar"));
+ // subdomains match with leading `.`
+ assert!(matcher.contains("www.foo.bar"));
+
+ // domains match with no leading `.`
+ assert!(matcher.contains("bar.foo"));
+ // subdomains match with no leading `.`
+ assert!(matcher.contains("www.bar.foo"));
+
+ // non-subdomain string prefixes don't match
+ assert!(!matcher.contains("notfoo.bar"));
+ assert!(!matcher.contains("notbar.foo"));
+ }
+
+ // Smallest possible content for a mutex
+ struct MutexInner;
+
+ static ENVLOCK: Lazy<Mutex<MutexInner>> = Lazy::new(|| Mutex::new(MutexInner));
+
+ #[test]
+ fn test_get_sys_proxies_parsing() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("HTTP_PROXY");
+ let _g2 = env_guard("http_proxy");
+
+ // Mock ENV, get the results, before doing assertions
+ // to avoid assert! -> panic! -> Mutex Poisoned.
+ let baseline_proxies = get_sys_proxies(None);
+ // the system proxy setting url is invalid.
+ env::set_var("http_proxy", "file://123465");
+ let invalid_proxies = get_sys_proxies(None);
+ // set valid proxy
+ env::set_var("http_proxy", "127.0.0.1/");
+ let valid_proxies = get_sys_proxies(None);
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+
+ assert!(!baseline_proxies.contains_key("http"));
+ assert!(!invalid_proxies.contains_key("http"));
+
+ let p = &valid_proxies["http"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1");
+ }
+
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_get_sys_proxies_registry_parsing() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("HTTP_PROXY");
+ let _g2 = env_guard("http_proxy");
+
+ // Mock ENV, get the results, before doing assertions
+ // to avoid assert! -> panic! -> Mutex Poisoned.
+ let baseline_proxies = get_sys_proxies(None);
+ // the system proxy in the registry has been disabled
+ let disabled_proxies = get_sys_proxies(Some((0, String::from("http://127.0.0.1/"))));
+ // set valid proxy
+ let valid_proxies = get_sys_proxies(Some((1, String::from("http://127.0.0.1/"))));
+ let valid_proxies_no_scheme = get_sys_proxies(Some((1, String::from("127.0.0.1"))));
+ let valid_proxies_explicit_https =
+ get_sys_proxies(Some((1, String::from("https://127.0.0.1/"))));
+ let multiple_proxies = get_sys_proxies(Some((
+ 1,
+ String::from("http=127.0.0.1:8888;https=127.0.0.2:8888"),
+ )));
+ let multiple_proxies_explicit_scheme = get_sys_proxies(Some((
+ 1,
+ String::from("http=http://127.0.0.1:8888;https=https://127.0.0.2:8888"),
+ )));
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+
+ assert_eq!(baseline_proxies.contains_key("http"), false);
+ assert_eq!(disabled_proxies.contains_key("http"), false);
+
+ let p = &valid_proxies["http"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1");
+
+ let p = &valid_proxies_no_scheme["http"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1");
+
+ let p = &valid_proxies_no_scheme["https"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1");
+
+ let p = &valid_proxies_explicit_https["https"];
+ assert_eq!(p.scheme(), "https");
+ assert_eq!(p.host(), "127.0.0.1");
+
+ let p = &multiple_proxies["http"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1:8888");
+
+ let p = &multiple_proxies["https"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.2:8888");
+
+ let p = &multiple_proxies_explicit_scheme["http"];
+ assert_eq!(p.scheme(), "http");
+ assert_eq!(p.host(), "127.0.0.1:8888");
+
+ let p = &multiple_proxies_explicit_scheme["https"];
+ assert_eq!(p.scheme(), "https");
+ assert_eq!(p.host(), "127.0.0.2:8888");
+ }
+
+ #[test]
+ fn test_get_sys_proxies_in_cgi() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("REQUEST_METHOD");
+ let _g2 = env_guard("HTTP_PROXY");
+
+ // Mock ENV, get the results, before doing assertions
+ // to avoid assert! -> panic! -> Mutex Poisoned.
+ env::set_var("HTTP_PROXY", "http://evil/");
+
+ let baseline_proxies = get_sys_proxies(None);
+ // set like we're in CGI
+ env::set_var("REQUEST_METHOD", "GET");
+
+ let cgi_proxies = get_sys_proxies(None);
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+
+ // not in CGI yet
+ assert_eq!(baseline_proxies["http"].host(), "evil");
+ // In CGI
+ assert!(!cgi_proxies.contains_key("http"));
+ }
+
+ #[test]
+ fn test_sys_no_proxy() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("HTTP_PROXY");
+ let _g2 = env_guard("NO_PROXY");
+
+ let target = "http://example.domain/";
+ env::set_var("HTTP_PROXY", target);
+
+ env::set_var(
+ "NO_PROXY",
+ ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17",
+ );
+
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+
+ // random url, not in no_proxy
+ assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
+ // make sure that random non-subdomain string prefixes don't match
+ assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target);
+ // make sure that random non-subdomain string prefixes don't match
+ assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target);
+ // ipv4 address out of range
+ assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target);
+ // ipv4 address out of range
+ assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target);
+ // ipv6 address out of range
+ assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target);
+ // ipv6 address out of range
+ assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target);
+
+ // make sure subdomains (with leading .) match
+ assert!(p.intercept(&url("http://hello.foo.bar")).is_none());
+ // make sure exact matches (without leading .) match (also makes sure spaces between entries work)
+ assert!(p.intercept(&url("http://bar.baz")).is_none());
+ // check case sensitivity
+ assert!(p.intercept(&url("http://BAR.baz")).is_none());
+ // make sure subdomains (without leading . in no_proxy) match
+ assert!(p.intercept(&url("http://foo.bar.baz")).is_none());
+ // make sure subdomains (without leading . in no_proxy) match - this differs from cURL
+ assert!(p.intercept(&url("http://foo.bar")).is_none());
+ // ipv4 address match within range
+ assert!(p.intercept(&url("http://10.42.1.100")).is_none());
+ // ipv6 address exact match
+ assert!(p.intercept(&url("http://[::1]")).is_none());
+ // ipv6 address match within range
+ assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none());
+ // ipv4 address exact match
+ assert!(p.intercept(&url("http://10.124.7.8")).is_none());
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+ }
+
+ #[test]
+ fn test_proxy_no_proxy_interception_for_proxy_types() {
+ let proxy_url = "http://example.domain/";
+ let no_proxy = ".no.proxy.tld";
+
+ // test all proxy interception
+ let p = Proxy::all(proxy_url)
+ .unwrap()
+ .no_proxy(NoProxy::from_string(no_proxy));
+
+ // random url, not in no_proxy
+ assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
+
+ // positive match for no proxy
+ assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
+
+ // test http proxy interception
+ let p = Proxy::http(proxy_url)
+ .unwrap()
+ .no_proxy(NoProxy::from_string(no_proxy));
+
+ // random url, not in no_proxy
+ assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url);
+
+ // positive match for no proxy
+ assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
+
+ // should not be intercepted due to scheme
+ assert!(p.intercept(&url("https://hyper.rs")).is_none());
+
+ // test https proxy interception
+ let p = Proxy::https(proxy_url)
+ .unwrap()
+ .no_proxy(NoProxy::from_string(no_proxy));
+
+ // random url, not in no_proxy
+ assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
+
+ // positive match for no proxy
+ assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
+
+ // should not be intercepted due to scheme
+ assert!(p.intercept(&url("http://hyper.rs")).is_none());
+
+ // test custom proxy interception
+ let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy));
+
+ // random url, not in no_proxy
+ assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url);
+
+ // positive match for no proxy
+ assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none());
+ assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none());
+ }
+
+ #[test]
+ fn test_wildcard_sys_no_proxy() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("HTTP_PROXY");
+ let _g2 = env_guard("NO_PROXY");
+
+ let target = "http://example.domain/";
+ env::set_var("HTTP_PROXY", target);
+
+ env::set_var("NO_PROXY", "*");
+
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+
+ assert!(p.intercept(&url("http://foo.bar")).is_none());
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+ }
+
+ #[test]
+ fn test_empty_sys_no_proxy() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+ // save system setting first.
+ let _g1 = env_guard("HTTP_PROXY");
+ let _g2 = env_guard("NO_PROXY");
+
+ let target = "http://example.domain/";
+ env::set_var("HTTP_PROXY", target);
+
+ env::set_var("NO_PROXY", ",");
+
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+
+ // everything should go through proxy, "effectively" nothing is in no_proxy
+ assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ // Let other threads run now
+ drop(_lock);
+ }
+
+ #[test]
+ fn test_no_proxy_load() {
+ // Stop other threads from modifying process-global ENV while we are.
+ let _lock = ENVLOCK.lock();
+
+ let _g1 = env_guard("no_proxy");
+ let domain = "lower.case";
+ env::set_var("no_proxy", domain);
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+ assert_eq!(
+ p.no_proxy.expect("should have a no proxy set").domains.0[0],
+ domain
+ );
+
+ env::remove_var("no_proxy");
+ let _g2 = env_guard("NO_PROXY");
+ let domain = "upper.case";
+ env::set_var("NO_PROXY", domain);
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+ assert_eq!(
+ p.no_proxy.expect("should have a no proxy set").domains.0[0],
+ domain
+ );
+
+ let _g3 = env_guard("HTTP_PROXY");
+ env::remove_var("NO_PROXY");
+ env::remove_var("no_proxy");
+ let target = "http://example.domain/";
+ env::set_var("HTTP_PROXY", target);
+
+ // Manually construct this so we aren't use the cache
+ let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None))));
+ p.no_proxy = NoProxy::from_env();
+ assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created");
+
+ assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target);
+
+ // reset user setting when guards drop
+ drop(_g1);
+ drop(_g2);
+ drop(_g3);
+ // Let other threads run now
+ drop(_lock);
+ }
+
+ #[cfg(target_os = "windows")]
+ #[test]
+ fn test_type_prefix_extraction() {
+ assert!(extract_type_prefix("test").is_none());
+ assert!(extract_type_prefix("://test").is_none());
+ assert!(extract_type_prefix("some:prefix://test").is_none());
+ assert!(extract_type_prefix("some/prefix://test").is_none());
+
+ assert_eq!(extract_type_prefix("http://test").unwrap(), "http");
+ assert_eq!(extract_type_prefix("a://test").unwrap(), "a");
+ }
+
+ /// Guard an environment variable, resetting it to the original value
+ /// when dropped.
+ fn env_guard(name: impl Into<String>) -> EnvGuard {
+ let name = name.into();
+ let orig_val = env::var(&name).ok();
+ env::remove_var(&name);
+ EnvGuard { name, orig_val }
+ }
+
+ struct EnvGuard {
+ name: String,
+ orig_val: Option<String>,
+ }
+
+ impl Drop for EnvGuard {
+ fn drop(&mut self) {
+ if let Some(val) = self.orig_val.take() {
+ env::set_var(&self.name, val);
+ } else {
+ env::remove_var(&self.name);
+ }
+ }
+ }
+
+ #[test]
+ fn test_has_http_auth() {
+ let http_proxy_with_auth = Proxy {
+ intercept: Intercept::Http(ProxyScheme::Http {
+ auth: Some(HeaderValue::from_static("auth1")),
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(http_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ Some(HeaderValue::from_static("auth1"))
+ );
+
+ let http_proxy_without_auth = Proxy {
+ intercept: Intercept::Http(ProxyScheme::Http {
+ auth: None,
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(!http_proxy_without_auth.maybe_has_http_auth());
+ assert_eq!(
+ http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ None
+ );
+
+ let https_proxy_with_auth = Proxy {
+ intercept: Intercept::Http(ProxyScheme::Https {
+ auth: Some(HeaderValue::from_static("auth2")),
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(https_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ Some(HeaderValue::from_static("auth2"))
+ );
+
+ let all_http_proxy_with_auth = Proxy {
+ intercept: Intercept::All(ProxyScheme::Http {
+ auth: Some(HeaderValue::from_static("auth3")),
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(all_http_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ Some(HeaderValue::from_static("auth3"))
+ );
+
+ let all_https_proxy_with_auth = Proxy {
+ intercept: Intercept::All(ProxyScheme::Https {
+ auth: Some(HeaderValue::from_static("auth4")),
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(all_https_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ Some(HeaderValue::from_static("auth4"))
+ );
+
+ let all_https_proxy_without_auth = Proxy {
+ intercept: Intercept::All(ProxyScheme::Https {
+ auth: None,
+ host: http::uri::Authority::from_static("authority"),
+ }),
+ no_proxy: None,
+ };
+ assert!(!all_https_proxy_without_auth.maybe_has_http_auth());
+ assert_eq!(
+ all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ None
+ );
+
+ let system_http_proxy_with_auth = Proxy {
+ intercept: Intercept::System(Arc::new({
+ let mut m = HashMap::new();
+ m.insert(
+ "http".into(),
+ ProxyScheme::Http {
+ auth: Some(HeaderValue::from_static("auth5")),
+ host: http::uri::Authority::from_static("authority"),
+ },
+ );
+ m
+ })),
+ no_proxy: None,
+ };
+ assert!(system_http_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ Some(HeaderValue::from_static("auth5"))
+ );
+
+ let system_https_proxy_with_auth = Proxy {
+ intercept: Intercept::System(Arc::new({
+ let mut m = HashMap::new();
+ m.insert(
+ "https".into(),
+ ProxyScheme::Https {
+ auth: Some(HeaderValue::from_static("auth6")),
+ host: http::uri::Authority::from_static("authority"),
+ },
+ );
+ m
+ })),
+ no_proxy: None,
+ };
+ assert!(!system_https_proxy_with_auth.maybe_has_http_auth());
+ assert_eq!(
+ system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")),
+ None
+ );
+ }
+}
+
+#[cfg(test)]
+mod test {
+ mod into_proxy_scheme {
+ use crate::Proxy;
+ use std::error::Error;
+ use std::mem::discriminant;
+
+ fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool {
+ let mut source = haystack.source();
+ while let Some(error) = source {
+ if let Some(parse_error) = error.downcast_ref::<url::ParseError>() {
+ if discriminant(parse_error) == discriminant(&needle) {
+ return true;
+ }
+ }
+ source = error.source();
+ }
+ false
+ }
+
+ fn check_parse_error(url: &str, needle: url::ParseError) {
+ let error = Proxy::http(url).unwrap_err();
+ if !includes(&error, needle) {
+ panic!("{:?} expected; {:?}, {} found", needle, error, error);
+ }
+ }
+
+ mod when_scheme_missing {
+ mod and_url_is_valid {
+ use crate::Proxy;
+
+ #[test]
+ fn lookback_works() {
+ let _ = Proxy::http("127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_port_works() {
+ let _ = Proxy::http("127.0.0.1:8080").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_works() {
+ let _ = Proxy::http("username@127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_password_works() {
+ let _ = Proxy::http("username:password@127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_password_port_works() {
+ let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
+ }
+
+ #[test]
+ fn domain_works() {
+ let _ = Proxy::http("proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_port_works() {
+ let _ = Proxy::http("proxy.example.com:8080").unwrap();
+ }
+
+ #[test]
+ fn domain_username_works() {
+ let _ = Proxy::http("username@proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_username_password_works() {
+ let _ = Proxy::http("username:password@proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_username_password_port_works() {
+ let _ =
+ Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap();
+ }
+ }
+ mod and_url_has_bad {
+ use super::super::check_parse_error;
+
+ #[test]
+ fn host() {
+ check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase);
+ }
+
+ #[test]
+ fn idna_encoding() {
+ check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase);
+ }
+
+ #[test]
+ fn port() {
+ check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase);
+ }
+
+ #[test]
+ fn ip_v4_address() {
+ check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase);
+ }
+
+ #[test]
+ fn ip_v6_address() {
+ check_parse_error(
+ "[56FE::2159:5BBC::6594]",
+ url::ParseError::RelativeUrlWithoutBase,
+ );
+ }
+
+ #[test]
+ fn invalid_domain_character() {
+ check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase);
+ }
+ }
+ }
+
+ mod when_scheme_present {
+ mod and_url_is_valid {
+ use crate::Proxy;
+
+ #[test]
+ fn loopback_works() {
+ let _ = Proxy::http("http://127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_port_works() {
+ let _ = Proxy::http("https://127.0.0.1:8080").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_works() {
+ let _ = Proxy::http("http://username@127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_password_works() {
+ let _ = Proxy::http("https://username:password@127.0.0.1").unwrap();
+ }
+
+ #[test]
+ fn loopback_username_password_port_works() {
+ let _ =
+ Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap();
+ }
+
+ #[test]
+ fn domain_works() {
+ let _ = Proxy::http("https://proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_port_works() {
+ let _ = Proxy::http("http://proxy.example.com:8080").unwrap();
+ }
+
+ #[test]
+ fn domain_username_works() {
+ let _ = Proxy::http("https://username@proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_username_password_works() {
+ let _ = Proxy::http("http://username:password@proxy.example.com").unwrap();
+ }
+
+ #[test]
+ fn domain_username_password_port_works() {
+ let _ =
+ Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080")
+ .unwrap();
+ }
+ }
+ mod and_url_has_bad {
+ use super::super::check_parse_error;
+
+ #[test]
+ fn host() {
+ check_parse_error("http://username@", url::ParseError::EmptyHost);
+ }
+
+ #[test]
+ fn idna_encoding() {
+ check_parse_error("http://xn---", url::ParseError::IdnaError);
+ }
+
+ #[test]
+ fn port() {
+ check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort);
+ }
+
+ #[test]
+ fn ip_v4_address() {
+ check_parse_error(
+ "http://421.627.718.469",
+ url::ParseError::InvalidIpv4Address,
+ );
+ }
+
+ #[test]
+ fn ip_v6_address() {
+ check_parse_error(
+ "http://[56FE::2159:5BBC::6594]",
+ url::ParseError::InvalidIpv6Address,
+ );
+ }
+
+ #[test]
+ fn invalid_domain_character() {
+ check_parse_error("http://abc 123/", url::ParseError::InvalidDomainCharacter);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/reqwest/src/redirect.rs b/vendor/reqwest/src/redirect.rs
new file mode 100644
index 000000000..eabaea37b
--- /dev/null
+++ b/vendor/reqwest/src/redirect.rs
@@ -0,0 +1,337 @@
+//! Redirect Handling
+//!
+//! By default, a `Client` will automatically handle HTTP redirects, having a
+//! maximum redirect chain of 10 hops. To customize this behavior, a
+//! `redirect::Policy` can be used with a `ClientBuilder`.
+
+use std::error::Error as StdError;
+use std::fmt;
+
+use crate::header::{HeaderMap, AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, WWW_AUTHENTICATE};
+use hyper::StatusCode;
+
+use crate::Url;
+
+/// A type that controls the policy on how to handle the following of redirects.
+///
+/// The default value will catch redirect loops, and has a maximum of 10
+/// redirects it will follow in a chain before returning an error.
+///
+/// - `limited` can be used have the same as the default behavior, but adjust
+/// the allowed maximum redirect hops in a chain.
+/// - `none` can be used to disable all redirect behavior.
+/// - `custom` can be used to create a customized policy.
+pub struct Policy {
+ inner: PolicyKind,
+}
+
+/// A type that holds information on the next request and previous requests
+/// in redirect chain.
+#[derive(Debug)]
+pub struct Attempt<'a> {
+ status: StatusCode,
+ next: &'a Url,
+ previous: &'a [Url],
+}
+
+/// An action to perform when a redirect status code is found.
+#[derive(Debug)]
+pub struct Action {
+ inner: ActionKind,
+}
+
+impl Policy {
+ /// Create a `Policy` with a maximum number of redirects.
+ ///
+ /// An `Error` will be returned if the max is reached.
+ pub fn limited(max: usize) -> Self {
+ Self {
+ inner: PolicyKind::Limit(max),
+ }
+ }
+
+ /// Create a `Policy` that does not follow any redirect.
+ pub fn none() -> Self {
+ Self {
+ inner: PolicyKind::None,
+ }
+ }
+
+ /// Create a custom `Policy` using the passed function.
+ ///
+ /// # Note
+ ///
+ /// The default `Policy` handles a maximum loop
+ /// chain, but the custom variant does not do that for you automatically.
+ /// The custom policy should have some way of handling those.
+ ///
+ /// Information on the next request and previous requests can be found
+ /// on the [`Attempt`] argument passed to the closure.
+ ///
+ /// Actions can be conveniently created from methods on the
+ /// [`Attempt`].
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use reqwest::{Error, redirect};
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let custom = redirect::Policy::custom(|attempt| {
+ /// if attempt.previous().len() > 5 {
+ /// attempt.error("too many redirects")
+ /// } else if attempt.url().host_str() == Some("example.domain") {
+ /// // prevent redirects to 'example.domain'
+ /// attempt.stop()
+ /// } else {
+ /// attempt.follow()
+ /// }
+ /// });
+ /// let client = reqwest::Client::builder()
+ /// .redirect(custom)
+ /// .build()?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`Attempt`]: struct.Attempt.html
+ pub fn custom<T>(policy: T) -> Self
+ where
+ T: Fn(Attempt) -> Action + Send + Sync + 'static,
+ {
+ Self {
+ inner: PolicyKind::Custom(Box::new(policy)),
+ }
+ }
+
+ /// Apply this policy to a given [`Attempt`] to produce a [`Action`].
+ ///
+ /// # Note
+ ///
+ /// This method can be used together with `Policy::custom()`
+ /// to construct one `Policy` that wraps another.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use reqwest::{Error, redirect};
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let custom = redirect::Policy::custom(|attempt| {
+ /// eprintln!("{}, Location: {:?}", attempt.status(), attempt.url());
+ /// redirect::Policy::default().redirect(attempt)
+ /// });
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn redirect(&self, attempt: Attempt) -> Action {
+ match self.inner {
+ PolicyKind::Custom(ref custom) => custom(attempt),
+ PolicyKind::Limit(max) => {
+ if attempt.previous.len() >= max {
+ attempt.error(TooManyRedirects)
+ } else {
+ attempt.follow()
+ }
+ }
+ PolicyKind::None => attempt.stop(),
+ }
+ }
+
+ pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> ActionKind {
+ self.redirect(Attempt {
+ status,
+ next,
+ previous,
+ })
+ .inner
+ }
+
+ pub(crate) fn is_default(&self) -> bool {
+ matches!(self.inner, PolicyKind::Limit(10))
+ }
+}
+
+impl Default for Policy {
+ fn default() -> Policy {
+ // Keep `is_default` in sync
+ Policy::limited(10)
+ }
+}
+
+impl<'a> Attempt<'a> {
+ /// Get the type of redirect.
+ pub fn status(&self) -> StatusCode {
+ self.status
+ }
+
+ /// Get the next URL to redirect to.
+ pub fn url(&self) -> &Url {
+ self.next
+ }
+
+ /// Get the list of previous URLs that have already been requested in this chain.
+ pub fn previous(&self) -> &[Url] {
+ self.previous
+ }
+ /// Returns an action meaning reqwest should follow the next URL.
+ pub fn follow(self) -> Action {
+ Action {
+ inner: ActionKind::Follow,
+ }
+ }
+
+ /// Returns an action meaning reqwest should not follow the next URL.
+ ///
+ /// The 30x response will be returned as the `Ok` result.
+ pub fn stop(self) -> Action {
+ Action {
+ inner: ActionKind::Stop,
+ }
+ }
+
+ /// Returns an action failing the redirect with an error.
+ ///
+ /// The `Error` will be returned for the result of the sent request.
+ pub fn error<E: Into<Box<dyn StdError + Send + Sync>>>(self, error: E) -> Action {
+ Action {
+ inner: ActionKind::Error(error.into()),
+ }
+ }
+}
+
+enum PolicyKind {
+ Custom(Box<dyn Fn(Attempt) -> Action + Send + Sync + 'static>),
+ Limit(usize),
+ None,
+}
+
+impl fmt::Debug for Policy {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("Policy").field(&self.inner).finish()
+ }
+}
+
+impl fmt::Debug for PolicyKind {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ PolicyKind::Custom(..) => f.pad("Custom"),
+ PolicyKind::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
+ PolicyKind::None => f.pad("None"),
+ }
+ }
+}
+
+// pub(crate)
+
+#[derive(Debug)]
+pub(crate) enum ActionKind {
+ Follow,
+ Stop,
+ Error(Box<dyn StdError + Send + Sync>),
+}
+
+pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
+ if let Some(previous) = previous.last() {
+ let cross_host = next.host_str() != previous.host_str()
+ || next.port_or_known_default() != previous.port_or_known_default();
+ if cross_host {
+ headers.remove(AUTHORIZATION);
+ headers.remove(COOKIE);
+ headers.remove("cookie2");
+ headers.remove(PROXY_AUTHORIZATION);
+ headers.remove(WWW_AUTHENTICATE);
+ }
+ }
+}
+
+#[derive(Debug)]
+struct TooManyRedirects;
+
+impl fmt::Display for TooManyRedirects {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str("too many redirects")
+ }
+}
+
+impl StdError for TooManyRedirects {}
+
+#[test]
+fn test_redirect_policy_limit() {
+ let policy = Policy::default();
+ let next = Url::parse("http://x.y/z").unwrap();
+ let mut previous = (0..9)
+ .map(|i| Url::parse(&format!("http://a.b/c/{}", i)).unwrap())
+ .collect::<Vec<_>>();
+
+ match policy.check(StatusCode::FOUND, &next, &previous) {
+ ActionKind::Follow => (),
+ other => panic!("unexpected {:?}", other),
+ }
+
+ previous.push(Url::parse("http://a.b.d/e/33").unwrap());
+
+ match policy.check(StatusCode::FOUND, &next, &previous) {
+ ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
+ other => panic!("unexpected {:?}", other),
+ }
+}
+
+#[test]
+fn test_redirect_policy_limit_to_0() {
+ let policy = Policy::limited(0);
+ let next = Url::parse("http://x.y/z").unwrap();
+ let previous = vec![Url::parse("http://a.b/c").unwrap()];
+
+ match policy.check(StatusCode::FOUND, &next, &previous) {
+ ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
+ other => panic!("unexpected {:?}", other),
+ }
+}
+
+#[test]
+fn test_redirect_policy_custom() {
+ let policy = Policy::custom(|attempt| {
+ if attempt.url().host_str() == Some("foo") {
+ attempt.stop()
+ } else {
+ attempt.follow()
+ }
+ });
+
+ let next = Url::parse("http://bar/baz").unwrap();
+ match policy.check(StatusCode::FOUND, &next, &[]) {
+ ActionKind::Follow => (),
+ other => panic!("unexpected {:?}", other),
+ }
+
+ let next = Url::parse("http://foo/baz").unwrap();
+ match policy.check(StatusCode::FOUND, &next, &[]) {
+ ActionKind::Stop => (),
+ other => panic!("unexpected {:?}", other),
+ }
+}
+
+#[test]
+fn test_remove_sensitive_headers() {
+ use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE};
+
+ let mut headers = HeaderMap::new();
+ headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
+ headers.insert(AUTHORIZATION, HeaderValue::from_static("let me in"));
+ headers.insert(COOKIE, HeaderValue::from_static("foo=bar"));
+
+ let next = Url::parse("http://initial-domain.com/path").unwrap();
+ let mut prev = vec![Url::parse("http://initial-domain.com/new_path").unwrap()];
+ let mut filtered_headers = headers.clone();
+
+ remove_sensitive_headers(&mut headers, &next, &prev);
+ assert_eq!(headers, filtered_headers);
+
+ prev.push(Url::parse("http://new-domain.com/path").unwrap());
+ filtered_headers.remove(AUTHORIZATION);
+ filtered_headers.remove(COOKIE);
+
+ remove_sensitive_headers(&mut headers, &next, &prev);
+ assert_eq!(headers, filtered_headers);
+}
diff --git a/vendor/reqwest/src/response.rs b/vendor/reqwest/src/response.rs
new file mode 100644
index 000000000..9c92cba53
--- /dev/null
+++ b/vendor/reqwest/src/response.rs
@@ -0,0 +1,41 @@
+use url::Url;
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct ResponseUrl(pub Url);
+
+/// Extension trait for http::response::Builder objects
+///
+/// Allows the user to add a `Url` to the http::Response
+pub trait ResponseBuilderExt {
+ /// A builder method for the `http::response::Builder` type that allows the user to add a `Url`
+ /// to the `http::Response`
+ fn url(self, url: Url) -> Self;
+}
+
+impl ResponseBuilderExt for http::response::Builder {
+ fn url(self, url: Url) -> Self {
+ self.extension(ResponseUrl(url))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{ResponseBuilderExt, ResponseUrl};
+ use http::response::Builder;
+ use url::Url;
+
+ #[test]
+ fn test_response_builder_ext() {
+ let url = Url::parse("http://example.com").unwrap();
+ let response = Builder::new()
+ .status(200)
+ .url(url.clone())
+ .body(())
+ .unwrap();
+
+ assert_eq!(
+ response.extensions().get::<ResponseUrl>(),
+ Some(&ResponseUrl(url))
+ );
+ }
+}
diff --git a/vendor/reqwest/src/tls.rs b/vendor/reqwest/src/tls.rs
new file mode 100644
index 000000000..60d702d77
--- /dev/null
+++ b/vendor/reqwest/src/tls.rs
@@ -0,0 +1,510 @@
+//! TLS configuration
+//!
+//! By default, a `Client` will make use of system-native transport layer
+//! security to connect to HTTPS destinations. This means schannel on Windows,
+//! Security-Framework on macOS, and OpenSSL on Linux.
+//!
+//! - Additional X509 certificates can be configured on a `ClientBuilder` with the
+//! [`Certificate`](Certificate) type.
+//! - Client certificates can be add to a `ClientBuilder` with the
+//! [`Identity`][Identity] type.
+//! - Various parts of TLS can also be configured or even disabled on the
+//! `ClientBuilder`.
+
+#[cfg(feature = "__rustls")]
+use rustls::{
+ client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier,
+ DigitallySignedStruct, Error as TLSError, ServerName,
+};
+use std::fmt;
+
+/// Represents a server X509 certificate.
+#[derive(Clone)]
+pub struct Certificate {
+ #[cfg(feature = "native-tls-crate")]
+ native: native_tls_crate::Certificate,
+ #[cfg(feature = "__rustls")]
+ original: Cert,
+}
+
+#[cfg(feature = "__rustls")]
+#[derive(Clone)]
+enum Cert {
+ Der(Vec<u8>),
+ Pem(Vec<u8>),
+}
+
+/// Represents a private key and X509 cert as a client certificate.
+#[derive(Clone)]
+pub struct Identity {
+ #[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
+ inner: ClientCert,
+}
+
+#[derive(Clone)]
+enum ClientCert {
+ #[cfg(feature = "native-tls")]
+ Pkcs12(native_tls_crate::Identity),
+ #[cfg(feature = "native-tls")]
+ Pkcs8(native_tls_crate::Identity),
+ #[cfg(feature = "__rustls")]
+ Pem {
+ key: rustls::PrivateKey,
+ certs: Vec<rustls::Certificate>,
+ },
+}
+
+impl Certificate {
+ /// Create a `Certificate` from a binary DER encoded certificate
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn cert() -> Result<(), Box<std::error::Error>> {
+ /// let mut buf = Vec::new();
+ /// File::open("my_cert.der")?
+ /// .read_to_end(&mut buf)?;
+ /// let cert = reqwest::Certificate::from_der(&buf)?;
+ /// # drop(cert);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
+ Ok(Certificate {
+ #[cfg(feature = "native-tls-crate")]
+ native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
+ #[cfg(feature = "__rustls")]
+ original: Cert::Der(der.to_owned()),
+ })
+ }
+
+ /// Create a `Certificate` from a PEM encoded certificate
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn cert() -> Result<(), Box<std::error::Error>> {
+ /// let mut buf = Vec::new();
+ /// File::open("my_cert.pem")?
+ /// .read_to_end(&mut buf)?;
+ /// let cert = reqwest::Certificate::from_pem(&buf)?;
+ /// # drop(cert);
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
+ Ok(Certificate {
+ #[cfg(feature = "native-tls-crate")]
+ native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
+ #[cfg(feature = "__rustls")]
+ original: Cert::Pem(pem.to_owned()),
+ })
+ }
+
+ #[cfg(feature = "native-tls-crate")]
+ pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
+ tls.add_root_certificate(self.native);
+ }
+
+ #[cfg(feature = "__rustls")]
+ pub(crate) fn add_to_rustls(
+ self,
+ root_cert_store: &mut rustls::RootCertStore,
+ ) -> crate::Result<()> {
+ use std::io::Cursor;
+
+ match self.original {
+ Cert::Der(buf) => root_cert_store
+ .add(&rustls::Certificate(buf))
+ .map_err(crate::error::builder)?,
+ Cert::Pem(buf) => {
+ let mut pem = Cursor::new(buf);
+ let certs = rustls_pemfile::certs(&mut pem).map_err(|_| {
+ crate::error::builder(TLSError::General(String::from(
+ "No valid certificate was found",
+ )))
+ })?;
+ for c in certs {
+ root_cert_store
+ .add(&rustls::Certificate(c))
+ .map_err(crate::error::builder)?;
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Identity {
+ /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
+ ///
+ /// The archive should contain a leaf certificate and its private key, as well any intermediate
+ /// certificates that allow clients to build a chain to a trusted root.
+ /// The chain certificates should be in order from the leaf certificate towards the root.
+ ///
+ /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
+ /// with the OpenSSL `pkcs12` tool:
+ ///
+ /// ```bash
+ /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
+ /// ```
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn pkcs12() -> Result<(), Box<std::error::Error>> {
+ /// let mut buf = Vec::new();
+ /// File::open("my-ident.pfx")?
+ /// .read_to_end(&mut buf)?;
+ /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
+ /// # drop(pkcs12);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the `native-tls` Cargo feature enabled.
+ #[cfg(feature = "native-tls")]
+ pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
+ Ok(Identity {
+ inner: ClientCert::Pkcs12(
+ native_tls_crate::Identity::from_pkcs12(der, password)
+ .map_err(crate::error::builder)?,
+ ),
+ })
+ }
+
+ /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
+ /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
+ ///
+ /// The certificate chain should contain any intermediate cerficates that should be sent to
+ /// clients to allow them to build a chain to a trusted root.
+ ///
+ /// A certificate chain here means a series of PEM encoded certificates concatenated together.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::fs;
+ /// # fn pkcs8() -> Result<(), Box<std::error::Error>> {
+ /// let cert = fs::read("client.pem")?;
+ /// let key = fs::read("key.pem")?;
+ /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
+ /// # drop(pkcs8);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the `native-tls` Cargo feature enabled.
+ #[cfg(feature = "native-tls")]
+ pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
+ Ok(Identity {
+ inner: ClientCert::Pkcs8(
+ native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
+ ),
+ })
+ }
+
+ /// Parses PEM encoded private key and certificate.
+ ///
+ /// The input should contain a PEM encoded private key
+ /// and at least one PEM encoded certificate.
+ ///
+ /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::fs::File;
+ /// # use std::io::Read;
+ /// # fn pem() -> Result<(), Box<std::error::Error>> {
+ /// let mut buf = Vec::new();
+ /// File::open("my-ident.pem")?
+ /// .read_to_end(&mut buf)?;
+ /// let id = reqwest::Identity::from_pem(&buf)?;
+ /// # drop(id);
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// # Optional
+ ///
+ /// This requires the `rustls-tls(-...)` Cargo feature enabled.
+ #[cfg(feature = "__rustls")]
+ pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
+ use std::io::Cursor;
+
+ let (key, certs) = {
+ let mut pem = Cursor::new(buf);
+ let mut sk = Vec::<rustls::PrivateKey>::new();
+ let mut certs = Vec::<rustls::Certificate>::new();
+
+ for item in std::iter::from_fn(|| rustls_pemfile::read_one(&mut pem).transpose()) {
+ match item.map_err(|_| {
+ crate::error::builder(TLSError::General(String::from(
+ "Invalid identity PEM file",
+ )))
+ })? {
+ rustls_pemfile::Item::X509Certificate(cert) => {
+ certs.push(rustls::Certificate(cert))
+ }
+ rustls_pemfile::Item::PKCS8Key(key) => sk.push(rustls::PrivateKey(key)),
+ rustls_pemfile::Item::RSAKey(key) => sk.push(rustls::PrivateKey(key)),
+ rustls_pemfile::Item::ECKey(key) => sk.push(rustls::PrivateKey(key)),
+ _ => {
+ return Err(crate::error::builder(TLSError::General(String::from(
+ "No valid certificate was found",
+ ))))
+ }
+ }
+ }
+
+ if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
+ (sk, certs)
+ } else {
+ return Err(crate::error::builder(TLSError::General(String::from(
+ "private key or certificate not found",
+ ))));
+ }
+ };
+
+ Ok(Identity {
+ inner: ClientCert::Pem { key, certs },
+ })
+ }
+
+ #[cfg(feature = "native-tls")]
+ pub(crate) fn add_to_native_tls(
+ self,
+ tls: &mut native_tls_crate::TlsConnectorBuilder,
+ ) -> crate::Result<()> {
+ match self.inner {
+ ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
+ tls.identity(id);
+ Ok(())
+ }
+ #[cfg(feature = "__rustls")]
+ ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
+ }
+ }
+
+ #[cfg(feature = "__rustls")]
+ pub(crate) fn add_to_rustls(
+ self,
+ config_builder: rustls::ConfigBuilder<
+ rustls::ClientConfig,
+ rustls::client::WantsTransparencyPolicyOrClientCert,
+ >,
+ ) -> crate::Result<rustls::ClientConfig> {
+ match self.inner {
+ ClientCert::Pem { key, certs } => config_builder
+ .with_single_cert(certs, key)
+ .map_err(crate::error::builder),
+ #[cfg(feature = "native-tls")]
+ ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
+ Err(crate::error::builder("incompatible TLS identity type"))
+ }
+ }
+ }
+}
+
+impl fmt::Debug for Certificate {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Certificate").finish()
+ }
+}
+
+impl fmt::Debug for Identity {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Identity").finish()
+ }
+}
+
+/// A TLS protocol version.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Version(InnerVersion);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[non_exhaustive]
+enum InnerVersion {
+ Tls1_0,
+ Tls1_1,
+ Tls1_2,
+ Tls1_3,
+}
+
+// These could perhaps be From/TryFrom implementations, but those would be
+// part of the public API so let's be careful
+impl Version {
+ /// Version 1.0 of the TLS protocol.
+ pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
+ /// Version 1.1 of the TLS protocol.
+ pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
+ /// Version 1.2 of the TLS protocol.
+ pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
+ /// Version 1.3 of the TLS protocol.
+ pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
+
+ #[cfg(feature = "default-tls")]
+ pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
+ match self.0 {
+ InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
+ InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
+ InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
+ InnerVersion::Tls1_3 => None,
+ }
+ }
+
+ #[cfg(feature = "__rustls")]
+ pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
+ match version {
+ rustls::ProtocolVersion::SSLv2 => None,
+ rustls::ProtocolVersion::SSLv3 => None,
+ rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
+ rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
+ rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
+ rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
+ _ => None,
+ }
+ }
+}
+
+pub(crate) enum TlsBackend {
+ // This is the default and HTTP/3 feature does not use it so suppress it.
+ #[allow(dead_code)]
+ #[cfg(feature = "default-tls")]
+ Default,
+ #[cfg(feature = "native-tls")]
+ BuiltNativeTls(native_tls_crate::TlsConnector),
+ #[cfg(feature = "__rustls")]
+ Rustls,
+ #[cfg(feature = "__rustls")]
+ BuiltRustls(rustls::ClientConfig),
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ UnknownPreconfigured,
+}
+
+impl fmt::Debug for TlsBackend {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ #[cfg(feature = "default-tls")]
+ TlsBackend::Default => write!(f, "Default"),
+ #[cfg(feature = "native-tls")]
+ TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::Rustls => write!(f, "Rustls"),
+ #[cfg(feature = "__rustls")]
+ TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
+ #[cfg(any(feature = "native-tls", feature = "__rustls",))]
+ TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
+ }
+ }
+}
+
+impl Default for TlsBackend {
+ fn default() -> TlsBackend {
+ #[cfg(all(feature = "default-tls", not(feature = "http3")))]
+ {
+ TlsBackend::Default
+ }
+
+ #[cfg(any(
+ all(feature = "__rustls", not(feature = "default-tls")),
+ feature = "http3"
+ ))]
+ {
+ TlsBackend::Rustls
+ }
+ }
+}
+
+#[cfg(feature = "__rustls")]
+pub(crate) struct NoVerifier;
+
+#[cfg(feature = "__rustls")]
+impl ServerCertVerifier for NoVerifier {
+ fn verify_server_cert(
+ &self,
+ _end_entity: &rustls::Certificate,
+ _intermediates: &[rustls::Certificate],
+ _server_name: &ServerName,
+ _scts: &mut dyn Iterator<Item = &[u8]>,
+ _ocsp_response: &[u8],
+ _now: std::time::SystemTime,
+ ) -> Result<ServerCertVerified, TLSError> {
+ Ok(ServerCertVerified::assertion())
+ }
+
+ fn verify_tls12_signature(
+ &self,
+ _message: &[u8],
+ _cert: &rustls::Certificate,
+ _dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, TLSError> {
+ Ok(HandshakeSignatureValid::assertion())
+ }
+
+ fn verify_tls13_signature(
+ &self,
+ _message: &[u8],
+ _cert: &rustls::Certificate,
+ _dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, TLSError> {
+ Ok(HandshakeSignatureValid::assertion())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[cfg(feature = "default-tls")]
+ #[test]
+ fn certificate_from_der_invalid() {
+ Certificate::from_der(b"not der").unwrap_err();
+ }
+
+ #[cfg(feature = "default-tls")]
+ #[test]
+ fn certificate_from_pem_invalid() {
+ Certificate::from_pem(b"not pem").unwrap_err();
+ }
+
+ #[cfg(feature = "native-tls")]
+ #[test]
+ fn identity_from_pkcs12_der_invalid() {
+ Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
+ }
+
+ #[cfg(feature = "native-tls")]
+ #[test]
+ fn identity_from_pkcs8_pem_invalid() {
+ Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
+ }
+
+ #[cfg(feature = "__rustls")]
+ #[test]
+ fn identity_from_pem_invalid() {
+ Identity::from_pem(b"not pem").unwrap_err();
+ }
+
+ #[cfg(feature = "__rustls")]
+ #[test]
+ fn identity_from_pem_pkcs1_key() {
+ let pem = b"-----BEGIN CERTIFICATE-----\n\
+ -----END CERTIFICATE-----\n\
+ -----BEGIN RSA PRIVATE KEY-----\n\
+ -----END RSA PRIVATE KEY-----\n";
+
+ Identity::from_pem(pem).unwrap();
+ }
+}
diff --git a/vendor/reqwest/src/util.rs b/vendor/reqwest/src/util.rs
new file mode 100644
index 000000000..018db307f
--- /dev/null
+++ b/vendor/reqwest/src/util.rs
@@ -0,0 +1,89 @@
+use crate::header::{Entry, HeaderMap, HeaderValue, OccupiedEntry};
+
+pub fn basic_auth<U, P>(username: U, password: Option<P>) -> HeaderValue
+where
+ U: std::fmt::Display,
+ P: std::fmt::Display,
+{
+ use base64::prelude::BASE64_STANDARD;
+ use base64::write::EncoderWriter;
+ use std::io::Write;
+
+ let mut buf = b"Basic ".to_vec();
+ {
+ let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
+ let _ = write!(encoder, "{}:", username);
+ if let Some(password) = password {
+ let _ = write!(encoder, "{}", password);
+ }
+ }
+ let mut header = HeaderValue::from_bytes(&buf).expect("base64 is always valid HeaderValue");
+ header.set_sensitive(true);
+ header
+}
+
+// xor-shift
+#[cfg(not(target_arch = "wasm32"))]
+pub(crate) fn fast_random() -> u64 {
+ use std::cell::Cell;
+ use std::collections::hash_map::RandomState;
+ use std::hash::{BuildHasher, Hasher};
+ use std::num::Wrapping;
+
+ thread_local! {
+ static RNG: Cell<Wrapping<u64>> = Cell::new(Wrapping(seed()));
+ }
+
+ fn seed() -> u64 {
+ let seed = RandomState::new();
+
+ let mut out = 0;
+ let mut cnt = 0;
+ while out == 0 {
+ cnt += 1;
+ let mut hasher = seed.build_hasher();
+ hasher.write_usize(cnt);
+ out = hasher.finish();
+ }
+ out
+ }
+
+ RNG.with(|rng| {
+ let mut n = rng.get();
+ debug_assert_ne!(n.0, 0);
+ n ^= n >> 12;
+ n ^= n << 25;
+ n ^= n >> 27;
+ rng.set(n);
+ n.0.wrapping_mul(0x2545_f491_4f6c_dd1d)
+ })
+}
+
+pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
+ // IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
+ // The first time a name is yielded, it will be Some(name), and if
+ // there are more values with the same name, the next yield will be
+ // None.
+
+ let mut prev_entry: Option<OccupiedEntry<_>> = None;
+ for (key, value) in src {
+ match key {
+ Some(key) => match dst.entry(key) {
+ Entry::Occupied(mut e) => {
+ e.insert(value);
+ prev_entry = Some(e);
+ }
+ Entry::Vacant(e) => {
+ let e = e.insert_entry(value);
+ prev_entry = Some(e);
+ }
+ },
+ None => match prev_entry {
+ Some(ref mut entry) => {
+ entry.append(value);
+ }
+ None => unreachable!("HeaderMap::into_iter yielded None first"),
+ },
+ }
+ }
+}
diff --git a/vendor/reqwest/src/wasm/body.rs b/vendor/reqwest/src/wasm/body.rs
new file mode 100644
index 000000000..dc38ed8ff
--- /dev/null
+++ b/vendor/reqwest/src/wasm/body.rs
@@ -0,0 +1,291 @@
+#[cfg(feature = "multipart")]
+use super::multipart::Form;
+/// dox
+use bytes::Bytes;
+use js_sys::Uint8Array;
+use std::fmt;
+use wasm_bindgen::JsValue;
+
+/// The body of a `Request`.
+///
+/// In most cases, this is not needed directly, as the
+/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
+/// passing many things (like a string or vector of bytes).
+///
+/// [builder]: ./struct.RequestBuilder.html#method.body
+pub struct Body {
+ inner: Inner,
+}
+
+enum Inner {
+ Bytes(Bytes),
+ /// MultipartForm holds a multipart/form-data body.
+ #[cfg(feature = "multipart")]
+ MultipartForm(Form),
+ /// MultipartPart holds the body of a multipart/form-data part.
+ #[cfg(feature = "multipart")]
+ MultipartPart(Bytes),
+}
+
+impl Body {
+ /// Returns a reference to the internal data of the `Body`.
+ ///
+ /// `None` is returned, if the underlying data is a multipart form.
+ #[inline]
+ pub fn as_bytes(&self) -> Option<&[u8]> {
+ match &self.inner {
+ Inner::Bytes(bytes) => Some(bytes.as_ref()),
+ #[cfg(feature = "multipart")]
+ Inner::MultipartForm(_) => None,
+ #[cfg(feature = "multipart")]
+ Inner::MultipartPart(bytes) => Some(bytes.as_ref()),
+ }
+ }
+ pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
+ match &self.inner {
+ Inner::Bytes(body_bytes) => {
+ let body_bytes: &[u8] = body_bytes.as_ref();
+ let body_uint8_array: Uint8Array = body_bytes.into();
+ let js_value: &JsValue = body_uint8_array.as_ref();
+ Ok(js_value.to_owned())
+ }
+ #[cfg(feature = "multipart")]
+ Inner::MultipartForm(form) => {
+ let form_data = form.to_form_data()?;
+ let js_value: &JsValue = form_data.as_ref();
+ Ok(js_value.to_owned())
+ }
+ #[cfg(feature = "multipart")]
+ Inner::MultipartPart(body_bytes) => {
+ let body_bytes: &[u8] = body_bytes.as_ref();
+ let body_uint8_array: Uint8Array = body_bytes.into();
+ let body_array = js_sys::Array::new();
+ body_array.push(&body_uint8_array);
+ let js_value: &JsValue = body_array.as_ref();
+ Ok(js_value.to_owned())
+ }
+ }
+ }
+
+ #[inline]
+ #[cfg(feature = "multipart")]
+ pub(crate) fn from_form(f: Form) -> Body {
+ Self {
+ inner: Inner::MultipartForm(f),
+ }
+ }
+
+ /// into_part turns a regular body into the body of a multipart/form-data part.
+ #[cfg(feature = "multipart")]
+ pub(crate) fn into_part(self) -> Body {
+ match self.inner {
+ Inner::Bytes(bytes) => Self {
+ inner: Inner::MultipartPart(bytes),
+ },
+ Inner::MultipartForm(form) => Self {
+ inner: Inner::MultipartForm(form),
+ },
+ Inner::MultipartPart(bytes) => Self {
+ inner: Inner::MultipartPart(bytes),
+ },
+ }
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ match &self.inner {
+ Inner::Bytes(bytes) => bytes.is_empty(),
+ #[cfg(feature = "multipart")]
+ Inner::MultipartForm(form) => form.is_empty(),
+ #[cfg(feature = "multipart")]
+ Inner::MultipartPart(bytes) => bytes.is_empty(),
+ }
+ }
+
+ pub(crate) fn try_clone(&self) -> Option<Body> {
+ match &self.inner {
+ Inner::Bytes(bytes) => Some(Self {
+ inner: Inner::Bytes(bytes.clone()),
+ }),
+ #[cfg(feature = "multipart")]
+ Inner::MultipartForm(_) => None,
+ #[cfg(feature = "multipart")]
+ Inner::MultipartPart(bytes) => Some(Self {
+ inner: Inner::MultipartPart(bytes.clone()),
+ }),
+ }
+ }
+}
+
+impl From<Bytes> for Body {
+ #[inline]
+ fn from(bytes: Bytes) -> Body {
+ Body {
+ inner: Inner::Bytes(bytes),
+ }
+ }
+}
+
+impl From<Vec<u8>> for Body {
+ #[inline]
+ fn from(vec: Vec<u8>) -> Body {
+ Body {
+ inner: Inner::Bytes(vec.into()),
+ }
+ }
+}
+
+impl From<&'static [u8]> for Body {
+ #[inline]
+ fn from(s: &'static [u8]) -> Body {
+ Body {
+ inner: Inner::Bytes(Bytes::from_static(s)),
+ }
+ }
+}
+
+impl From<String> for Body {
+ #[inline]
+ fn from(s: String) -> Body {
+ Body {
+ inner: Inner::Bytes(s.into()),
+ }
+ }
+}
+
+impl From<&'static str> for Body {
+ #[inline]
+ fn from(s: &'static str) -> Body {
+ s.as_bytes().into()
+ }
+}
+
+impl fmt::Debug for Body {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Body").finish()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::Body;
+ use js_sys::Uint8Array;
+ use wasm_bindgen::prelude::*;
+ use wasm_bindgen_test::*;
+
+ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
+
+ #[wasm_bindgen]
+ extern "C" {
+ // Use `js_namespace` here to bind `console.log(..)` instead of just
+ // `log(..)`
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: String);
+ }
+
+ #[wasm_bindgen_test]
+ async fn test_body() {
+ let body = Body::from("TEST");
+ assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
+ }
+
+ #[wasm_bindgen_test]
+ async fn test_body_js_static_str() {
+ let body_value = "TEST";
+ let body = Body::from(body_value);
+
+ let mut init = web_sys::RequestInit::new();
+ init.method("POST");
+ init.body(Some(
+ body.to_js_value()
+ .expect("could not convert body to JsValue")
+ .as_ref(),
+ ));
+
+ let js_req = web_sys::Request::new_with_str_and_init("", &init)
+ .expect("could not create JS request");
+ let text_promise = js_req.text().expect("could not get text promise");
+ let text = crate::wasm::promise::<JsValue>(text_promise)
+ .await
+ .expect("could not get request body as text");
+
+ assert_eq!(text.as_string().expect("text is not a string"), body_value);
+ }
+ #[wasm_bindgen_test]
+ async fn test_body_js_string() {
+ let body_value = "TEST".to_string();
+ let body = Body::from(body_value.clone());
+
+ let mut init = web_sys::RequestInit::new();
+ init.method("POST");
+ init.body(Some(
+ body.to_js_value()
+ .expect("could not convert body to JsValue")
+ .as_ref(),
+ ));
+
+ let js_req = web_sys::Request::new_with_str_and_init("", &init)
+ .expect("could not create JS request");
+ let text_promise = js_req.text().expect("could not get text promise");
+ let text = crate::wasm::promise::<JsValue>(text_promise)
+ .await
+ .expect("could not get request body as text");
+
+ assert_eq!(text.as_string().expect("text is not a string"), body_value);
+ }
+
+ #[wasm_bindgen_test]
+ async fn test_body_js_static_u8_slice() {
+ let body_value: &'static [u8] = b"\x00\x42";
+ let body = Body::from(body_value);
+
+ let mut init = web_sys::RequestInit::new();
+ init.method("POST");
+ init.body(Some(
+ body.to_js_value()
+ .expect("could not convert body to JsValue")
+ .as_ref(),
+ ));
+
+ let js_req = web_sys::Request::new_with_str_and_init("", &init)
+ .expect("could not create JS request");
+
+ let array_buffer_promise = js_req
+ .array_buffer()
+ .expect("could not get array_buffer promise");
+ let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
+ .await
+ .expect("could not get request body as array buffer");
+
+ let v = Uint8Array::new(&array_buffer).to_vec();
+
+ assert_eq!(v, body_value);
+ }
+
+ #[wasm_bindgen_test]
+ async fn test_body_js_vec_u8() {
+ let body_value = vec![0u8, 42];
+ let body = Body::from(body_value.clone());
+
+ let mut init = web_sys::RequestInit::new();
+ init.method("POST");
+ init.body(Some(
+ body.to_js_value()
+ .expect("could not convert body to JsValue")
+ .as_ref(),
+ ));
+
+ let js_req = web_sys::Request::new_with_str_and_init("", &init)
+ .expect("could not create JS request");
+
+ let array_buffer_promise = js_req
+ .array_buffer()
+ .expect("could not get array_buffer promise");
+ let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
+ .await
+ .expect("could not get request body as array buffer");
+
+ let v = Uint8Array::new(&array_buffer).to_vec();
+
+ assert_eq!(v, body_value);
+ }
+}
diff --git a/vendor/reqwest/src/wasm/client.rs b/vendor/reqwest/src/wasm/client.rs
new file mode 100644
index 000000000..cfe97bf03
--- /dev/null
+++ b/vendor/reqwest/src/wasm/client.rs
@@ -0,0 +1,379 @@
+use http::{HeaderMap, Method};
+use js_sys::{Promise, JSON};
+use std::{fmt, future::Future, sync::Arc};
+use url::Url;
+use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
+
+use super::{AbortGuard, Request, RequestBuilder, Response};
+use crate::IntoUrl;
+
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_name = fetch)]
+ fn fetch_with_request(input: &web_sys::Request) -> Promise;
+}
+
+fn js_fetch(req: &web_sys::Request) -> Promise {
+ use wasm_bindgen::{JsCast, JsValue};
+ let global = js_sys::global();
+
+ if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope"))
+ {
+ global
+ .unchecked_into::<web_sys::ServiceWorkerGlobalScope>()
+ .fetch_with_request(req)
+ } else {
+ // browser
+ fetch_with_request(req)
+ }
+}
+
+/// dox
+#[derive(Clone)]
+pub struct Client {
+ config: Arc<Config>,
+}
+
+/// dox
+pub struct ClientBuilder {
+ config: Config,
+}
+
+impl Client {
+ /// dox
+ pub fn new() -> Self {
+ Client::builder().build().unwrap_throw()
+ }
+
+ /// dox
+ pub fn builder() -> ClientBuilder {
+ ClientBuilder::new()
+ }
+
+ /// Convenience method to make a `GET` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::GET, url)
+ }
+
+ /// Convenience method to make a `POST` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::POST, url)
+ }
+
+ /// Convenience method to make a `PUT` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PUT, url)
+ }
+
+ /// Convenience method to make a `PATCH` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::PATCH, url)
+ }
+
+ /// Convenience method to make a `DELETE` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::DELETE, url)
+ }
+
+ /// Convenience method to make a `HEAD` request to a URL.
+ ///
+ /// # Errors
+ ///
+ /// This method fails whenever supplied `Url` cannot be parsed.
+ pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
+ self.request(Method::HEAD, url)
+ }
+
+ /// Start building a `Request` with the `Method` and `Url`.
+ ///
+ /// Returns a `RequestBuilder`, which will allow setting headers and
+ /// request body before sending.
+ ///
+ /// # Errors
+ ///
+ /// 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));
+ RequestBuilder::new(self.clone(), req)
+ }
+
+ /// Executes a `Request`.
+ ///
+ /// A `Request` can be built manually with `Request::new()` or obtained
+ /// from a RequestBuilder with `RequestBuilder::build()`.
+ ///
+ /// You should prefer to use the `RequestBuilder` and
+ /// `RequestBuilder::send()`.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request,
+ /// redirect loop was detected or redirect limit was exhausted.
+ pub fn execute(
+ &self,
+ request: Request,
+ ) -> impl Future<Output = Result<Response, crate::Error>> {
+ self.execute_request(request)
+ }
+
+ // merge request headers with Client default_headers, prior to external http fetch
+ fn merge_headers(&self, req: &mut Request) {
+ use http::header::Entry;
+ let headers: &mut HeaderMap = req.headers_mut();
+ // insert default headers in the request headers
+ // without overwriting already appended headers.
+ for (key, value) in self.config.headers.iter() {
+ if let Entry::Vacant(entry) = headers.entry(key) {
+ entry.insert(value.clone());
+ }
+ }
+ }
+
+ pub(super) fn execute_request(
+ &self,
+ mut req: Request,
+ ) -> impl Future<Output = crate::Result<Response>> {
+ self.merge_headers(&mut req);
+ fetch(req)
+ }
+}
+
+impl Default for Client {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl fmt::Debug for Client {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("Client");
+ self.config.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+impl fmt::Debug for ClientBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("ClientBuilder");
+ self.config.fmt_fields(&mut builder);
+ builder.finish()
+ }
+}
+
+async fn fetch(req: Request) -> crate::Result<Response> {
+ // Build the js Request
+ let mut init = web_sys::RequestInit::new();
+ init.method(req.method().as_str());
+
+ // convert HeaderMap to Headers
+ let js_headers = web_sys::Headers::new()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?;
+
+ for (name, value) in req.headers() {
+ js_headers
+ .append(
+ name.as_str(),
+ value.to_str().map_err(crate::error::builder)?,
+ )
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?;
+ }
+ init.headers(&js_headers.into());
+
+ // When req.cors is true, do nothing because the default mode is 'cors'
+ if !req.cors {
+ init.mode(web_sys::RequestMode::NoCors);
+ }
+
+ if let Some(creds) = req.credentials {
+ init.credentials(creds);
+ }
+
+ if let Some(body) = req.body() {
+ if !body.is_empty() {
+ init.body(Some(body.to_js_value()?.as_ref()));
+ }
+ }
+
+ let abort = AbortGuard::new()?;
+ init.signal(Some(&abort.signal()));
+
+ let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?;
+
+ // Await the fetch() promise
+ let p = js_fetch(&js_req);
+ let js_resp = super::promise::<web_sys::Response>(p)
+ .await
+ .map_err(crate::error::request)?;
+
+ // Convert from the js Response
+ let mut resp = http::Response::builder().status(js_resp.status());
+
+ let url = Url::parse(&js_resp.url()).expect_throw("url parse");
+
+ let js_headers = js_resp.headers();
+ let js_iter = js_sys::try_iter(&js_headers)
+ .expect_throw("headers try_iter")
+ .expect_throw("headers have an iterator");
+
+ for item in js_iter {
+ let item = item.expect_throw("headers iterator doesn't throw");
+ let serialized_headers: String = JSON::stringify(&item)
+ .expect_throw("serialized headers")
+ .into();
+ let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers)
+ .expect_throw("deserializable serialized headers");
+ resp = resp.header(&name, &value);
+ }
+
+ resp.body(js_resp)
+ .map(|resp| Response::new(resp, url, abort))
+ .map_err(crate::error::request)
+}
+
+// ===== impl ClientBuilder =====
+
+impl ClientBuilder {
+ /// dox
+ pub fn new() -> Self {
+ ClientBuilder {
+ config: Config::default(),
+ }
+ }
+
+ /// Returns a 'Client' that uses this ClientBuilder configuration
+ pub fn build(mut self) -> Result<Client, crate::Error> {
+ let config = std::mem::take(&mut self.config);
+ Ok(Client {
+ config: Arc::new(config),
+ })
+ }
+
+ /// Sets the default headers for every request
+ pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
+ for (key, value) in headers.iter() {
+ self.config.headers.insert(key, value.clone());
+ }
+ self
+ }
+}
+
+impl Default for ClientBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[derive(Clone, Debug)]
+struct Config {
+ headers: HeaderMap,
+}
+
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ headers: HeaderMap::new(),
+ }
+ }
+}
+
+impl Config {
+ fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
+ f.field("default_headers", &self.headers);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use wasm_bindgen_test::*;
+
+ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
+
+ #[wasm_bindgen_test]
+ async fn default_headers() {
+ use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
+
+ let mut headers = HeaderMap::new();
+ headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
+ let client = crate::Client::builder()
+ .default_headers(headers)
+ .build()
+ .expect("client");
+ let mut req = client
+ .get("https://www.example.com")
+ .build()
+ .expect("request");
+ // merge headers as if client were about to issue fetch
+ client.merge_headers(&mut req);
+
+ let test_headers = req.headers();
+ assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type");
+ assert!(test_headers.get("x-custom").is_some(), "custom header");
+ assert!(test_headers.get("accept").is_none(), "no accept header");
+ }
+
+ #[wasm_bindgen_test]
+ async fn default_headers_clone() {
+ use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
+
+ let mut headers = HeaderMap::new();
+ headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
+ let client = crate::Client::builder()
+ .default_headers(headers)
+ .build()
+ .expect("client");
+
+ let mut req = client
+ .get("https://www.example.com")
+ .header(CONTENT_TYPE, "text/plain")
+ .build()
+ .expect("request");
+ client.merge_headers(&mut req);
+ let headers1 = req.headers();
+
+ // confirm that request headers override defaults
+ assert_eq!(
+ headers1.get(CONTENT_TYPE).unwrap(),
+ "text/plain",
+ "request headers override defaults"
+ );
+
+ // confirm that request headers don't change client defaults
+ let mut req2 = client
+ .get("https://www.example.com/x")
+ .build()
+ .expect("req 2");
+ client.merge_headers(&mut req2);
+ let headers2 = req2.headers();
+ assert_eq!(
+ headers2.get(CONTENT_TYPE).unwrap(),
+ "application/json",
+ "request headers don't change client defaults"
+ );
+ }
+}
diff --git a/vendor/reqwest/src/wasm/mod.rs b/vendor/reqwest/src/wasm/mod.rs
new file mode 100644
index 000000000..e99fb11fb
--- /dev/null
+++ b/vendor/reqwest/src/wasm/mod.rs
@@ -0,0 +1,53 @@
+use wasm_bindgen::JsCast;
+use web_sys::{AbortController, AbortSignal};
+
+mod body;
+mod client;
+/// TODO
+#[cfg(feature = "multipart")]
+pub mod multipart;
+mod request;
+mod response;
+
+pub use self::body::Body;
+pub use self::client::{Client, ClientBuilder};
+pub use self::request::{Request, RequestBuilder};
+pub use self::response::Response;
+
+async fn promise<T>(promise: js_sys::Promise) -> Result<T, crate::error::BoxError>
+where
+ T: JsCast,
+{
+ use wasm_bindgen_futures::JsFuture;
+
+ let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
+
+ js_val
+ .dyn_into::<T>()
+ .map_err(|_js_val| "promise resolved to unexpected type".into())
+}
+
+/// A guard that cancels a fetch request when dropped.
+struct AbortGuard {
+ ctrl: AbortController,
+}
+
+impl AbortGuard {
+ fn new() -> crate::Result<Self> {
+ Ok(AbortGuard {
+ ctrl: AbortController::new()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?,
+ })
+ }
+
+ fn signal(&self) -> AbortSignal {
+ self.ctrl.signal()
+ }
+}
+
+impl Drop for AbortGuard {
+ fn drop(&mut self) {
+ self.ctrl.abort();
+ }
+}
diff --git a/vendor/reqwest/src/wasm/multipart.rs b/vendor/reqwest/src/wasm/multipart.rs
new file mode 100644
index 000000000..909875c0a
--- /dev/null
+++ b/vendor/reqwest/src/wasm/multipart.rs
@@ -0,0 +1,360 @@
+//! multipart/form-data
+use std::borrow::Cow;
+use std::fmt;
+
+use http::HeaderMap;
+use mime_guess::Mime;
+use web_sys::FormData;
+
+use super::Body;
+
+/// An async multipart/form-data request.
+pub struct Form {
+ inner: FormParts<Part>,
+}
+
+impl Form {
+ pub(crate) fn is_empty(&self) -> bool {
+ self.inner.fields.is_empty()
+ }
+}
+
+/// A field in a multipart form.
+pub struct Part {
+ meta: PartMetadata,
+ value: Body,
+}
+
+pub(crate) struct FormParts<P> {
+ pub(crate) fields: Vec<(Cow<'static, str>, P)>,
+}
+
+pub(crate) struct PartMetadata {
+ mime: Option<Mime>,
+ file_name: Option<Cow<'static, str>>,
+ pub(crate) headers: HeaderMap,
+}
+
+pub(crate) trait PartProps {
+ fn metadata(&self) -> &PartMetadata;
+}
+
+// ===== impl Form =====
+
+impl Default for Form {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Form {
+ /// Creates a new async Form without any content.
+ pub fn new() -> Form {
+ Form {
+ inner: FormParts::new(),
+ }
+ }
+
+ /// Add a data field with supplied name and value.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// let form = reqwest::multipart::Form::new()
+ /// .text("username", "seanmonstar")
+ /// .text("password", "secret");
+ /// ```
+ pub fn text<T, U>(self, name: T, value: U) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ U: Into<Cow<'static, str>>,
+ {
+ self.part(name, Part::text(value))
+ }
+
+ /// Adds a customized Part.
+ pub fn part<T>(self, name: T, part: Part) -> Form
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.part(name, part))
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(FormParts<Part>) -> FormParts<Part>,
+ {
+ Form {
+ inner: func(self.inner),
+ }
+ }
+
+ pub(crate) fn to_form_data(&self) -> crate::Result<FormData> {
+ let form = FormData::new()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?;
+
+ for (name, part) in self.inner.fields.iter() {
+ let blob = part.blob()?;
+
+ if let Some(file_name) = &part.metadata().file_name {
+ form.append_with_blob_and_filename(name, &blob, &file_name)
+ } else {
+ form.append_with_blob(name, &blob)
+ }
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)?;
+ }
+ Ok(form)
+ }
+}
+
+impl fmt::Debug for Form {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.inner.fmt_fields("Form", f)
+ }
+}
+
+// ===== impl Part =====
+
+impl Part {
+ /// Makes a text parameter.
+ pub fn text<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(string) => Body::from(string),
+ };
+ Part::new(body)
+ }
+
+ /// Makes a new parameter from arbitrary bytes.
+ pub fn bytes<T>(value: T) -> Part
+ where
+ T: Into<Cow<'static, [u8]>>,
+ {
+ let body = match value.into() {
+ Cow::Borrowed(slice) => Body::from(slice),
+ Cow::Owned(vec) => Body::from(vec),
+ };
+ Part::new(body)
+ }
+
+ /// Makes a new parameter from an arbitrary stream.
+ pub fn stream<T: Into<Body>>(value: T) -> Part {
+ Part::new(value.into())
+ }
+
+ fn new(value: Body) -> Part {
+ Part {
+ meta: PartMetadata::new(),
+ value: value.into_part(),
+ }
+ }
+
+ /// Tries to set the mime of this part.
+ pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
+ Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
+ }
+
+ // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
+ fn mime(self, mime: Mime) -> Part {
+ self.with_inner(move |inner| inner.mime(mime))
+ }
+
+ /// Sets the filename, builder style.
+ pub fn file_name<T>(self, filename: T) -> Part
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.with_inner(move |inner| inner.file_name(filename))
+ }
+
+ fn with_inner<F>(self, func: F) -> Self
+ where
+ F: FnOnce(PartMetadata) -> PartMetadata,
+ {
+ Part {
+ meta: func(self.meta),
+ value: self.value,
+ }
+ }
+
+ fn blob(&self) -> crate::Result<web_sys::Blob> {
+ use web_sys::Blob;
+ use web_sys::BlobPropertyBag;
+ let mut properties = BlobPropertyBag::new();
+ if let Some(mime) = &self.meta.mime {
+ properties.type_(mime.as_ref());
+ }
+
+ // BUG: the return value of to_js_value() is not valid if
+ // it is a MultipartForm variant.
+ let js_value = self.value.to_js_value()?;
+ Blob::new_with_u8_array_sequence_and_options(&js_value, &properties)
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::builder)
+ }
+}
+
+impl fmt::Debug for Part {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut dbg = f.debug_struct("Part");
+ dbg.field("value", &self.value);
+ self.meta.fmt_fields(&mut dbg);
+ dbg.finish()
+ }
+}
+
+impl PartProps for Part {
+ fn metadata(&self) -> &PartMetadata {
+ &self.meta
+ }
+}
+
+// ===== impl FormParts =====
+
+impl<P: PartProps> FormParts<P> {
+ pub(crate) fn new() -> Self {
+ FormParts { fields: Vec::new() }
+ }
+
+ /// Adds a customized Part.
+ pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.fields.push((name.into(), part));
+ self
+ }
+}
+
+impl<P: fmt::Debug> FormParts<P> {
+ pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct(ty_name)
+ .field("parts", &self.fields)
+ .finish()
+ }
+}
+
+// ===== impl PartMetadata =====
+
+impl PartMetadata {
+ pub(crate) fn new() -> Self {
+ PartMetadata {
+ mime: None,
+ file_name: None,
+ headers: HeaderMap::default(),
+ }
+ }
+
+ pub(crate) fn mime(mut self, mime: Mime) -> Self {
+ self.mime = Some(mime);
+ self
+ }
+
+ pub(crate) fn file_name<T>(mut self, filename: T) -> Self
+ where
+ T: Into<Cow<'static, str>>,
+ {
+ self.file_name = Some(filename.into());
+ self
+ }
+}
+
+impl PartMetadata {
+ pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
+ &self,
+ debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
+ ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
+ debug_struct
+ .field("mime", &self.mime)
+ .field("file_name", &self.file_name)
+ .field("headers", &self.headers)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use wasm_bindgen_test::*;
+
+ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
+
+ #[wasm_bindgen_test]
+ async fn test_multipart_js() {
+ use super::{Form, Part};
+ use js_sys::Uint8Array;
+ use wasm_bindgen::JsValue;
+ use web_sys::{File, FormData};
+
+ let text_file_name = "test.txt";
+ let text_file_type = "text/plain";
+ let text_content = "TEST";
+ let text_part = Part::text(text_content)
+ .file_name(text_file_name)
+ .mime_str(text_file_type)
+ .expect("invalid mime type");
+
+ let binary_file_name = "binary.bin";
+ let binary_file_type = "application/octet-stream";
+ let binary_content = vec![0u8, 42];
+ let binary_part = Part::bytes(binary_content.clone())
+ .file_name(binary_file_name)
+ .mime_str(binary_file_type)
+ .expect("invalid mime type");
+
+ let text_name = "text part";
+ let binary_name = "binary part";
+ let form = Form::new()
+ .part(text_name, text_part)
+ .part(binary_name, binary_part);
+
+ let mut init = web_sys::RequestInit::new();
+ init.method("POST");
+ init.body(Some(
+ form.to_form_data()
+ .expect("could not convert to FormData")
+ .as_ref(),
+ ));
+
+ let js_req = web_sys::Request::new_with_str_and_init("", &init)
+ .expect("could not create JS request");
+
+ let form_data_promise = js_req.form_data().expect("could not get form_data promise");
+
+ let form_data = crate::wasm::promise::<FormData>(form_data_promise)
+ .await
+ .expect("could not get body as form data");
+
+ // check text part
+ let text_file = File::from(form_data.get(text_name));
+ assert_eq!(text_file.name(), text_file_name);
+ assert_eq!(text_file.type_(), text_file_type);
+
+ let text_promise = text_file.text();
+ let text = crate::wasm::promise::<JsValue>(text_promise)
+ .await
+ .expect("could not get text body as text");
+ assert_eq!(
+ text.as_string().expect("text is not a string"),
+ text_content
+ );
+
+ // check binary part
+ let binary_file = File::from(form_data.get(binary_name));
+ assert_eq!(binary_file.name(), binary_file_name);
+ assert_eq!(binary_file.type_(), binary_file_type);
+
+ let binary_array_buffer_promise = binary_file.array_buffer();
+ let array_buffer = crate::wasm::promise::<JsValue>(binary_array_buffer_promise)
+ .await
+ .expect("could not get request body as array buffer");
+
+ let binary = Uint8Array::new(&array_buffer).to_vec();
+
+ assert_eq!(binary, binary_content);
+ }
+}
diff --git a/vendor/reqwest/src/wasm/request.rs b/vendor/reqwest/src/wasm/request.rs
new file mode 100644
index 000000000..2b0a6bec7
--- /dev/null
+++ b/vendor/reqwest/src/wasm/request.rs
@@ -0,0 +1,479 @@
+use std::convert::TryFrom;
+use std::fmt;
+
+use bytes::Bytes;
+use http::{request::Parts, Method, Request as HttpRequest};
+use serde::Serialize;
+#[cfg(feature = "json")]
+use serde_json;
+use url::Url;
+use web_sys::RequestCredentials;
+
+use super::{Body, Client, Response};
+use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
+
+/// A request which can be executed with `Client::execute()`.
+pub struct Request {
+ method: Method,
+ url: Url,
+ headers: HeaderMap,
+ body: Option<Body>,
+ pub(super) cors: bool,
+ pub(super) credentials: Option<RequestCredentials>,
+}
+
+/// A builder to construct the properties of a `Request`.
+pub struct RequestBuilder {
+ client: Client,
+ request: crate::Result<Request>,
+}
+
+impl Request {
+ /// Constructs a new request.
+ #[inline]
+ pub fn new(method: Method, url: Url) -> Self {
+ Request {
+ method,
+ url,
+ headers: HeaderMap::new(),
+ body: None,
+ cors: true,
+ credentials: None,
+ }
+ }
+
+ /// Get the method.
+ #[inline]
+ pub fn method(&self) -> &Method {
+ &self.method
+ }
+
+ /// Get a mutable reference to the method.
+ #[inline]
+ pub fn method_mut(&mut self) -> &mut Method {
+ &mut self.method
+ }
+
+ /// Get the url.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /// Get a mutable reference to the url.
+ #[inline]
+ pub fn url_mut(&mut self) -> &mut Url {
+ &mut self.url
+ }
+
+ /// Get the headers.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ &self.headers
+ }
+
+ /// Get a mutable reference to the headers.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ &mut self.headers
+ }
+
+ /// Get the body.
+ #[inline]
+ pub fn body(&self) -> Option<&Body> {
+ self.body.as_ref()
+ }
+
+ /// Get a mutable reference to the body.
+ #[inline]
+ pub fn body_mut(&mut self) -> &mut Option<Body> {
+ &mut self.body
+ }
+
+ /// Attempts to clone the `Request`.
+ ///
+ /// None is returned if a body is which can not be cloned.
+ pub fn try_clone(&self) -> Option<Request> {
+ let body = match self.body.as_ref() {
+ Some(body) => Some(body.try_clone()?),
+ None => None,
+ };
+
+ Some(Self {
+ method: self.method.clone(),
+ url: self.url.clone(),
+ headers: self.headers.clone(),
+ body,
+ cors: self.cors,
+ credentials: self.credentials,
+ })
+ }
+}
+
+impl RequestBuilder {
+ pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
+ RequestBuilder { client, request }
+ }
+
+ /// Modify the query string of the URL.
+ ///
+ /// Modifies the URL of this request, adding the parameters provided.
+ /// This method appends and does not overwrite. This means that it can
+ /// be called multiple times and that existing query parameters are not
+ /// overwritten if the same key is used. The key will simply show up
+ /// twice in the query string.
+ /// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
+ ///
+ /// # Note
+ /// This method does not support serializing a single key-value
+ /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
+ /// as `.query(&[("key", "val")])`. It's also possible to serialize structs
+ /// and maps into a key-value pair.
+ ///
+ /// # Errors
+ /// This method will fail if the object you provide cannot be serialized
+ /// into a query string.
+ pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ let url = req.url_mut();
+ let mut pairs = url.query_pairs_mut();
+ let serializer = serde_urlencoded::Serializer::new(&mut pairs);
+
+ if let Err(err) = query.serialize(serializer) {
+ error = Some(crate::error::builder(err));
+ }
+ }
+ if let Ok(ref mut req) = self.request {
+ if let Some("") = req.url().query() {
+ req.url_mut().set_query(None);
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Send a form body.
+ ///
+ /// Sets the body to the url encoded serialization of the passed value,
+ /// and also sets the `Content-Type: application/x-www-form-urlencoded`
+ /// header.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if the passed value cannot be serialized into
+ /// url encoded format
+ pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_urlencoded::to_string(form) {
+ Ok(body) => {
+ req.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("application/x-www-form-urlencoded"),
+ );
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ /// Set the request json
+ pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match serde_json::to_vec(json) {
+ Ok(body) => {
+ req.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
+ *req.body_mut() = Some(body.into());
+ }
+ Err(err) => error = Some(crate::error::builder(err)),
+ }
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Enable HTTP basic authentication.
+ pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
+ where
+ U: fmt::Display,
+ P: fmt::Display,
+ {
+ let header_value = crate::util::basic_auth(username, password);
+ self.header(crate::header::AUTHORIZATION, header_value)
+ }
+
+ /// Enable HTTP bearer authentication.
+ pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
+ where
+ T: fmt::Display,
+ {
+ let header_value = format!("Bearer {}", token);
+ self.header(crate::header::AUTHORIZATION, header_value)
+ }
+
+ /// Set the request body.
+ pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.body = Some(body.into());
+ }
+ self
+ }
+
+ /// TODO
+ #[cfg(feature = "multipart")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
+ pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ *req.body_mut() = Some(Body::from_form(multipart))
+ }
+ self
+ }
+
+ /// Add a `Header` to this Request.
+ pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
+ where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+ {
+ let mut error = None;
+ if let Ok(ref mut req) = self.request {
+ match <HeaderName as TryFrom<K>>::try_from(key) {
+ Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(value) => {
+ req.headers_mut().append(key, value);
+ }
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ },
+ Err(e) => error = Some(crate::error::builder(e.into())),
+ };
+ }
+ if let Some(err) = error {
+ self.request = Err(err);
+ }
+ self
+ }
+
+ /// Add a set of Headers to the existing ones on this Request.
+ ///
+ /// The headers will be merged in to any already set.
+ pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ crate::util::replace_headers(req.headers_mut(), headers);
+ }
+ self
+ }
+
+ /// Disable CORS on fetching the request.
+ ///
+ /// # WASM
+ ///
+ /// This option is only effective with WebAssembly target.
+ ///
+ /// The [request mode][mdn] will be set to 'no-cors'.
+ ///
+ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
+ pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.cors = false;
+ }
+ self
+ }
+
+ /// Set fetch credentials to 'same-origin'
+ ///
+ /// # WASM
+ ///
+ /// This option is only effective with WebAssembly target.
+ ///
+ /// The [request credentials][mdn] will be set to 'same-origin'.
+ ///
+ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
+ pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.credentials = Some(RequestCredentials::SameOrigin);
+ }
+ self
+ }
+
+ /// Set fetch credentials to 'include'
+ ///
+ /// # WASM
+ ///
+ /// This option is only effective with WebAssembly target.
+ ///
+ /// The [request credentials][mdn] will be set to 'include'.
+ ///
+ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
+ pub fn fetch_credentials_include(mut self) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.credentials = Some(RequestCredentials::Include);
+ }
+ self
+ }
+
+ /// Set fetch credentials to 'omit'
+ ///
+ /// # WASM
+ ///
+ /// This option is only effective with WebAssembly target.
+ ///
+ /// The [request credentials][mdn] will be set to 'omit'.
+ ///
+ /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
+ pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
+ if let Ok(ref mut req) = self.request {
+ req.credentials = Some(RequestCredentials::Omit);
+ }
+ self
+ }
+
+ /// Build a `Request`, which can be inspected, modified and executed with
+ /// `Client::execute()`.
+ pub fn build(self) -> crate::Result<Request> {
+ self.request
+ }
+
+ /// Constructs the Request and sends it to the target URL, returning a
+ /// future Response.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if there was an error while sending request.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// # use reqwest::Error;
+ /// #
+ /// # async fn run() -> Result<(), Error> {
+ /// let response = reqwest::Client::new()
+ /// .get("https://hyper.rs")
+ /// .send()
+ /// .await?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub async fn send(self) -> crate::Result<Response> {
+ let req = self.request?;
+ self.client.execute_request(req).await
+ }
+
+ /// Attempt to clone the RequestBuilder.
+ ///
+ /// `None` is returned if the RequestBuilder can not be cloned.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # use reqwest::Error;
+ /// #
+ /// # fn run() -> Result<(), Error> {
+ /// let client = reqwest::Client::new();
+ /// let builder = client.post("http://httpbin.org/post")
+ /// .body("from a &str!");
+ /// let clone = builder.try_clone();
+ /// assert!(clone.is_some());
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn try_clone(&self) -> Option<RequestBuilder> {
+ self.request
+ .as_ref()
+ .ok()
+ .and_then(|req| req.try_clone())
+ .map(|req| RequestBuilder {
+ client: self.client.clone(),
+ request: Ok(req),
+ })
+ }
+}
+
+impl fmt::Debug for Request {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
+ }
+}
+
+impl fmt::Debug for RequestBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("RequestBuilder");
+ match self.request {
+ Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
+ Err(ref err) => builder.field("error", err).finish(),
+ }
+ }
+}
+
+fn fmt_request_fields<'a, 'b>(
+ f: &'a mut fmt::DebugStruct<'a, 'b>,
+ req: &Request,
+) -> &'a mut fmt::DebugStruct<'a, 'b> {
+ f.field("method", &req.method)
+ .field("url", &req.url)
+ .field("headers", &req.headers)
+}
+
+impl<T> TryFrom<HttpRequest<T>> for Request
+where
+ T: Into<Body>,
+{
+ type Error = crate::Error;
+
+ fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
+ let (parts, body) = req.into_parts();
+ let Parts {
+ method,
+ uri,
+ headers,
+ ..
+ } = parts;
+ let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
+ Ok(Request {
+ method,
+ url,
+ headers,
+ body: Some(body.into()),
+ cors: true,
+ credentials: None,
+ })
+ }
+}
+
+impl TryFrom<Request> for HttpRequest<Body> {
+ type Error = crate::Error;
+
+ fn try_from(req: Request) -> crate::Result<Self> {
+ let Request {
+ method,
+ url,
+ headers,
+ body,
+ ..
+ } = req;
+
+ let mut req = HttpRequest::builder()
+ .method(method)
+ .uri(url.as_str())
+ .body(body.unwrap_or_else(|| Body::from(Bytes::default())))
+ .map_err(crate::error::builder)?;
+
+ *req.headers_mut() = headers;
+ Ok(req)
+ }
+}
diff --git a/vendor/reqwest/src/wasm/response.rs b/vendor/reqwest/src/wasm/response.rs
new file mode 100644
index 000000000..47a90d04d
--- /dev/null
+++ b/vendor/reqwest/src/wasm/response.rs
@@ -0,0 +1,189 @@
+use std::fmt;
+
+use bytes::Bytes;
+use http::{HeaderMap, StatusCode};
+use js_sys::Uint8Array;
+use url::Url;
+
+use crate::wasm::AbortGuard;
+
+#[cfg(feature = "stream")]
+use wasm_bindgen::JsCast;
+
+#[cfg(feature = "stream")]
+use futures_util::stream::StreamExt;
+
+#[cfg(feature = "json")]
+use serde::de::DeserializeOwned;
+
+/// A Response to a submitted `Request`.
+pub struct Response {
+ http: http::Response<web_sys::Response>,
+ _abort: AbortGuard,
+ // Boxed to save space (11 words to 1 word), and it's not accessed
+ // frequently internally.
+ url: Box<Url>,
+}
+
+impl Response {
+ pub(super) fn new(
+ res: http::Response<web_sys::Response>,
+ url: Url,
+ abort: AbortGuard,
+ ) -> Response {
+ Response {
+ http: res,
+ url: Box::new(url),
+ _abort: abort,
+ }
+ }
+
+ /// Get the `StatusCode` of this `Response`.
+ #[inline]
+ pub fn status(&self) -> StatusCode {
+ self.http.status()
+ }
+
+ /// Get the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers(&self) -> &HeaderMap {
+ self.http.headers()
+ }
+
+ /// Get a mutable reference to the `Headers` of this `Response`.
+ #[inline]
+ pub fn headers_mut(&mut self) -> &mut HeaderMap {
+ self.http.headers_mut()
+ }
+
+ /// Get the content-length of this response, if known.
+ ///
+ /// Reasons it may not be known:
+ ///
+ /// - The server didn't send a `content-length` header.
+ /// - The response is compressed and automatically decoded (thus changing
+ /// the actual decoded length).
+ pub fn content_length(&self) -> Option<u64> {
+ self.headers()
+ .get(http::header::CONTENT_LENGTH)?
+ .to_str()
+ .ok()?
+ .parse()
+ .ok()
+ }
+
+ /// Get the final `Url` of this `Response`.
+ #[inline]
+ pub fn url(&self) -> &Url {
+ &self.url
+ }
+
+ /* It might not be possible to detect this in JS?
+ /// Get the HTTP `Version` of this `Response`.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.http.version()
+ }
+ */
+
+ /// Try to deserialize the response body as JSON.
+ #[cfg(feature = "json")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
+ pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
+ let full = self.bytes().await?;
+
+ serde_json::from_slice(&full).map_err(crate::error::decode)
+ }
+
+ /// Get the response text.
+ pub async fn text(self) -> crate::Result<String> {
+ let p = self
+ .http
+ .body()
+ .text()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::decode)?;
+ let js_val = super::promise::<wasm_bindgen::JsValue>(p)
+ .await
+ .map_err(crate::error::decode)?;
+ if let Some(s) = js_val.as_string() {
+ Ok(s)
+ } else {
+ Err(crate::error::decode("response.text isn't string"))
+ }
+ }
+
+ /// Get the response as bytes
+ pub async fn bytes(self) -> crate::Result<Bytes> {
+ let p = self
+ .http
+ .body()
+ .array_buffer()
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::decode)?;
+
+ let buf_js = super::promise::<wasm_bindgen::JsValue>(p)
+ .await
+ .map_err(crate::error::decode)?;
+
+ let buffer = Uint8Array::new(&buf_js);
+ let mut bytes = vec![0; buffer.length() as usize];
+ buffer.copy_to(&mut bytes);
+ Ok(bytes.into())
+ }
+
+ /// Convert the response into a `Stream` of `Bytes` from the body.
+ #[cfg(feature = "stream")]
+ pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
+ let web_response = self.http.into_body();
+ let abort = self._abort;
+ let body = web_response
+ .body()
+ .expect("could not create wasm byte stream");
+ let body = wasm_streams::ReadableStream::from_raw(body.unchecked_into());
+ Box::pin(body.into_stream().map(move |buf_js| {
+ // Keep the abort guard alive as long as this stream is.
+ let _abort = &abort;
+ let buffer = Uint8Array::new(
+ &buf_js
+ .map_err(crate::error::wasm)
+ .map_err(crate::error::decode)?,
+ );
+ let mut bytes = vec![0; buffer.length() as usize];
+ buffer.copy_to(&mut bytes);
+ Ok(bytes.into())
+ }))
+ }
+
+ // util methods
+
+ /// Turn a response into an error if the server returned an error.
+ pub fn error_for_status(self) -> crate::Result<Self> {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url, status))
+ } else {
+ Ok(self)
+ }
+ }
+
+ /// Turn a reference to a response into an error if the server returned an error.
+ pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
+ let status = self.status();
+ if status.is_client_error() || status.is_server_error() {
+ Err(crate::error::status_code(*self.url.clone(), status))
+ } else {
+ Ok(self)
+ }
+ }
+}
+
+impl fmt::Debug for Response {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Response")
+ //.field("url", self.url())
+ .field("status", &self.status())
+ .field("headers", self.headers())
+ .finish()
+ }
+}