Make cookies an optional feature (default off)

This commit is contained in:
Sean McArthur
2019-09-17 14:53:56 -07:00
parent 53495e1526
commit 0a87d3d7da
9 changed files with 85 additions and 28 deletions

View File

@@ -27,6 +27,11 @@ matrix:
- rust: nightly - rust: nightly
env: FEATURES="--features rustls-tls" env: FEATURES="--features rustls-tls"
# optional cookies
#- rust: stable
- rust: nightly
env: FEATURES="--features cookies"
# socks # socks
#- rust: stable #- rust: stable
#- rust: nightly #- rust: nightly

View File

@@ -10,6 +10,7 @@ readme = "README.md"
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
categories = ["web-programming::http-client"] categories = ["web-programming::http-client"]
edition = "2018" edition = "2018"
autotests = true
publish = false publish = false
@@ -38,8 +39,7 @@ time = "0.1.42"
# TODO: candidates for optional features # TODO: candidates for optional features
async-compression = { version = "0.1.0-alpha.4", default-features = false, features = ["gzip", "stream"] } async-compression = { version = "0.1.0-alpha.4", default-features = false, features = ["gzip", "stream"] }
cookie_store = "0.9.0"
cookie = "0.12.0"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.6.1" serde_urlencoded = "0.6.1"
@@ -57,6 +57,10 @@ rustls = { version = "0.16", features = ["dangerous_configuration"], optional =
tokio-rustls = { version = "=0.12.0-alpha.2", optional = true } tokio-rustls = { version = "=0.12.0-alpha.2", optional = true }
webpki-roots = { version = "0.17", optional = true } webpki-roots = { version = "0.17", optional = true }
## cookies
cookie_crate = { version = "0.12", package = "cookie", optional = true }
cookie_store = { version = "0.9", optional = true }
## socks ## socks
#socks = { version = "0.3.2", optional = true } #socks = { version = "0.3.2", optional = true }
@@ -81,7 +85,15 @@ default-tls-vendored = ["default-tls", "native-tls/vendored"]
rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"] rustls-tls = ["hyper-rustls", "tokio-rustls", "webpki-roots", "rustls", "tls"]
cookies = ["cookie_crate", "cookie_store"]
#trust-dns = ["trust-dns-resolver"] #trust-dns = ["trust-dns-resolver"]
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.6" winreg = "0.6"
[[test]]
name = "cookie"
path = "tests/cookie.rs"
required-features = ["cookies"]

View File

@@ -1,5 +1,7 @@
use std::net::IpAddr; use std::net::IpAddr;
use std::sync::{Arc, RwLock}; use std::sync::Arc;
#[cfg(feature = "cookies")]
use std::sync::RwLock;
use std::time::Duration; use std::time::Duration;
use std::{fmt, str}; use std::{fmt, str};
@@ -23,6 +25,7 @@ use log::debug;
use super::request::{Request, RequestBuilder}; use super::request::{Request, RequestBuilder};
use super::response::Response; use super::response::Response;
use crate::connect::Connector; use crate::connect::Connector;
#[cfg(feature = "cookies")]
use crate::cookie; use crate::cookie;
use crate::into_url::{expect_uri, try_uri}; use crate::into_url::{expect_uri, try_uri};
use crate::proxy::get_proxies; use crate::proxy::get_proxies;
@@ -76,6 +79,7 @@ struct Config {
http1_title_case_headers: bool, http1_title_case_headers: bool,
local_address: Option<IpAddr>, local_address: Option<IpAddr>,
nodelay: bool, nodelay: bool,
#[cfg(feature = "cookies")]
cookie_store: Option<cookie::CookieStore>, cookie_store: Option<cookie::CookieStore>,
} }
@@ -115,6 +119,7 @@ impl ClientBuilder {
http1_title_case_headers: false, http1_title_case_headers: false,
local_address: None, local_address: None,
nodelay: false, nodelay: false,
#[cfg(feature = "cookies")]
cookie_store: None, cookie_store: None,
}, },
} }
@@ -210,11 +215,10 @@ impl ClientBuilder {
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);
Ok(Client { Ok(Client {
inner: Arc::new(ClientRef { inner: Arc::new(ClientRef {
cookie_store, #[cfg(feature = "cookies")]
cookie_store: config.cookie_store.map(RwLock::new),
gzip: config.gzip, gzip: config.gzip,
hyper: hyper_client, hyper: hyper_client,
headers: config.headers, headers: config.headers,
@@ -430,6 +434,11 @@ impl ClientBuilder {
/// additional requests. /// additional requests.
/// ///
/// By default, no cookie store is used. /// By default, no cookie store is used.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
pub fn cookie_store(mut self, enable: bool) -> ClientBuilder { pub fn cookie_store(mut self, enable: bool) -> ClientBuilder {
self.config.cookie_store = if enable { self.config.cookie_store = if enable {
Some(cookie::CookieStore::default()) Some(cookie::CookieStore::default())
@@ -561,10 +570,13 @@ impl Client {
} }
// Add cookies from the cookie store. // Add cookies from the cookie store.
if let Some(cookie_store_wrapper) = self.inner.cookie_store.as_ref() { #[cfg(feature = "cookies")]
if headers.get(crate::header::COOKIE).is_none() { {
let cookie_store = cookie_store_wrapper.read().unwrap(); if let Some(cookie_store_wrapper) = self.inner.cookie_store.as_ref() {
add_cookie_header(&mut headers, &cookie_store, &url); if headers.get(crate::header::COOKIE).is_none() {
let cookie_store = cookie_store_wrapper.read().unwrap();
add_cookie_header(&mut headers, &cookie_store, &url);
}
} }
} }
@@ -662,6 +674,7 @@ impl fmt::Debug for ClientBuilder {
} }
struct ClientRef { struct ClientRef {
#[cfg(feature = "cookies")]
cookie_store: Option<RwLock<cookie::CookieStore>>, cookie_store: Option<RwLock<cookie::CookieStore>>,
gzip: bool, gzip: bool,
headers: HeaderMap, headers: HeaderMap,
@@ -760,12 +773,16 @@ impl Future for PendingRequest {
Poll::Ready(Ok(res)) => res, Poll::Ready(Ok(res)) => res,
Poll::Pending => return Poll::Pending, Poll::Pending => return Poll::Pending,
}; };
if let Some(store_wrapper) = self.client.cookie_store.as_ref() {
let mut store = store_wrapper.write().unwrap(); #[cfg(feature = "cookies")]
let cookies = cookie::extract_response_cookies(&res.headers()) {
.filter_map(|res| res.ok()) if let Some(store_wrapper) = self.client.cookie_store.as_ref() {
.map(|cookie| cookie.into_inner().into_owned()); let mut store = store_wrapper.write().unwrap();
store.0.store_response_cookies(cookies, &self.url); let cookies = cookie::extract_response_cookies(&res.headers())
.filter_map(|res| res.ok())
.map(|cookie| cookie.into_inner().into_owned());
store.0.store_response_cookies(cookies, &self.url);
}
} }
let should_redirect = match res.status() { let should_redirect = match res.status() {
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => {
@@ -854,9 +871,14 @@ impl Future for PendingRequest {
.expect("valid request parts"); .expect("valid request parts");
// Add cookies from the cookie store. // Add cookies from the cookie store.
if let Some(cookie_store_wrapper) = self.client.cookie_store.as_ref() { #[cfg(feature = "cookies")]
let cookie_store = cookie_store_wrapper.read().unwrap(); {
add_cookie_header(&mut headers, &cookie_store, &self.url); if let Some(cookie_store_wrapper) =
self.client.cookie_store.as_ref()
{
let cookie_store = cookie_store_wrapper.read().unwrap();
add_cookie_header(&mut headers, &cookie_store, &self.url);
}
} }
*req.headers_mut() = headers.clone(); *req.headers_mut() = headers.clone();
@@ -909,6 +931,7 @@ fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
referer.as_str().parse().ok() referer.as_str().parse().ok()
} }
#[cfg(feature = "cookies")]
fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore, url: &Url) { fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &cookie::CookieStore, url: &Url) {
let header = cookie_store let header = cookie_store
.0 .0

View File

@@ -18,6 +18,7 @@ use url::Url;
use super::body::Body; use super::body::Body;
use super::Decoder; use super::Decoder;
#[cfg(feature = "cookies")]
use crate::cookie; use crate::cookie;
/// A Response to a submitted `Request`. /// A Response to a submitted `Request`.
@@ -99,6 +100,11 @@ impl Response {
/// Retrieve the cookies contained in the response. /// Retrieve the cookies contained in the response.
/// ///
/// Note that invalid 'Set-Cookie' headers will be ignored. /// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { 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)
} }

View File

@@ -392,6 +392,11 @@ impl ClientBuilder {
/// .build() /// .build()
/// .unwrap(); /// .unwrap();
/// ``` /// ```
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
pub fn cookie_store(self, enable: bool) -> ClientBuilder { pub fn cookie_store(self, enable: bool) -> ClientBuilder {
self.with_inner(|inner| inner.cookie_store(enable)) self.with_inner(|inner| inner.cookie_store(enable))
} }

View File

@@ -11,6 +11,7 @@ use serde::de::DeserializeOwned;
use super::client::KeepCoreThreadAlive; use super::client::KeepCoreThreadAlive;
use super::wait; use super::wait;
#[cfg(feature = "cookies")]
use crate::cookie; use crate::cookie;
use crate::{async_impl, StatusCode, Url, Version}; use crate::{async_impl, StatusCode, Url, Version};
@@ -121,6 +122,11 @@ impl Response {
/// Retrieve the cookies contained in the response. /// Retrieve the cookies contained in the response.
/// ///
/// Note that invalid 'Set-Cookie' headers will be ignored. /// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a { 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)
} }

View File

@@ -1,6 +1,5 @@
//! The cookies module contains types for working with request and response cookies. //! The cookies module contains types for working with request and response cookies.
use crate::cookie_crate;
use crate::header; use crate::header;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
@@ -18,7 +17,7 @@ fn tm_to_systemtime(tm: time::Tm) -> SystemTime {
} }
/// Error representing a parse failure of a 'Set-Cookie' header. /// Error representing a parse failure of a 'Set-Cookie' header.
pub struct CookieParseError(cookie::ParseError); pub struct CookieParseError(cookie_crate::ParseError);
impl<'a> fmt::Debug for CookieParseError { impl<'a> fmt::Debug for CookieParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -35,7 +34,7 @@ impl<'a> fmt::Display for CookieParseError {
impl std::error::Error for CookieParseError {} impl std::error::Error for CookieParseError {}
/// A single HTTP cookie. /// A single HTTP cookie.
pub struct Cookie<'a>(cookie::Cookie<'a>); pub struct Cookie<'a>(cookie_crate::Cookie<'a>);
impl<'a> fmt::Debug for Cookie<'a> { impl<'a> fmt::Debug for Cookie<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -50,20 +49,20 @@ impl Cookie<'static> {
N: Into<Cow<'static, str>>, N: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>, V: Into<Cow<'static, str>>,
{ {
Cookie(cookie::Cookie::new(name, value)) Cookie(cookie_crate::Cookie::new(name, value))
} }
} }
impl<'a> Cookie<'a> { impl<'a> Cookie<'a> {
fn parse(value: &'a crate::header::HeaderValue) -> Result<Cookie<'a>, CookieParseError> { fn parse(value: &'a crate::header::HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
std::str::from_utf8(value.as_bytes()) std::str::from_utf8(value.as_bytes())
.map_err(cookie::ParseError::from) .map_err(cookie_crate::ParseError::from)
.and_then(cookie::Cookie::parse) .and_then(cookie_crate::Cookie::parse)
.map_err(CookieParseError) .map_err(CookieParseError)
.map(Cookie) .map(Cookie)
} }
pub(crate) fn into_inner(self) -> cookie::Cookie<'a> { pub(crate) fn into_inner(self) -> cookie_crate::Cookie<'a> {
self.0 self.0
} }

View File

@@ -156,6 +156,7 @@
//! `native-tls` library to connect over HTTPS. //! `native-tls` library to connect over HTTPS.
//! - **default-tls-vendored**: Enables the `vendored` feature of `native-tls`. //! - **default-tls-vendored**: Enables the `vendored` feature of `native-tls`.
//! - **rustls-tls**: Provides TLS support via the `rustls` library. //! - **rustls-tls**: Provides TLS support via the `rustls` library.
//! - **cookies**: Provides cookie session support.
//! //!
//! //!
//! [hyper]: http://hyper.rs //! [hyper]: http://hyper.rs
@@ -172,8 +173,6 @@
////! - **trust-dns**: Enables a trust-dns async resolver instead of default ////! - **trust-dns**: Enables a trust-dns async resolver instead of default
////! threadpool using `getaddrinfo`. ////! threadpool using `getaddrinfo`.
extern crate cookie as cookie_crate;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
extern crate doc_comment; extern crate doc_comment;
@@ -208,6 +207,7 @@ mod error;
mod async_impl; mod async_impl;
pub mod blocking; pub mod blocking;
mod connect; mod connect;
#[cfg(feature = "cookies")]
pub mod cookie; pub mod cookie;
//#[cfg(feature = "trust-dns")] //#[cfg(feature = "trust-dns")]
//mod dns; //mod dns;

View File

@@ -397,6 +397,7 @@ async fn test_invalid_location_stops_redirect_gh484() {
); );
} }
#[cfg(feature = "cookies")]
#[tokio::test] #[tokio::test]
async fn test_redirect_302_with_set_cookies() { async fn test_redirect_302_with_set_cookies() {
let code = 302; let code = 302;