1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

optional cookies features (#1981)

This commit is contained in:
Rob Ede 2021-02-13 15:08:43 +00:00 committed by GitHub
parent b37669cb3b
commit 3279070f9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 205 additions and 89 deletions

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changed
* Feature `cookies` is now optional and enabled by default. [#1981]
[#1981]: https://github.com/actix/actix-web/pull/1981
## 4.0.0-beta.3 - 2021-02-10 ## 4.0.0-beta.3 - 2021-02-10

View File

@ -15,6 +15,7 @@ license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
# features that docs.rs will build with
features = ["openssl", "rustls", "compress", "secure-cookies"] features = ["openssl", "rustls", "compress", "secure-cookies"]
[badges] [badges]
@ -38,12 +39,15 @@ members = [
] ]
[features] [features]
default = ["compress"] default = ["compress", "cookies"]
# content-encoding support # content-encoding support
compress = ["actix-http/compress", "awc/compress"] compress = ["actix-http/compress", "awc/compress"]
# sessions feature # support for cookies
cookies = ["actix-http/cookies", "awc/cookies"]
# secure cookies feature
secure-cookies = ["actix-http/secure-cookies"] secure-cookies = ["actix-http/secure-cookies"]
# openssl # openssl
@ -95,17 +99,17 @@ futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
socket2 = "0.3.16"
pin-project = "1.0.0" pin-project = "1.0.0"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
smallvec = "1.6"
socket2 = "0.3.16"
time = { version = "0.2.23", default-features = false, features = ["std"] } time = { version = "0.2.23", default-features = false, features = ["std"] }
url = "2.1"
tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-openssl = { package = "openssl", version = "0.10.9", optional = true }
tls-rustls = { package = "rustls", version = "0.19.0", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true }
smallvec = "1.6" url = "2.1"
[target.'cfg(windows)'.dependencies.tls-openssl] [target.'cfg(windows)'.dependencies.tls-openssl]
version = "0.10.9" version = "0.10.9"
@ -122,9 +126,6 @@ brotli2 = "0.3.2"
flate2 = "1.0.13" flate2 = "1.0.13"
criterion = "0.3" criterion = "0.3"
[profile.dev]
debug = false
[profile.release] [profile.release]
lto = true lto = true
opt-level = 3 opt-level = 3

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changed
* Feature `cookies` is now optional and disabled by default. [#1981]
[#1981]: https://github.com/actix/actix-web/pull/1981
## 3.0.0-beta.3 - 2021-02-10 ## 3.0.0-beta.3 - 2021-02-10

View File

@ -15,7 +15,8 @@ license = "MIT OR Apache-2.0"
edition = "2018" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "compress", "secure-cookies"] # features that docs.rs will build with
features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"]
[lib] [lib]
name = "actix_http" name = "actix_http"
@ -30,11 +31,14 @@ openssl = ["actix-tls/openssl"]
# rustls support # rustls support
rustls = ["actix-tls/rustls"] rustls = ["actix-tls/rustls"]
# enable compressison support # enable compression support
compress = ["flate2", "brotli2"] compress = ["flate2", "brotli2"]
# support for cookies
cookies = ["cookie"]
# support for secure cookies # support for secure cookies
secure-cookies = ["cookie/secure"] secure-cookies = ["cookies", "cookie/secure"]
# trust-dns as client dns resolver # trust-dns as client dns resolver
trust-dns = ["trust-dns-resolver"] trust-dns = ["trust-dns-resolver"]
@ -46,24 +50,25 @@ actix-utils = "3.0.0-beta.2"
actix-rt = "2" actix-rt = "2"
actix-tls = "3.0.0-beta.2" actix-tls = "3.0.0-beta.2"
ahash = "0.7"
base64 = "0.13" base64 = "0.13"
bitflags = "1.2" bitflags = "1.2"
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
cookie = { version = "0.14.1", features = ["percent-encode"] } cfg-if = "1"
cookie = { version = "0.14.1", features = ["percent-encode"], optional = true }
derive_more = "0.99.5" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
ahash = "0.7"
h2 = "0.3.0" h2 = "0.3.0"
http = "0.2.2" http = "0.2.2"
httparse = "1.3" httparse = "1.3"
indexmap = "1.3" indexmap = "1.3"
itoa = "0.4" itoa = "0.4"
lazy_static = "1.4"
language-tags = "0.2" language-tags = "0.2"
lazy_static = "1.4"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
@ -72,10 +77,10 @@ rand = "0.8"
regex = "1.3" regex = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
sha-1 = "0.9"
smallvec = "1.6"
slab = "0.4"
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
sha-1 = "0.9"
slab = "0.4"
smallvec = "1.6"
time = { version = "0.2.23", default-features = false, features = ["std"] } time = { version = "0.2.23", default-features = false, features = ["std"] }
# compression # compression

View File

@ -19,10 +19,12 @@ use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError; use serde_urlencoded::ser::Error as FormError;
use crate::body::Body; use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer; use crate::helpers::Writer;
use crate::response::{Response, ResponseBuilder}; use crate::response::{Response, ResponseBuilder};
#[cfg(feature = "cookies")]
pub use crate::cookie::ParseError as CookieParseError;
/// A specialized [`std::result::Result`] /// A specialized [`std::result::Result`]
/// for actix web operations /// for actix web operations
/// ///
@ -397,6 +399,7 @@ impl ResponseError for PayloadError {
} }
/// Return `BadRequest` for `cookie::ParseError` /// Return `BadRequest` for `cookie::ParseError`
#[cfg(feature = "cookies")]
impl ResponseError for crate::cookie::ParseError { impl ResponseError for crate::cookie::ParseError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST StatusCode::BAD_REQUEST

View File

@ -549,7 +549,6 @@ mod tests {
); );
let data = let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
eprintln!("{}", &data);
assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Connection: close\r\n"));

View File

@ -5,12 +5,14 @@ use encoding_rs::{Encoding, UTF_8};
use http::header; use http::header;
use mime::Mime; use mime::Mime;
use crate::cookie::Cookie; use crate::error::{ContentTypeError, ParseError};
use crate::error::{ContentTypeError, CookieParseError, ParseError};
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::{Header, HeaderMap}; use crate::header::{Header, HeaderMap};
use crate::payload::Payload; use crate::payload::Payload;
#[cfg(feature = "cookies")]
use crate::{cookie::Cookie, error::CookieParseError};
#[cfg(feature = "cookies")]
struct Cookies(Vec<Cookie<'static>>); struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on HTTP messages. /// Trait that implements general purpose operations on HTTP messages.
@ -104,7 +106,7 @@ pub trait HttpMessage: Sized {
} }
/// Load request cookies. /// Load request cookies.
#[inline] #[cfg(feature = "cookies")]
fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> { fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() { if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new(); let mut cookies = Vec::new();
@ -119,12 +121,14 @@ pub trait HttpMessage: Sized {
} }
self.extensions_mut().insert(Cookies(cookies)); self.extensions_mut().insert(Cookies(cookies));
} }
Ok(Ref::map(self.extensions(), |ext| { Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0 &ext.get::<Cookies>().unwrap().0
})) }))
} }
/// Return request cookie. /// Return request cookie.
#[cfg(feature = "cookies")]
fn cookie(&self, name: &str) -> Option<Cookie<'static>> { fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() { if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() { for cookie in cookies.iter() {

