committed by
					
						 Sean McArthur
						Sean McArthur
					
				
			
			
				
	
			
			
			
						parent
						
							81e0f1ff2a
						
					
				
				
					commit
					cf8944a0f0
				
			| @@ -1,7 +1,7 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use futures::{Future, Stream, Poll, Async, try_ready}; | ||||
| use bytes::{Buf, Bytes}; | ||||
| use futures::{try_ready, Async, Future, Poll, Stream}; | ||||
| use hyper::body::Payload; | ||||
| use tokio::timer::Delay; | ||||
|  | ||||
| @@ -15,7 +15,7 @@ enum Inner { | ||||
|     Hyper { | ||||
|         body: hyper::Body, | ||||
|         timeout: Option<Delay>, | ||||
|     } | ||||
|     }, | ||||
| } | ||||
|  | ||||
| impl Body { | ||||
| @@ -29,10 +29,7 @@ impl Body { | ||||
|     #[inline] | ||||
|     pub(crate) fn response(body: hyper::Body, timeout: Option<Delay>) -> Body { | ||||
|         Body { | ||||
|             inner: Inner::Hyper { | ||||
|                 body, | ||||
|                 timeout, | ||||
|             }, | ||||
|             inner: Inner::Hyper { body, timeout }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -65,7 +62,7 @@ impl Body { | ||||
|             Inner::Hyper { body, timeout } => { | ||||
|                 debug_assert!(timeout.is_none()); | ||||
|                 (None, body) | ||||
|             }, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -77,14 +74,17 @@ impl Stream for Body { | ||||
|     #[inline] | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         let opt = match self.inner { | ||||
|             Inner::Hyper { ref mut body, ref mut timeout } => { | ||||
|             Inner::Hyper { | ||||
|                 ref mut body, | ||||
|                 ref mut timeout, | ||||
|             } => { | ||||
|                 if let Some(ref mut timeout) = timeout { | ||||
|                     if let Async::Ready(()) = try_!(timeout.poll()) { | ||||
|                         return Err(crate::error::timedout(None)); | ||||
|                     } | ||||
|                 } | ||||
|                 try_ready!(body.poll_data().map_err(crate::error::from)) | ||||
|             }, | ||||
|             } | ||||
|             Inner::Reusable(ref mut bytes) => { | ||||
|                 return if bytes.is_empty() { | ||||
|                     Ok(Async::Ready(None)) | ||||
| @@ -93,12 +93,10 @@ impl Stream for Body { | ||||
|                     *bytes = Bytes::new(); | ||||
|                     Ok(Async::Ready(Some(chunk))) | ||||
|                 }; | ||||
|             }, | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         Ok(Async::Ready(opt.map(|chunk| Chunk { | ||||
|             inner: chunk, | ||||
|         }))) | ||||
|         Ok(Async::Ready(opt.map(|chunk| Chunk { inner: chunk }))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -161,7 +159,7 @@ impl Chunk { | ||||
|     #[inline] | ||||
|     pub(crate) fn from_chunk(chunk: Bytes) -> Chunk { | ||||
|         Chunk { | ||||
|             inner: hyper::Chunk::from(chunk) | ||||
|             inner: hyper::Chunk::from(chunk), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -197,7 +195,9 @@ impl std::ops::Deref for Chunk { | ||||
|  | ||||
| impl Extend<u8> for Chunk { | ||||
|     fn extend<T>(&mut self, iter: T) | ||||
|     where T: IntoIterator<Item=u8> { | ||||
|     where | ||||
|         T: IntoIterator<Item = u8>, | ||||
|     { | ||||
|         self.inner.extend(iter) | ||||
|     } | ||||
| } | ||||
| @@ -219,7 +219,9 @@ impl From<Vec<u8>> for Chunk { | ||||
|  | ||||
| impl From<&'static [u8]> for Chunk { | ||||
|     fn from(slice: &'static [u8]) -> Chunk { | ||||
|         Chunk { inner: slice.into() } | ||||
|         Chunk { | ||||
|             inner: slice.into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -231,13 +233,17 @@ impl From<String> for Chunk { | ||||
|  | ||||
| impl From<&'static str> for Chunk { | ||||
|     fn from(slice: &'static str) -> Chunk { | ||||
|         Chunk { inner: slice.into() } | ||||
|         Chunk { | ||||
|             inner: slice.into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Bytes> for Chunk { | ||||
|     fn from(bytes: Bytes) -> Chunk { | ||||
|         Chunk { inner: bytes.into() } | ||||
|         Chunk { | ||||
|             inner: bytes.into(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -249,8 +255,7 @@ impl From<Chunk> for hyper::Chunk { | ||||
|  | ||||
| impl fmt::Debug for Body { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Body") | ||||
|             .finish() | ||||
|         f.debug_struct("Body").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,26 +1,14 @@ | ||||
| use std::{fmt, str}; | ||||
| use std::net::IpAddr; | ||||
| use std::sync::{Arc, RwLock}; | ||||
| use std::time::Duration; | ||||
| use std::net::IpAddr; | ||||
| use std::{fmt, str}; | ||||
|  | ||||
| use crate::header::{ | ||||
|     Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, | ||||
|     CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT, | ||||
| }; | ||||
| use bytes::Bytes; | ||||
| use futures::{Async, Future, Poll}; | ||||
| use crate::header::{ | ||||
|     Entry, | ||||
|     HeaderMap, | ||||
|     HeaderValue, | ||||
|     ACCEPT, | ||||
|     ACCEPT_ENCODING, | ||||
|     CONTENT_LENGTH, | ||||
|     CONTENT_ENCODING, | ||||
|     CONTENT_TYPE, | ||||
|     LOCATION, | ||||
|     PROXY_AUTHORIZATION, | ||||
|     RANGE, | ||||
|     REFERER, | ||||
|     TRANSFER_ENCODING, | ||||
|     USER_AGENT, | ||||
| }; | ||||
| use http::Uri; | ||||
| use hyper::client::ResponseFuture; | ||||
| use mime; | ||||
| @@ -28,24 +16,22 @@ use mime; | ||||
| use native_tls::TlsConnector; | ||||
| use tokio::{clock, timer::Delay}; | ||||
|  | ||||
| use log::{debug}; | ||||
|  | ||||
| use log::debug; | ||||
|  | ||||
| use super::request::{Request, RequestBuilder}; | ||||
| use super::response::Response; | ||||
| use crate::connect::Connector; | ||||
| use crate::into_url::{expect_uri, try_uri}; | ||||
| use crate::cookie; | ||||
| use crate::redirect::{self, RedirectPolicy, remove_sensitive_headers}; | ||||
| use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; | ||||
| use crate::into_url::{expect_uri, try_uri}; | ||||
| use crate::proxy::get_proxies; | ||||
| #[cfg(feature = "tls")] | ||||
| use crate::{Certificate, Identity}; | ||||
| use crate::redirect::{self, remove_sensitive_headers, RedirectPolicy}; | ||||
| #[cfg(feature = "tls")] | ||||
| use crate::tls::TlsBackend; | ||||
| #[cfg(feature = "tls")] | ||||
| use crate::{Certificate, Identity}; | ||||
| use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; | ||||
|  | ||||
| static DEFAULT_USER_AGENT: &str = | ||||
|     concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); | ||||
| static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); | ||||
|  | ||||
| /// An asynchronous `Client` to make Requests with. | ||||
| /// | ||||
| @@ -98,7 +84,10 @@ impl ClientBuilder { | ||||
|     pub fn new() -> ClientBuilder { | ||||
|         let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2); | ||||
|         headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_USER_AGENT)); | ||||
|         headers.insert(ACCEPT, HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime")); | ||||
|         headers.insert( | ||||
|             ACCEPT, | ||||
|             HeaderValue::from_str(mime::STAR_STAR.as_ref()).expect("unable to parse mime"), | ||||
|         ); | ||||
|  | ||||
|         ClientBuilder { | ||||
|             config: Config { | ||||
| @@ -156,8 +145,13 @@ impl ClientBuilder { | ||||
|                         id.add_to_native_tls(&mut tls)?; | ||||
|                     } | ||||
|  | ||||
|                     Connector::new_default_tls(tls, proxies.clone(), config.local_address, config.nodelay)? | ||||
|                 }, | ||||
|                     Connector::new_default_tls( | ||||
|                         tls, | ||||
|                         proxies.clone(), | ||||
|                         config.local_address, | ||||
|                         config.nodelay, | ||||
|                     )? | ||||
|                 } | ||||
|                 #[cfg(feature = "rustls-tls")] | ||||
|                 TlsBackend::Rustls => { | ||||
|                     use crate::tls::NoVerifier; | ||||
| @@ -166,15 +160,14 @@ impl ClientBuilder { | ||||
|                     if config.http2_only { | ||||
|                         tls.set_protocols(&["h2".into()]); | ||||
|                     } else { | ||||
|                         tls.set_protocols(&[ | ||||
|                             "h2".into(), | ||||
|                             "http/1.1".into(), | ||||
|                         ]); | ||||
|                         tls.set_protocols(&["h2".into(), "http/1.1".into()]); | ||||
|                     } | ||||
|                     tls.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); | ||||
|                     tls.root_store | ||||
|                         .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); | ||||
|  | ||||
|                     if !config.certs_verification { | ||||
|                         tls.dangerous().set_certificate_verifier(Arc::new(NoVerifier)); | ||||
|                         tls.dangerous() | ||||
|                             .set_certificate_verifier(Arc::new(NoVerifier)); | ||||
|                     } | ||||
|  | ||||
|                     for cert in config.root_certs { | ||||
| @@ -185,7 +178,12 @@ impl ClientBuilder { | ||||
|                         id.add_to_rustls(&mut tls)?; | ||||
|                     } | ||||
|  | ||||
|                     Connector::new_rustls_tls(tls, proxies.clone(), config.local_address, config.nodelay)? | ||||
|                     Connector::new_rustls_tls( | ||||
|                         tls, | ||||
|                         proxies.clone(), | ||||
|                         config.local_address, | ||||
|                         config.nodelay, | ||||
|                     )? | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -208,9 +206,7 @@ impl ClientBuilder { | ||||
|  | ||||
|         let hyper_client = builder.build(connector); | ||||
|  | ||||
|         let proxies_maybe_http_auth = proxies | ||||
|             .iter() | ||||
|             .any(|p| p.maybe_has_http_auth()); | ||||
|         let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth()); | ||||
|  | ||||
|         let cookie_store = config.cookie_store.map(RwLock::new); | ||||
|  | ||||
| @@ -277,7 +273,10 @@ impl ClientBuilder { | ||||
|     /// site will be trusted for use from any other. This introduces a | ||||
|     /// significant vulnerability to man-in-the-middle attacks. | ||||
|     #[cfg(feature = "default-tls")] | ||||
|     pub fn danger_accept_invalid_hostnames(mut self, accept_invalid_hostname: bool) -> ClientBuilder { | ||||
|     pub fn danger_accept_invalid_hostnames( | ||||
|         mut self, | ||||
|         accept_invalid_hostname: bool, | ||||
|     ) -> ClientBuilder { | ||||
|         self.config.hostname_verification = !accept_invalid_hostname; | ||||
|         self | ||||
|     } | ||||
| @@ -299,7 +298,6 @@ impl ClientBuilder { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Sets the default headers for every request. | ||||
|     pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder { | ||||
|         for (key, value) in headers.iter() { | ||||
| @@ -349,7 +347,6 @@ impl ClientBuilder { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Set a `RedirectPolicy` for this client. | ||||
|     /// | ||||
|     /// Default will follow redirects up to a maximum of 10. | ||||
| @@ -454,9 +451,7 @@ impl Client { | ||||
|     /// Use `Client::builder()` if you wish to handle the failure as an `Error` | ||||
|     /// instead of panicking. | ||||
|     pub fn new() -> Client { | ||||
|         ClientBuilder::new() | ||||
|             .build() | ||||
|             .expect("Client::new()") | ||||
|         ClientBuilder::new().build().expect("Client::new()") | ||||
|     } | ||||
|  | ||||
|     /// Creates a `ClientBuilder` to configure a `Client`. | ||||
| @@ -529,9 +524,7 @@ impl Client { | ||||
|     /// | ||||
|     /// This method fails whenever supplied `Url` cannot be parsed. | ||||
|     pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { | ||||
|         let req = url | ||||
|             .into_url() | ||||
|             .map(move |url| Request::new(method, url)); | ||||
|         let req = url.into_url().map(move |url| Request::new(method, url)); | ||||
|         RequestBuilder::new(self.clone(), req) | ||||
|     } | ||||
|  | ||||
| @@ -551,14 +544,8 @@ impl Client { | ||||
|         self.execute_request(request) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     pub(super) fn execute_request(&self, req: Request) -> Pending { | ||||
|         let ( | ||||
|             method, | ||||
|             url, | ||||
|             mut headers, | ||||
|             body | ||||
|         ) = req.pieces(); | ||||
|         let (method, url, mut headers, body) = req.pieces(); | ||||
|  | ||||
|         // insert default headers in the request headers | ||||
|         // without overwriting already appended headers. | ||||
| @@ -576,9 +563,8 @@ impl Client { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if self.inner.gzip && | ||||
|             !headers.contains_key(ACCEPT_ENCODING) && | ||||
|             !headers.contains_key(RANGE) { | ||||
|         if self.inner.gzip && !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) | ||||
|         { | ||||
|             headers.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip")); | ||||
|         } | ||||
|  | ||||
| @@ -588,10 +574,8 @@ impl Client { | ||||
|             Some(body) => { | ||||
|                 let (reusable, body) = body.into_hyper(); | ||||
|                 (Some(reusable), body) | ||||
|             }, | ||||
|             None => { | ||||
|                 (None, hyper::Body::empty()) | ||||
|             } | ||||
|             None => (None, hyper::Body::empty()), | ||||
|         }; | ||||
|  | ||||
|         self.proxy_auth(&uri, &mut headers); | ||||
| @@ -606,9 +590,10 @@ impl Client { | ||||
|  | ||||
|         let in_flight = self.inner.hyper.request(req); | ||||
|  | ||||
|         let timeout = self.inner.request_timeout.map(|dur| { | ||||
|             Delay::new(clock::now() + dur) | ||||
|         }); | ||||
|         let timeout = self | ||||
|             .inner | ||||
|             .request_timeout | ||||
|             .map(|dur| Delay::new(clock::now() + dur)); | ||||
|  | ||||
|         Pending { | ||||
|             inner: PendingInner::Request(PendingRequest { | ||||
| @@ -643,14 +628,10 @@ impl Client { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         for proxy in self.inner.proxies.iter() { | ||||
|             if proxy.is_match(dst) { | ||||
|                 if let Some(header) = proxy.http_basic_auth(dst) { | ||||
|                     headers.insert( | ||||
|                         PROXY_AUTHORIZATION, | ||||
|                         header, | ||||
|                     ); | ||||
|                     headers.insert(PROXY_AUTHORIZATION, header); | ||||
|                 } | ||||
|  | ||||
|                 break; | ||||
| @@ -671,8 +652,7 @@ impl fmt::Debug for Client { | ||||
|  | ||||
| impl fmt::Debug for ClientBuilder { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("ClientBuilder") | ||||
|             .finish() | ||||
|         f.debug_struct("ClientBuilder").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -726,7 +706,9 @@ impl Future for Pending { | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         match self.inner { | ||||
|             PendingInner::Request(ref mut req) => req.poll(), | ||||
|             PendingInner::Error(ref mut err) => Err(err.take().expect("Pending error polled more than once")), | ||||
|             PendingInner::Error(ref mut err) => { | ||||
|                 Err(err.take().expect("Pending error polled more than once")) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -755,56 +737,58 @@ impl Future for PendingRequest { | ||||
|                 store.0.store_response_cookies(cookies, &self.url); | ||||
|             } | ||||
|             let should_redirect = match res.status() { | ||||
|                 StatusCode::MOVED_PERMANENTLY | | ||||
|                 StatusCode::FOUND | | ||||
|                 StatusCode::SEE_OTHER => { | ||||
|                 StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { | ||||
|                     self.body = None; | ||||
|                     for header in &[TRANSFER_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, CONTENT_LENGTH] { | ||||
|                     for header in &[ | ||||
|                         TRANSFER_ENCODING, | ||||
|                         CONTENT_ENCODING, | ||||
|                         CONTENT_TYPE, | ||||
|                         CONTENT_LENGTH, | ||||
|                     ] { | ||||
|                         self.headers.remove(header); | ||||
|                     } | ||||
|  | ||||
|                     match self.method { | ||||
|                         Method::GET | Method::HEAD => {}, | ||||
|                         Method::GET | Method::HEAD => {} | ||||
|                         _ => { | ||||
|                             self.method = Method::GET; | ||||
|                         } | ||||
|                     } | ||||
|                     true | ||||
|                 }, | ||||
|                 StatusCode::TEMPORARY_REDIRECT | | ||||
|                 StatusCode::PERMANENT_REDIRECT => match self.body { | ||||
|                     Some(Some(_)) | None => true, | ||||
|                     Some(None) => false, | ||||
|                 }, | ||||
|                 } | ||||
|                 StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => { | ||||
|                     match self.body { | ||||
|                         Some(Some(_)) | None => true, | ||||
|                         Some(None) => false, | ||||
|                     } | ||||
|                 } | ||||
|                 _ => false, | ||||
|             }; | ||||
|             if should_redirect { | ||||
|                 let loc = res.headers() | ||||
|                     .get(LOCATION) | ||||
|                     .and_then(|val| { | ||||
|                         let loc = (|| -> Option<Url> { | ||||
|                             // Some sites may send a utf-8 Location header, | ||||
|                             // even though we're supposed to treat those bytes | ||||
|                             // as opaque, we'll check specifically for utf8. | ||||
|                             self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok() | ||||
|                         })(); | ||||
|                 let loc = res.headers().get(LOCATION).and_then(|val| { | ||||
|                     let loc = (|| -> Option<Url> { | ||||
|                         // Some sites may send a utf-8 Location header, | ||||
|                         // even though we're supposed to treat those bytes | ||||
|                         // as opaque, we'll check specifically for utf8. | ||||
|                         self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok() | ||||
|                     })(); | ||||
|  | ||||
|                         // Check that the `url` is also a valid `http::Uri`. | ||||
|                         // | ||||
|                         // If not, just log it and skip the redirect. | ||||
|                         let loc = loc.and_then(|url| { | ||||
|                             if try_uri(&url).is_some() { | ||||
|                                 Some(url) | ||||
|                             } else { | ||||
|                                 None | ||||
|                             } | ||||
|                         }); | ||||
|  | ||||
|                         if loc.is_none() { | ||||
|                             debug!("Location header had invalid URI: {:?}", val); | ||||
|                     // Check that the `url` is also a valid `http::Uri`. | ||||
|                     // | ||||
|                     // If not, just log it and skip the redirect. | ||||
|                     let loc = loc.and_then(|url| { | ||||
|                         if try_uri(&url).is_some() { | ||||
|                             Some(url) | ||||
|                         } else { | ||||
|                             None | ||||
|                         } | ||||
|                         loc | ||||
|                     }); | ||||
|  | ||||
|                     if loc.is_none() { | ||||
|                         debug!("Location header had invalid URI: {:?}", val); | ||||
|                     } | ||||
|                     loc | ||||
|                 }); | ||||
|                 if let Some(loc) = loc { | ||||
|                     if self.client.referer { | ||||
|                         if let Some(referer) = make_referer(&loc, &self.url) { | ||||
| @@ -812,11 +796,10 @@ impl Future for PendingRequest { | ||||
|                         } | ||||
|                     } | ||||
|                     self.urls.push(self.url.clone()); | ||||
|                     let action = self.client.redirect_policy.check( | ||||
|                         res.status(), | ||||
|                         &loc, | ||||
|                         &self.urls, | ||||
|                     ); | ||||
|                     let action = self | ||||
|                         .client | ||||
|                         .redirect_policy | ||||
|                         .check(res.status(), &loc, &self.urls); | ||||
|  | ||||
|                     match action { | ||||
|                         redirect::Action::Follow => { | ||||
| @@ -844,13 +827,13 @@ impl Future for PendingRequest { | ||||
|                             *req.headers_mut() = self.headers.clone(); | ||||
|                             self.in_flight = self.client.hyper.request(req); | ||||
|                             continue; | ||||
|                         }, | ||||
|                         } | ||||
|                         redirect::Action::Stop => { | ||||
|                             debug!("redirect_policy disallowed redirection to '{}'", loc); | ||||
|                         }, | ||||
|                         } | ||||
|                         redirect::Action::LoopDetected => { | ||||
|                             return Err(crate::error::loop_detected(self.url.clone())); | ||||
|                         }, | ||||
|                         } | ||||
|                         redirect::Action::TooManyRedirects => { | ||||
|                             return Err(crate::error::too_many_redirects(self.url.clone())); | ||||
|                         } | ||||
| @@ -866,17 +849,12 @@ impl Future for PendingRequest { | ||||
| impl fmt::Debug for Pending { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match self.inner { | ||||
|             PendingInner::Request(ref req) => { | ||||
|                 f.debug_struct("Pending") | ||||
|                     .field("method", &req.method) | ||||
|                     .field("url", &req.url) | ||||
|                     .finish() | ||||
|             }, | ||||
|             PendingInner::Error(ref err) => { | ||||
|                 f.debug_struct("Pending") | ||||
|                     .field("error", err) | ||||
|                     .finish() | ||||
|             } | ||||
|             PendingInner::Request(ref req) => f | ||||
|                 .debug_struct("Pending") | ||||
|                 .field("method", &req.method) | ||||
|                 .field("url", &req.url) | ||||
|                 .finish(), | ||||
|             PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -903,7 +881,7 @@ fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore | ||||
|     if !header.is_empty() { | ||||
|         headers.insert( | ||||
|             crate::header::COOKIE, | ||||
|             HeaderValue::from_bytes(header.as_bytes()).unwrap() | ||||
|             HeaderValue::from_bytes(header.as_bytes()).unwrap(), | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -20,18 +20,18 @@ The following types directly support the gzip compression case: | ||||
| - `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF | ||||
| */ | ||||
|  | ||||
| use std::fmt; | ||||
| use std::mem; | ||||
| use std::cmp; | ||||
| use std::fmt; | ||||
| use std::io::{self, Read}; | ||||
| use std::mem; | ||||
|  | ||||
| use bytes::{Buf, BufMut, BytesMut}; | ||||
| use flate2::read::GzDecoder; | ||||
| use futures::{Async, Future, Poll, Stream}; | ||||
| use hyper::{HeaderMap}; | ||||
| use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; | ||||
| use hyper::HeaderMap; | ||||
|  | ||||
| use log::{warn}; | ||||
| use log::warn; | ||||
|  | ||||
| use super::{Body, Chunk}; | ||||
| use crate::error; | ||||
| @@ -42,7 +42,7 @@ const INIT_BUFFER_SIZE: usize = 8192; | ||||
| /// | ||||
| /// The inner decoder may be constructed asynchronously. | ||||
| pub struct Decoder { | ||||
|     inner: Inner | ||||
|     inner: Inner, | ||||
| } | ||||
|  | ||||
| enum Inner { | ||||
| @@ -51,7 +51,7 @@ enum Inner { | ||||
|     /// A `Gzip` decoder will uncompress the gzipped response content before returning it. | ||||
|     Gzip(Gzip), | ||||
|     /// A decoder that doesn't have a value yet. | ||||
|     Pending(Pending) | ||||
|     Pending(Pending), | ||||
| } | ||||
|  | ||||
| /// A future attempt to poll the response body for EOF so we know whether to use gzip or not. | ||||
| @@ -68,8 +68,7 @@ struct Gzip { | ||||
|  | ||||
| impl fmt::Debug for Decoder { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Decoder") | ||||
|             .finish() | ||||
|         f.debug_struct("Decoder").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -80,7 +79,7 @@ impl Decoder { | ||||
|     #[inline] | ||||
|     pub fn empty() -> Decoder { | ||||
|         Decoder { | ||||
|             inner: Inner::PlainText(Body::empty()) | ||||
|             inner: Inner::PlainText(Body::empty()), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -90,7 +89,7 @@ impl Decoder { | ||||
|     #[inline] | ||||
|     fn plain_text(body: Body) -> Decoder { | ||||
|         Decoder { | ||||
|             inner: Inner::PlainText(body) | ||||
|             inner: Inner::PlainText(body), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -100,7 +99,9 @@ impl Decoder { | ||||
|     #[inline] | ||||
|     fn gzip(body: Body) -> Decoder { | ||||
|         Decoder { | ||||
|             inner: Inner::Pending(Pending { body: ReadableChunks::new(body) }) | ||||
|             inner: Inner::Pending(Pending { | ||||
|                 body: ReadableChunks::new(body), | ||||
|             }), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -120,11 +121,11 @@ impl Decoder { | ||||
|                 .get_all(CONTENT_ENCODING) | ||||
|                 .iter() | ||||
|                 .any(|enc| enc == "gzip"); | ||||
|             content_encoding_gzip || | ||||
|             headers | ||||
|                 .get_all(TRANSFER_ENCODING) | ||||
|                 .iter() | ||||
|                 .any(|enc| enc == "gzip") | ||||
|             content_encoding_gzip | ||||
|                 || headers | ||||
|                     .get_all(TRANSFER_ENCODING) | ||||
|                     .iter() | ||||
|                     .any(|enc| enc == "gzip") | ||||
|         }; | ||||
|         if is_gzip { | ||||
|             if let Some(content_length) = headers.get(CONTENT_LENGTH) { | ||||
| @@ -153,15 +154,13 @@ impl Stream for Decoder { | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { | ||||
|         // Do a read or poll for a pendidng decoder value. | ||||
|         let new_value = match self.inner { | ||||
|             Inner::Pending(ref mut future) => { | ||||
|                 match future.poll() { | ||||
|                     Ok(Async::Ready(inner)) => inner, | ||||
|                     Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|                     Err(e) => return Err(e) | ||||
|                 } | ||||
|             Inner::Pending(ref mut future) => match future.poll() { | ||||
|                 Ok(Async::Ready(inner)) => inner, | ||||
|                 Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|                 Err(e) => return Err(e), | ||||
|             }, | ||||
|             Inner::PlainText(ref mut body) => return body.poll(), | ||||
|             Inner::Gzip(ref mut decoder) => return decoder.poll() | ||||
|             Inner::Gzip(ref mut decoder) => return decoder.poll(), | ||||
|         }; | ||||
|  | ||||
|         self.inner = new_value; | ||||
| @@ -177,13 +176,13 @@ impl Future for Pending { | ||||
|         let body_state = match self.body.poll_stream() { | ||||
|             Ok(Async::Ready(state)) => state, | ||||
|             Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|             Err(e) => return Err(e) | ||||
|             Err(e) => return Err(e), | ||||
|         }; | ||||
|  | ||||
|         let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty())); | ||||
|         match body_state { | ||||
|             StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))), | ||||
|             StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body)))) | ||||
|             StreamState::HasMore => Ok(Async::Ready(Inner::Gzip(Gzip::new(body)))), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -258,7 +257,7 @@ enum StreamState { | ||||
|     /// More bytes can be read from the stream. | ||||
|     HasMore, | ||||
|     /// No more bytes can be read from the stream. | ||||
|     Eof | ||||
|     Eof, | ||||
| } | ||||
|  | ||||
| impl<S> ReadableChunks<S> { | ||||
| @@ -273,8 +272,7 @@ impl<S> ReadableChunks<S> { | ||||
|  | ||||
| impl<S> fmt::Debug for ReadableChunks<S> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("ReadableChunks") | ||||
|             .finish() | ||||
|         f.debug_struct("ReadableChunks").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -296,20 +294,12 @@ where | ||||
|                     } else { | ||||
|                         return Ok(len); | ||||
|                     } | ||||
|                 }, | ||||
|                 ReadState::NotReady => { | ||||
|                     match self.poll_stream() { | ||||
|                         Ok(Async::Ready(StreamState::HasMore)) => continue, | ||||
|                         Ok(Async::Ready(StreamState::Eof)) => { | ||||
|                             return Ok(0) | ||||
|                         }, | ||||
|                         Ok(Async::NotReady) => { | ||||
|                             return Err(io::ErrorKind::WouldBlock.into()) | ||||
|                         }, | ||||
|                         Err(e) => { | ||||
|                             return Err(error::into_io(e)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 ReadState::NotReady => match self.poll_stream() { | ||||
|                     Ok(Async::Ready(StreamState::HasMore)) => continue, | ||||
|                     Ok(Async::Ready(StreamState::Eof)) => return Ok(0), | ||||
|                     Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()), | ||||
|                     Err(e) => return Err(error::into_io(e)), | ||||
|                 }, | ||||
|                 ReadState::Eof => return Ok(0), | ||||
|             } | ||||
| @@ -320,7 +310,8 @@ where | ||||
| } | ||||
|  | ||||
| impl<S> ReadableChunks<S> | ||||
|     where S: Stream<Item = Chunk, Error = error::Error> | ||||
| where | ||||
|     S: Stream<Item = Chunk, Error = error::Error>, | ||||
| { | ||||
|     /// Poll the readiness of the inner reader. | ||||
|     /// | ||||
| @@ -332,16 +323,14 @@ impl<S> ReadableChunks<S> | ||||
|                 self.state = ReadState::Ready(chunk); | ||||
|  | ||||
|                 Ok(Async::Ready(StreamState::HasMore)) | ||||
|             }, | ||||
|             } | ||||
|             Ok(Async::Ready(None)) => { | ||||
|                 self.state = ReadState::Eof; | ||||
|  | ||||
|                 Ok(Async::Ready(StreamState::Eof)) | ||||
|             }, | ||||
|             Ok(Async::NotReady) => { | ||||
|                 Ok(Async::NotReady) | ||||
|             }, | ||||
|             Err(e) => Err(e) | ||||
|             } | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Err(e) => Err(e), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,10 +2,10 @@ | ||||
| use std::borrow::Cow; | ||||
| use std::fmt; | ||||
|  | ||||
| use http::HeaderMap; | ||||
| use mime_guess::Mime; | ||||
| use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET}; | ||||
| use uuid::Uuid; | ||||
| use http::HeaderMap; | ||||
|  | ||||
| use futures::Stream; | ||||
|  | ||||
| @@ -98,7 +98,7 @@ impl Form { | ||||
|  | ||||
|     /// Consume this instance and transform into an instance of hyper::Body for use in a request. | ||||
|     pub(crate) fn stream(mut self) -> hyper::Body { | ||||
|         if self.inner.fields.is_empty(){ | ||||
|         if self.inner.fields.is_empty() { | ||||
|             return hyper::Body::empty(); | ||||
|         } | ||||
|  | ||||
| @@ -117,7 +117,7 @@ impl Form { | ||||
|         hyper::Body::wrap_stream(stream.chain(last)) | ||||
|     } | ||||
|  | ||||
|     /// Generate a hyper::Body stream for a single Part instance of a Form request.  | ||||
|     /// 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) -> hyper::Body | ||||
|     where | ||||
|         T: Into<Cow<'static, str>>, | ||||
| @@ -126,12 +126,20 @@ impl Form { | ||||
|         let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary())); | ||||
|         // append headers | ||||
|         let header = hyper::Body::from({ | ||||
|             let mut h = self.inner.percent_encoding.encode_headers(&name.into(), &part.meta); | ||||
|             let mut h = self | ||||
|                 .inner | ||||
|                 .percent_encoding | ||||
|                 .encode_headers(&name.into(), &part.meta); | ||||
|             h.extend_from_slice(b"\r\n\r\n"); | ||||
|             h | ||||
|         }); | ||||
|         // then append form data followed by terminating CRLF | ||||
|         hyper::Body::wrap_stream(boundary.chain(header).chain(hyper::Body::wrap_stream(part.value)).chain(hyper::Body::from("\r\n".to_owned()))) | ||||
|         hyper::Body::wrap_stream( | ||||
|             boundary | ||||
|                 .chain(header) | ||||
|                 .chain(hyper::Body::wrap_stream(part.value)) | ||||
|                 .chain(hyper::Body::from("\r\n".to_owned())), | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn compute_length(&mut self) -> Option<u64> { | ||||
| @@ -188,7 +196,9 @@ impl Part { | ||||
|         T::Item: Into<Chunk>, | ||||
|         T::Error: std::error::Error + Send + Sync, | ||||
|     { | ||||
|         Part::new(Body::wrap(hyper::Body::wrap_stream(value.map(|chunk| chunk.into())))) | ||||
|         Part::new(Body::wrap(hyper::Body::wrap_stream( | ||||
|             value.map(|chunk| chunk.into()), | ||||
|         ))) | ||||
|     } | ||||
|  | ||||
|     fn new(value: Body) -> Part { | ||||
| @@ -306,7 +316,13 @@ impl<P: PartProps> FormParts<P> { | ||||
|                     // 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 | ||||
|                     length += 2 | ||||
|                         + self.boundary().len() as u64 | ||||
|                         + 2 | ||||
|                         + header_length as u64 | ||||
|                         + 4 | ||||
|                         + value_length | ||||
|                         + 2 | ||||
|                 } | ||||
|                 _ => return None, | ||||
|             } | ||||
| @@ -340,7 +356,7 @@ impl PartMetadata { | ||||
|         PartMetadata { | ||||
|             mime: None, | ||||
|             file_name: None, | ||||
|             headers: HeaderMap::default() | ||||
|             headers: HeaderMap::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -358,11 +374,10 @@ impl PartMetadata { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl PartMetadata { | ||||
|     pub(crate) fn fmt_fields<'f, 'fa, 'fb>( | ||||
|         &self, | ||||
|         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb> | ||||
|         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, | ||||
|     ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { | ||||
|         debug_struct | ||||
|             .field("mime", &self.mime) | ||||
| @@ -377,22 +392,24 @@ pub(crate) struct AttrCharEncodeSet; | ||||
| impl EncodeSet for AttrCharEncodeSet { | ||||
|     fn contains(&self, ch: u8) -> bool { | ||||
|         match ch as char { | ||||
|              '!'  => false, | ||||
|              '#'  => false, | ||||
|              '$'  => false, | ||||
|              '&'  => false, | ||||
|              '+'  => false, | ||||
|              '-'  => false, | ||||
|              '.' => false, | ||||
|              '^'  => false, | ||||
|              '_'  => false, | ||||
|              '`'  => false, | ||||
|              '|'  => false, | ||||
|              '~' => false, | ||||
|               _ => { | ||||
|                   let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a || ch >= 0x30 && ch <= 0x39; | ||||
|                   !is_alpha_numeric | ||||
|               } | ||||
|             '!' => false, | ||||
|             '#' => false, | ||||
|             '$' => false, | ||||
|             '&' => false, | ||||
|             '+' => false, | ||||
|             '-' => false, | ||||
|             '.' => false, | ||||
|             '^' => false, | ||||
|             '_' => false, | ||||
|             '`' => false, | ||||
|             '|' => false, | ||||
|             '~' => false, | ||||
|             _ => { | ||||
|                 let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a | ||||
|                     || ch >= 0x61 && ch <= 0x7a | ||||
|                     || ch >= 0x30 && ch <= 0x39; | ||||
|                 !is_alpha_numeric | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -417,36 +434,38 @@ impl PercentEncoding { | ||||
|                 None => "".to_string(), | ||||
|             }, | ||||
|         ); | ||||
|         field.headers.iter().fold(s.into_bytes(), |mut header, (k,v)| { | ||||
|             header.extend_from_slice(b"\r\n"); | ||||
|             header.extend_from_slice(k.as_str().as_bytes()); | ||||
|             header.extend_from_slice(b": "); | ||||
|             header.extend_from_slice(v.as_bytes()); | ||||
|             header | ||||
|         }) | ||||
|         field | ||||
|             .headers | ||||
|             .iter() | ||||
|             .fold(s.into_bytes(), |mut header, (k, v)| { | ||||
|                 header.extend_from_slice(b"\r\n"); | ||||
|                 header.extend_from_slice(k.as_str().as_bytes()); | ||||
|                 header.extend_from_slice(b": "); | ||||
|                 header.extend_from_slice(v.as_bytes()); | ||||
|                 header | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     // According to RFC7578 Section 4.2, `filename*=` syntax is invalid. | ||||
|     // See https://github.com/seanmonstar/reqwest/issues/419. | ||||
|     fn format_filename(&self, filename: &str) -> String { | ||||
|         let legal_filename = filename.replace("\\", "\\\\") | ||||
|                                      .replace("\"", "\\\"") | ||||
|                                      .replace("\r", "\\\r") | ||||
|                                      .replace("\n", "\\\n"); | ||||
|         let legal_filename = filename | ||||
|             .replace("\\", "\\\\") | ||||
|             .replace("\"", "\\\"") | ||||
|             .replace("\r", "\\\r") | ||||
|             .replace("\n", "\\\n"); | ||||
|         format!("filename=\"{}\"", legal_filename) | ||||
|     } | ||||
|  | ||||
|     fn format_parameter(&self, name: &str, value: &str) -> String { | ||||
|         let legal_value = match *self { | ||||
|             PercentEncoding::PathSegment => { | ||||
|                 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET) | ||||
|                     .to_string() | ||||
|             }, | ||||
|                 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string() | ||||
|             } | ||||
|             PercentEncoding::AttrChar => { | ||||
|                 percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet) | ||||
|                     .to_string() | ||||
|             }, | ||||
|             PercentEncoding::NoOp => { value.to_string() }, | ||||
|                 percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet).to_string() | ||||
|             } | ||||
|             PercentEncoding::NoOp => value.to_string(), | ||||
|         }; | ||||
|         if value.len() == legal_value.len() { | ||||
|             // nothing has been percent encoded | ||||
| @@ -477,38 +496,45 @@ mod tests { | ||||
|     #[test] | ||||
|     fn stream_to_end() { | ||||
|         let mut form = Form::new() | ||||
|             .part("reader1", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1".to_owned()))))) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part( | ||||
|                 "key2", | ||||
|                 Part::text("value2").mime(mime::IMAGE_BMP), | ||||
|                 "reader1", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "part1".to_owned(), | ||||
|                 )))), | ||||
|             ) | ||||
|             .part("reader2", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part2".to_owned()))))) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) | ||||
|             .part( | ||||
|                 "key3", | ||||
|                 Part::text("value3").file_name("filename"), | ||||
|             ); | ||||
|                 "reader2", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "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 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 mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|  | ||||
|         let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap(); | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
| @@ -534,7 +560,9 @@ mod tests { | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|  | ||||
|         let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap(); | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use base64::{encode}; | ||||
| use base64::encode; | ||||
| use futures::Future; | ||||
| use serde::Serialize; | ||||
| use serde_json; | ||||
| use serde_urlencoded; | ||||
|  | ||||
| use super::body::{Body}; | ||||
| use super::body::Body; | ||||
| use super::client::{Client, Pending}; | ||||
| use super::multipart; | ||||
| use super::response::Response; | ||||
| use crate::header::{CONTENT_LENGTH, CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue}; | ||||
| use http::HttpTryFrom; | ||||
| use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; | ||||
| use crate::{Method, Url}; | ||||
| use http::HttpTryFrom; | ||||
|  | ||||
| /// A request which can be executed with `Client::execute()`. | ||||
| pub struct Request { | ||||
| @@ -95,10 +95,7 @@ impl Request { | ||||
|  | ||||
| impl RequestBuilder { | ||||
|     pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder { | ||||
|         RequestBuilder { | ||||
|             client, | ||||
|             request, | ||||
|         } | ||||
|         RequestBuilder { client, request } | ||||
|     } | ||||
|  | ||||
|     /// Add a `Header` to this Request. | ||||
| @@ -110,11 +107,11 @@ impl RequestBuilder { | ||||
|         let mut error = None; | ||||
|         if let Ok(ref mut req) = self.request { | ||||
|             match <HeaderName as HttpTryFrom<K>>::try_from(key) { | ||||
|                 Ok(key) => { | ||||
|                     match <HeaderValue as HttpTryFrom<V>>::try_from(value) { | ||||
|                         Ok(value) => { req.headers_mut().append(key, value); } | ||||
|                         Err(e) => error = Some(crate::error::from(e.into())), | ||||
|                 Ok(key) => match <HeaderValue as HttpTryFrom<V>>::try_from(value) { | ||||
|                     Ok(value) => { | ||||
|                         req.headers_mut().append(key, value); | ||||
|                     } | ||||
|                     Err(e) => error = Some(crate::error::from(e.into())), | ||||
|                 }, | ||||
|                 Err(e) => error = Some(crate::error::from(e.into())), | ||||
|             }; | ||||
| @@ -168,7 +165,7 @@ impl RequestBuilder { | ||||
|     { | ||||
|         let auth = match password { | ||||
|             Some(password) => format!("{}:{}", username, password), | ||||
|             None => format!("{}:", username) | ||||
|             None => format!("{}:", username), | ||||
|         }; | ||||
|         let header_value = format!("Basic {}", encode(&auth)); | ||||
|         self.header(crate::header::AUTHORIZATION, &*header_value) | ||||
| @@ -221,10 +218,7 @@ impl RequestBuilder { | ||||
|     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() | ||||
|             format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(), | ||||
|         ); | ||||
|  | ||||
|         builder = match multipart.compute_length() { | ||||
| @@ -286,10 +280,10 @@ impl RequestBuilder { | ||||
|                 Ok(body) => { | ||||
|                     req.headers_mut().insert( | ||||
|                         CONTENT_TYPE, | ||||
|                         HeaderValue::from_static("application/x-www-form-urlencoded") | ||||
|                         HeaderValue::from_static("application/x-www-form-urlencoded"), | ||||
|                     ); | ||||
|                     *req.body_mut() = Some(body.into()); | ||||
|                 }, | ||||
|                 } | ||||
|                 Err(err) => error = Some(crate::error::from(err)), | ||||
|             } | ||||
|         } | ||||
| @@ -310,12 +304,10 @@ impl RequestBuilder { | ||||
|         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.headers_mut() | ||||
|                         .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); | ||||
|                     *req.body_mut() = Some(body.into()); | ||||
|                 }, | ||||
|                 } | ||||
|                 Err(err) => error = Some(crate::error::from(err)), | ||||
|             } | ||||
|         } | ||||
| @@ -368,8 +360,7 @@ impl RequestBuilder { | ||||
|  | ||||
| impl fmt::Debug for Request { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         fmt_request_fields(&mut f.debug_struct("Request"), self) | ||||
|             .finish() | ||||
|         fmt_request_fields(&mut f.debug_struct("Request"), self).finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -377,27 +368,22 @@ 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() | ||||
|             } | ||||
|             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> { | ||||
| 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) | ||||
| } | ||||
|  | ||||
| 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 | ||||
| @@ -413,11 +399,11 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { | ||||
|             Some(key) => { | ||||
|                 dst.insert(key.clone(), value); | ||||
|                 prev_name = Some(key); | ||||
|             }, | ||||
|             } | ||||
|             None => match prev_name { | ||||
|                 Some(ref key) => { | ||||
|                     dst.append(key.clone(), value); | ||||
|                 }, | ||||
|                 } | ||||
|                 None => unreachable!("HeaderMap::into_iter yielded None first"), | ||||
|             }, | ||||
|         } | ||||
| @@ -427,8 +413,8 @@ pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::Client; | ||||
|     use std::collections::BTreeMap; | ||||
|     use serde::Serialize; | ||||
|     use std::collections::BTreeMap; | ||||
|  | ||||
|     #[test] | ||||
|     fn add_query_append() { | ||||
| @@ -467,7 +453,10 @@ mod tests { | ||||
|         let some_url = "https://google.com/"; | ||||
|         let r = client.get(some_url); | ||||
|  | ||||
|         let params = Params { foo: "bar".into(), qux: 3 }; | ||||
|         let params = Params { | ||||
|             foo: "bar".into(), | ||||
|             qux: 3, | ||||
|         }; | ||||
|  | ||||
|         let r = r.query(¶ms); | ||||
|  | ||||
| @@ -510,11 +499,7 @@ mod tests { | ||||
|  | ||||
|         assert_eq!(req.headers()["im-a"], "keeper"); | ||||
|  | ||||
|         let foo = req | ||||
|             .headers() | ||||
|             .get_all("foo") | ||||
|             .iter() | ||||
|             .collect::<Vec<_>>(); | ||||
|         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"); | ||||
|   | ||||
| @@ -1,28 +1,26 @@ | ||||
| use std::fmt; | ||||
| use std::mem; | ||||
| use std::marker::PhantomData; | ||||
| use std::net::SocketAddr; | ||||
| use std::borrow::Cow; | ||||
| use std::fmt; | ||||
| use std::marker::PhantomData; | ||||
| use std::mem; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use encoding_rs::{Encoding, UTF_8}; | ||||
| use futures::{Async, Future, Poll, Stream, try_ready}; | ||||
| use futures::stream::Concat2; | ||||
| use futures::{try_ready, Async, Future, Poll, Stream}; | ||||
| use http; | ||||
| use hyper::{HeaderMap, StatusCode, Version}; | ||||
| use hyper::client::connect::HttpInfo; | ||||
| use hyper::header::{CONTENT_LENGTH}; | ||||
| use hyper::header::CONTENT_LENGTH; | ||||
| use hyper::{HeaderMap, StatusCode, Version}; | ||||
| use log::debug; | ||||
| use mime::Mime; | ||||
| use tokio::timer::Delay; | ||||
| use serde::de::DeserializeOwned; | ||||
| use serde_json; | ||||
| use tokio::timer::Delay; | ||||
| use url::Url; | ||||
| use log::{debug}; | ||||
|  | ||||
|  | ||||
| use crate::cookie; | ||||
| use super::Decoder; | ||||
| use super::body::Body; | ||||
|  | ||||
| use super::Decoder; | ||||
| use crate::cookie; | ||||
|  | ||||
| /// A Response to a submitted `Request`. | ||||
| pub struct Response { | ||||
| @@ -37,7 +35,12 @@ pub struct Response { | ||||
| } | ||||
|  | ||||
| impl Response { | ||||
|     pub(super) fn new(res: hyper::Response<hyper::Body>, url: Url, gzip: bool, timeout: Option<Delay>) -> Response { | ||||
|     pub(super) fn new( | ||||
|         res: hyper::Response<hyper::Body>, | ||||
|         url: Url, | ||||
|         gzip: bool, | ||||
|         timeout: Option<Delay>, | ||||
|     ) -> Response { | ||||
|         let (parts, body) = res.into_parts(); | ||||
|         let status = parts.status; | ||||
|         let version = parts.version; | ||||
| @@ -57,7 +60,6 @@ impl Response { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Get the `StatusCode` of this `Response`. | ||||
|     #[inline] | ||||
|     pub fn status(&self) -> StatusCode { | ||||
| @@ -77,11 +79,10 @@ impl Response { | ||||
|     } | ||||
|  | ||||
|     /// Retrieve the cookies contained in the response. | ||||
|     ///  | ||||
|     /// | ||||
|     /// Note that invalid 'Set-Cookie' headers will be ignored. | ||||
|     pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { | ||||
|         cookie::extract_response_cookies(&self.headers) | ||||
|             .filter_map(Result::ok) | ||||
|         cookie::extract_response_cookies(&self.headers).filter_map(Result::ok) | ||||
|     } | ||||
|  | ||||
|     /// Get the final `Url` of this `Response`. | ||||
| @@ -92,8 +93,7 @@ impl Response { | ||||
|  | ||||
|     /// Get the remote address used to get this `Response`. | ||||
|     pub fn remote_addr(&self) -> Option<SocketAddr> { | ||||
|         self | ||||
|             .extensions | ||||
|         self.extensions | ||||
|             .get::<HttpInfo>() | ||||
|             .map(|info| info.remote_addr()) | ||||
|     } | ||||
| @@ -106,8 +106,7 @@ impl Response { | ||||
|     /// - The response is gzipped and automatically decoded (thus changing | ||||
|     ///   the actual decoded length). | ||||
|     pub fn content_length(&self) -> Option<u64> { | ||||
|         self | ||||
|             .headers() | ||||
|         self.headers() | ||||
|             .get(CONTENT_LENGTH) | ||||
|             .and_then(|ct_len| ct_len.to_str().ok()) | ||||
|             .and_then(|ct_len| ct_len.parse().ok()) | ||||
| @@ -145,27 +144,24 @@ impl Response { | ||||
|     } | ||||
|  | ||||
|     /// Get the response text given a specific encoding | ||||
|     pub fn text_with_charset(&mut self, default_encoding: &str) -> impl Future<Item = String, Error = crate::Error> { | ||||
|     pub fn text_with_charset( | ||||
|         &mut self, | ||||
|         default_encoding: &str, | ||||
|     ) -> impl Future<Item = String, Error = crate::Error> { | ||||
|         let body = mem::replace(&mut self.body, Decoder::empty()); | ||||
|         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 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()) | ||||
|             }) | ||||
|             .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); | ||||
|         Text { | ||||
|             concat: body.concat2(), | ||||
|             encoding | ||||
|             encoding, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -256,7 +252,8 @@ impl<T: Into<Body>> From<http::Response<T>> for Response { | ||||
|         let (mut parts, body) = r.into_parts(); | ||||
|         let body = body.into(); | ||||
|         let body = Decoder::detect(&mut parts.headers, body, false); | ||||
|         let url = parts.extensions | ||||
|         let url = parts | ||||
|             .extensions | ||||
|             .remove::<ResponseUrl>() | ||||
|             .unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap())); | ||||
|         let url = url.0; | ||||
| @@ -289,8 +286,7 @@ impl<T: DeserializeOwned> Future for Json<T> { | ||||
|  | ||||
| impl<T> fmt::Debug for Json<T> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Json") | ||||
|             .finish() | ||||
|         f.debug_struct("Json").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -308,7 +304,9 @@ impl Future for Text { | ||||
|         // a block because of borrow checker | ||||
|         { | ||||
|             let (text, _, _) = self.encoding.decode(&bytes); | ||||
|             if let Cow::Owned(s) = text { return Ok(Async::Ready(s)) } | ||||
|             if let Cow::Owned(s) = text { | ||||
|                 return Ok(Async::Ready(s)); | ||||
|             } | ||||
|         } | ||||
|         unsafe { | ||||
|             // decoding returned Cow::Borrowed, meaning these bytes | ||||
| @@ -357,9 +355,9 @@ impl ResponseBuilderExt for http::response::Builder { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use url::Url; | ||||
|     use super::{Response, ResponseBuilderExt, ResponseUrl}; | ||||
|     use http::response::Builder; | ||||
|     use super::{Response, ResponseUrl, ResponseBuilderExt}; | ||||
|     use url::Url; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_response_builder_ext() { | ||||
| @@ -370,7 +368,10 @@ mod tests { | ||||
|             .body(()) | ||||
|             .unwrap(); | ||||
|  | ||||
|         assert_eq!(response.extensions().get::<ResponseUrl>(), Some(&ResponseUrl(url))); | ||||
|         assert_eq!( | ||||
|             response.extensions().get::<ResponseUrl>(), | ||||
|             Some(&ResponseUrl(url)) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user