View File

@ -1,4 +1,19 @@
//! HTTP primitives for the Actix ecosystem. //! HTTP primitives for the Actix ecosystem.
//!
//! ## Crate Features
//! | Feature | Functionality |
//! | ---------------- | ----------------------------------------------------- |
//! | `openssl` | TLS support via [OpenSSL]. |
//! | `rustls` | TLS support via [rustls]. |
//! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) |
//! | `cookies` | Support for cookies backed by the [cookie] crate. |
//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. |
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
//!
//! [OpenSSL]: https://crates.io/crates/openssl
//! [rustls]: https://crates.io/crates/rustls
//! [cookie]: https://crates.io/crates/cookie
//! [trust-dns]: https://crates.io/crates/trust-dns
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![allow( #![allow(
@ -34,13 +49,15 @@ mod response;
mod service; mod service;
mod time_parser; mod time_parser;
pub use cookie;
pub mod error; pub mod error;
pub mod h1; pub mod h1;
pub mod h2; pub mod h2;
pub mod test; pub mod test;
pub mod ws; pub mod ws;
#[cfg(feature = "cookies")]
pub use cookie;
pub use self::builder::HttpServiceBuilder; pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig}; pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::{Error, ResponseError, Result}; pub use self::error::{Error, ResponseError, Result};
@ -61,6 +78,7 @@ pub mod http {
pub use http::{uri, Error, Uri}; pub use http::{uri, Error, Uri};
pub use http::{Method, StatusCode, Version}; pub use http::{Method, StatusCode, Version};
#[cfg(feature = "cookies")]
pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::cookie::{Cookie, CookieBuilder};
pub use crate::header::HeaderMap; pub use crate::header::HeaderMap;

View File

@ -16,13 +16,17 @@ use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
use crate::cookie::{Cookie, CookieJar};
use crate::error::Error; use crate::error::Error;
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::{IntoHeaderPair, IntoHeaderValue}; use crate::header::{IntoHeaderPair, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::header::{self, HeaderName};
use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
#[cfg(feature = "cookies")]
use crate::{
cookie::{Cookie, CookieJar},
http::header::HeaderValue,
};
/// An HTTP Response /// An HTTP Response
pub struct Response<B = Body> { pub struct Response<B = Body> {
@ -133,6 +137,7 @@ impl<B> Response<B> {
} }
/// Get an iterator for the cookies set by this response /// Get an iterator for the cookies set by this response
#[cfg(feature = "cookies")]
#[inline] #[inline]
pub fn cookies(&self) -> CookieIter<'_> { pub fn cookies(&self) -> CookieIter<'_> {
CookieIter { CookieIter {
@ -141,6 +146,7 @@ impl<B> Response<B> {
} }
/// Add a cookie to this response /// Add a cookie to this response
#[cfg(feature = "cookies")]
#[inline] #[inline]
pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
let h = &mut self.head.headers; let h = &mut self.head.headers;
@ -153,6 +159,7 @@ impl<B> Response<B> {
/// Remove all cookies with the given name from this response. Returns /// Remove all cookies with the given name from this response. Returns
/// the number of cookies removed. /// the number of cookies removed.
#[cfg(feature = "cookies")]
#[inline] #[inline]
pub fn del_cookie(&mut self, name: &str) -> usize { pub fn del_cookie(&mut self, name: &str) -> usize {
let h = &mut self.head.headers; let h = &mut self.head.headers;
@ -298,10 +305,12 @@ impl Future for Response {
} }
} }
#[cfg(feature = "cookies")]
pub struct CookieIter<'a> { pub struct CookieIter<'a> {
iter: header::GetAll<'a>, iter: header::GetAll<'a>,
} }
#[cfg(feature = "cookies")]
impl<'a> Iterator for CookieIter<'a> { impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>; type Item = Cookie<'a>;
@ -316,13 +325,13 @@ impl<'a> Iterator for CookieIter<'a> {
} }
} }
/// An HTTP response builder /// An HTTP response builder.
/// ///
/// This type can be used to construct an instance of `Response` through a /// This type can be used to construct an instance of `Response` through a builder-like pattern.
/// builder-like pattern.
pub struct ResponseBuilder { pub struct ResponseBuilder {
head: Option<BoxedResponseHead>, head: Option<BoxedResponseHead>,
err: Option<HttpError>, err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
} }
@ -333,6 +342,7 @@ impl ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: Some(BoxedResponseHead::new(status)), head: Some(BoxedResponseHead::new(status)),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: None, cookies: None,
} }
} }
@ -531,6 +541,7 @@ impl ResponseBuilder {
/// .finish() /// .finish()
/// } /// }
/// ``` /// ```
#[cfg(feature = "cookies")]
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
if self.cookies.is_none() { if self.cookies.is_none() {
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();
@ -557,6 +568,7 @@ impl ResponseBuilder {
/// builder.finish() /// builder.finish()
/// } /// }
/// ``` /// ```
#[cfg(feature = "cookies")]
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
if self.cookies.is_none() { if self.cookies.is_none() {
self.cookies = Some(CookieJar::new()) self.cookies = Some(CookieJar::new())
@ -624,8 +636,11 @@ impl ResponseBuilder {
return Response::from(Error::from(e)).into_body(); return Response::from(Error::from(e)).into_body();
} }
// allow unused mut when cookies feature is disabled
#[allow(unused_mut)]
let mut response = self.head.take().expect("cannot reuse response builder"); let mut response = self.head.take().expect("cannot reuse response builder");
#[cfg(feature = "cookies")]
if let Some(ref jar) = self.cookies { if let Some(ref jar) = self.cookies {
for cookie in jar.delta() { for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) { match HeaderValue::from_str(&cookie.to_string()) {
@ -693,6 +708,7 @@ impl ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: self.head.take(), head: self.head.take(),
err: self.err.take(), err: self.err.take(),
#[cfg(feature = "cookies")]
cookies: self.cookies.take(), cookies: self.cookies.take(),
} }
} }
@ -712,21 +728,28 @@ fn parts<'a>(
/// Convert `Response` to a `ResponseBuilder`. Body get dropped. /// Convert `Response` to a `ResponseBuilder`. Body get dropped.
impl<B> From<Response<B>> for ResponseBuilder { impl<B> From<Response<B>> for ResponseBuilder {
fn from(res: Response<B>) -> ResponseBuilder { fn from(res: Response<B>) -> ResponseBuilder {
// If this response has cookies, load them into a jar #[cfg(feature = "cookies")]
let mut jar: Option<CookieJar> = None; let jar = {
for c in res.cookies() { // If this response has cookies, load them into a jar
if let Some(ref mut j) = jar { let mut jar: Option<CookieJar> = None;
j.add_original(c.into_owned());
} else { for c in res.cookies() {
let mut j = CookieJar::new(); if let Some(ref mut j) = jar {
j.add_original(c.into_owned()); j.add_original(c.into_owned());
jar = Some(j); } else {
let mut j = CookieJar::new();
j.add_original(c.into_owned());
jar = Some(j);
}
} }
}
jar
};
ResponseBuilder { ResponseBuilder {
head: Some(res.head), head: Some(res.head),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: jar, cookies: jar,
} }
} }
@ -735,22 +758,6 @@ impl<B> From<Response<B>> for ResponseBuilder {
/// Convert `ResponseHead` to a `ResponseBuilder` /// Convert `ResponseHead` to a `ResponseBuilder`
impl<'a> From<&'a ResponseHead> for ResponseBuilder { impl<'a> From<&'a ResponseHead> for ResponseBuilder {
fn from(head: &'a ResponseHead) -> ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE),
};
for c in cookies {
if let Some(ref mut j) = jar {
j.add_original(c.into_owned());
} else {
let mut j = CookieJar::new();
j.add_original(c.into_owned());
jar = Some(j);
}
}
let mut msg = BoxedResponseHead::new(head.status); let mut msg = BoxedResponseHead::new(head.status);
msg.version = head.version; msg.version = head.version;
msg.reason = head.reason; msg.reason = head.reason;
@ -761,9 +768,32 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
msg.no_chunking(!head.chunked()); msg.no_chunking(!head.chunked());
#[cfg(feature = "cookies")]
let jar = {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE),
};
for c in cookies {
if let Some(ref mut j) = jar {
j.add_original(c.into_owned());
} else {
let mut j = CookieJar::new();
j.add_original(c.into_owned());
jar = Some(j);
}
}
jar
};
ResponseBuilder { ResponseBuilder {
head: Some(msg), head: Some(msg),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: jar, cookies: jar,
} }
} }

View File

@ -11,13 +11,14 @@ use std::{
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::{ use http::{Method, Uri, Version};
header::{self, HeaderValue},
Method, Uri, Version,
};
#[cfg(feature = "cookies")]
use crate::{ use crate::{
cookie::{Cookie, CookieJar}, cookie::{Cookie, CookieJar},
header::{self, HeaderValue},
};
use crate::{
header::{HeaderMap, IntoHeaderPair}, header::{HeaderMap, IntoHeaderPair},
payload::Payload, payload::Payload,
Request, Request,
@ -53,6 +54,7 @@ struct Inner {
method: Method, method: Method,
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
#[cfg(feature = "cookies")]
cookies: CookieJar, cookies: CookieJar,
payload: Option<Payload>, payload: Option<Payload>,
} }
@ -64,6 +66,7 @@ impl Default for TestRequest {
uri: Uri::from_str("/").unwrap(), uri: Uri::from_str("/").unwrap(),
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::new(), headers: HeaderMap::new(),
#[cfg(feature = "cookies")]
cookies: CookieJar::new(), cookies: CookieJar::new(),
payload: None, payload: None,
})) }))
@ -132,6 +135,7 @@ impl TestRequest {
} }
/// Set cookie for this request. /// Set cookie for this request.
#[cfg(feature = "cookies")]
pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self {
parts(&mut self.0).cookies.add(cookie.into_owned()); parts(&mut self.0).cookies.add(cookie.into_owned());
self self
@ -165,17 +169,20 @@ impl TestRequest {
head.version = inner.version; head.version = inner.version;
head.headers = inner.headers; head.headers = inner.headers;
let cookie: String = inner #[cfg(feature = "cookies")]
.cookies {
.delta() let cookie: String = inner
// ensure only name=value is written to cookie header .cookies
.map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) .delta()
.collect::<Vec<_>>() // ensure only name=value is written to cookie header
.join("; "); .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string())
.collect::<Vec<_>>()
.join("; ");
if !cookie.is_empty() { if !cookie.is_empty() {
head.headers head.headers
.insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap());
}
} }
req req

View File

@ -1,6 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changed
* Feature `cookies` is now optional and enabled by default. [#1981]
[#1981]: https://github.com/actix/actix-web/pull/1981
## 3.0.0-beta.2 - 2021-02-10 ## 3.0.0-beta.2 - 2021-02-10

View File

@ -22,10 +22,11 @@ name = "awc"
path = "src/lib.rs" path = "src/lib.rs"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["openssl", "rustls", "compress"] # features that docs.rs will build with
features = ["openssl", "rustls", "compress", "cookies"]
[features] [features]
default = ["compress"] default = ["compress", "cookies"]
# openssl # openssl
openssl = ["tls-openssl", "actix-http/openssl"] openssl = ["tls-openssl", "actix-http/openssl"]
@ -36,6 +37,9 @@ rustls = ["tls-rustls", "actix-http/rustls"]
# content-encoding support # content-encoding support
compress = ["actix-http/compress"] compress = ["actix-http/compress"]
# cookie parsing and cookie jar
cookies = ["actix-http/cookies"]
# trust-dns as dns resolver # trust-dns as dns resolver
trust-dns = ["actix-http/trust-dns"] trust-dns = ["actix-http/trust-dns"]

View File

@ -97,7 +97,9 @@ use std::convert::TryFrom;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
pub use actix_http::{client::Connector, cookie, http}; #[cfg(feature = "cookies")]
pub use actix_http::cookie;
pub use actix_http::{client::Connector, http};
use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri};
use actix_http::RequestHead; use actix_http::RequestHead;

View File

@ -8,6 +8,7 @@ use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
use actix_http::body::Body; use actix_http::body::Body;
#[cfg(feature = "cookies")]
use actix_http::cookie::{Cookie, CookieJar}; use actix_http::cookie::{Cookie, CookieJar};
use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::header::{self, IntoHeaderPair};
use actix_http::http::{ use actix_http::http::{
@ -54,10 +55,12 @@ pub struct ClientRequest {
pub(crate) head: RequestHead, pub(crate) head: RequestHead,
err: Option<HttpError>, err: Option<HttpError>,
addr: Option<net::SocketAddr>, addr: Option<net::SocketAddr>,
cookies: Option<CookieJar>,
response_decompress: bool, response_decompress: bool,
timeout: Option<Duration>, timeout: Option<Duration>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
} }
impl ClientRequest { impl ClientRequest {
@ -72,6 +75,7 @@ impl ClientRequest {
head: RequestHead::default(), head: RequestHead::default(),
err: None, err: None,
addr: None, addr: None,
#[cfg(feature = "cookies")]
cookies: None, cookies: None,
timeout: None, timeout: None,
response_decompress: true, response_decompress: true,
@ -290,6 +294,7 @@ impl ClientRequest {
/// println!("Response: {:?}", resp); /// println!("Response: {:?}", resp);
/// } /// }
/// ``` /// ```
#[cfg(feature = "cookies")]
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
if self.cookies.is_none() { if self.cookies.is_none() {
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();
@ -472,7 +477,8 @@ impl ClientRequest {
) )
} }
fn prep_for_sending(mut self) -> Result<Self, PrepForSendingError> { // allow unused mut when cookies feature is disabled
fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result<Self, PrepForSendingError> {
if let Some(e) = self.err { if let Some(e) = self.err {
return Err(e.into()); return Err(e.into());
} }
@ -493,6 +499,7 @@ impl ClientRequest {
} }
// set cookies // set cookies
#[cfg(feature = "cookies")]
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
let cookie: String = jar let cookie: String = jar
.delta() .delta()

View File

@ -1,20 +1,25 @@
use std::cell::{Ref, RefMut};
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{
cell::{Ref, RefMut},
mem,
};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use actix_http::cookie::Cookie; use actix_http::error::PayloadError;
use actix_http::error::{CookieParseError, PayloadError}; use actix_http::http::header;
use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE};
use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::http::{HeaderMap, StatusCode, Version};
use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
#[cfg(feature = "cookies")]
use actix_http::{cookie::Cookie, error::CookieParseError};
use crate::error::JsonPayloadError; use crate::error::JsonPayloadError;
/// Client Response /// Client Response
@ -39,17 +44,17 @@ impl<S> HttpMessage for ClientResponse<S> {
} }
fn take_payload(&mut self) -> Payload<S> { fn take_payload(&mut self) -> Payload<S> {
std::mem::replace(&mut self.payload, Payload::None) mem::replace(&mut self.payload, Payload::None)
} }
/// Load request cookies. /// Load request cookies.
#[inline] #[cfg(feature = "cookies")]
fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> { fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
struct Cookies(Vec<Cookie<'static>>); struct Cookies(Vec<Cookie<'static>>);
if self.extensions().get::<Cookies>().is_none() { if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new(); let mut cookies = Vec::new();
for hdr in self.headers().get_all(&SET_COOKIE) { for hdr in self.headers().get_all(&header::SET_COOKIE) {
let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
cookies.push(Cookie::parse_encoded(s)?.into_owned()); cookies.push(Cookie::parse_encoded(s)?.into_owned());
} }
@ -161,7 +166,7 @@ where
/// Create `MessageBody` for request. /// Create `MessageBody` for request.
pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> { pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> {
let mut len = None; let mut len = None;
if let Some(l) = res.headers().get(&CONTENT_LENGTH) { if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() { if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() { if let Ok(l) = s.parse::<usize>() {
len = Some(l) len = Some(l)
@ -256,7 +261,7 @@ where
} }
let mut len = None; let mut len = None;
if let Some(l) = req.headers().get(&CONTENT_LENGTH) { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() { if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() { if let Ok(l) = s.parse::<usize>() {
len = Some(l) len = Some(l)

View File

@ -1,9 +1,13 @@
//! Test helpers for actix http client to use during testing. //! Test helpers for actix http client to use during testing.
use std::convert::TryFrom; use std::convert::TryFrom;
use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{Header, IntoHeaderValue};
use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue};
use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version};
#[cfg(feature = "cookies")]
use actix_http::{
cookie::{Cookie, CookieJar},
http::header::{self, HeaderValue},
};
use actix_http::{h1, Payload, ResponseHead}; use actix_http::{h1, Payload, ResponseHead};
use bytes::Bytes; use bytes::Bytes;
@ -12,6 +16,7 @@ use crate::ClientResponse;
/// Test `ClientResponse` builder /// Test `ClientResponse` builder
pub struct TestResponse { pub struct TestResponse {
head: ResponseHead, head: ResponseHead,
#[cfg(feature = "cookies")]
cookies: CookieJar, cookies: CookieJar,
payload: Option<Payload>, payload: Option<Payload>,
} }
@ -20,6 +25,7 @@ impl Default for TestResponse {
fn default() -> TestResponse { fn default() -> TestResponse {
TestResponse { TestResponse {
head: ResponseHead::new(StatusCode::OK), head: ResponseHead::new(StatusCode::OK),
#[cfg(feature = "cookies")]
cookies: CookieJar::new(), cookies: CookieJar::new(),
payload: None, payload: None,
} }
@ -69,6 +75,7 @@ impl TestResponse {
} }
/// Set cookie for this response /// Set cookie for this response
#[cfg(feature = "cookies")]
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
self.cookies.add(cookie.into_owned()); self.cookies.add(cookie.into_owned());
self self
@ -84,8 +91,11 @@ impl TestResponse {
/// Complete response creation and generate `ClientResponse` instance /// Complete response creation and generate `ClientResponse` instance
pub fn finish(self) -> ClientResponse { pub fn finish(self) -> ClientResponse {
// allow unused mut when cookies feature is disabled
#[allow(unused_mut)]
let mut head = self.head; let mut head = self.head;
#[cfg(feature = "cookies")]
for cookie in self.cookies.delta() { for cookie in self.cookies.delta() {
head.headers.insert( head.headers.insert(
header::SET_COOKIE, header::SET_COOKIE,

View File

@ -32,6 +32,7 @@ use std::rc::Rc;
use std::{fmt, str}; use std::{fmt, str};
use actix_codec::Framed; use actix_codec::Framed;
#[cfg(feature = "cookies")]
use actix_http::cookie::{Cookie, CookieJar}; use actix_http::cookie::{Cookie, CookieJar};
use actix_http::{ws, Payload, RequestHead}; use actix_http::{ws, Payload, RequestHead};
use actix_rt::time::timeout; use actix_rt::time::timeout;
@ -54,8 +55,10 @@ pub struct WebsocketsRequest {
addr: Option<SocketAddr>, addr: Option<SocketAddr>,
max_size: usize, max_size: usize,
server_mode: bool, server_mode: bool,
cookies: Option<CookieJar>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
} }
impl WebsocketsRequest { impl WebsocketsRequest {
@ -89,6 +92,7 @@ impl WebsocketsRequest {
protocols: None, protocols: None,
max_size: 65_536, max_size: 65_536,
server_mode: false, server_mode: false,
#[cfg(feature = "cookies")]
cookies: None, cookies: None,
} }
} }
@ -117,6 +121,7 @@ impl WebsocketsRequest {
} }
/// Set a cookie /// Set a cookie
#[cfg(feature = "cookies")]
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
if self.cookies.is_none() { if self.cookies.is_none() {
let mut jar = CookieJar::new(); let mut jar = CookieJar::new();
@ -270,6 +275,7 @@ impl WebsocketsRequest {
} }
// set cookies // set cookies
#[cfg(feature = "cookies")]
if let Some(ref mut jar) = self.cookies { if let Some(ref mut jar) = self.cookies {
let cookie: String = jar let cookie: String = jar
.delta() .delta()

View File

@ -13,9 +13,11 @@ pub enum UrlGenerationError {
/// Resource not found /// Resource not found
#[display(fmt = "Resource not found")] #[display(fmt = "Resource not found")]
ResourceNotFound, ResourceNotFound,
/// Not all path pattern covered /// Not all path pattern covered
#[display(fmt = "Not all path pattern covered")] #[display(fmt = "Not all path pattern covered")]
NotEnoughElements, NotEnoughElements,
/// URL parse error /// URL parse error
#[display(fmt = "{}", _0)] #[display(fmt = "{}", _0)]
ParseError(UrlParseError), ParseError(UrlParseError),

View File

@ -61,6 +61,7 @@
//! ## Crate Features //! ## Crate Features
//! //!
//! * `compress` - content encoding compression support (enabled by default) //! * `compress` - content encoding compression support (enabled by default)
//! * `cookies` - cookies support (enabled by default)
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` //! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
//! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
//! * `secure-cookies` - secure cookies support //! * `secure-cookies` - secure cookies support

View File

@ -806,15 +806,15 @@ async fn test_server_cookies() {
})) }))
}); });
let req = srv.get("/");
let res = req.send().await.unwrap();
assert!(res.status().is_success());
let first_cookie = http::CookieBuilder::new("first", "first_value") let first_cookie = http::CookieBuilder::new("first", "first_value")
.http_only(true) .http_only(true)
.finish(); .finish();
let second_cookie = http::Cookie::new("second", "second_value"); let second_cookie = http::Cookie::new("second", "second_value");
let req = srv.get("/");
let res = req.send().await.unwrap();
assert!(res.status().is_success());
let cookies = res.cookies().expect("To have cookies"); let cookies = res.cookies().expect("To have cookies");
assert_eq!(cookies.len(), 2); assert_eq!(cookies.len(), 2);
if cookies[0] == first_cookie { if cookies[0] == first_cookie {