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

fork cookie crate

This commit is contained in:
Nikolay Kim 2019-03-29 21:13:39 -07:00
parent 193f8fb2d9
commit d846328f36
40 changed files with 3357 additions and 253 deletions

View File

@ -35,10 +35,10 @@ members = [
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "tls", "brotli", "flate2-zlib", "cookies", "client", "rust-tls"] features = ["ssl", "tls", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
[features] [features]
default = ["brotli", "flate2-zlib", "cookies", "client"] default = ["brotli", "flate2-zlib", "secure-cookies", "client"]
# http client # http client
client = ["awc"] client = ["awc"]
@ -53,7 +53,7 @@ flate2-zlib = ["actix-http/flate2-zlib"]
flate2-rust = ["actix-http/flate2-rust"] flate2-rust = ["actix-http/flate2-rust"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
cookies = ["cookie", "actix-http/cookies"] secure-cookies = ["actix-http/secure-cookies"]
# tls # tls
tls = ["native-tls", "actix-server/ssl"] tls = ["native-tls", "actix-server/ssl"]
@ -94,9 +94,6 @@ serde_urlencoded = "^0.5.3"
time = "0.1" time = "0.1"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
# cookies support
cookie = { version="0.11", features=["secure", "percent-encode"], optional = true }
# ssl support # ssl support
native-tls = { version="0.2", optional = true } native-tls = { version="0.2", optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
@ -112,9 +109,6 @@ tokio-timer = "0.2.8"
brotli2 = "0.3.2" brotli2 = "0.3.2"
flate2 = "1.0.2" flate2 = "1.0.2"
[replace]
"cookie:0.11.0" = { git = 'https://github.com/alexcrichton/cookie-rs.git' }
[profile.release] [profile.release]
lto = true lto = true
opt-level = 3 opt-level = 3

View File

@ -18,8 +18,8 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.1" #actix-web = "1.0.0-alpha.1"
actix-http = "0.1.0-alpha.1" actix-web = { path = ".." }
actix-service = "0.3.3" actix-service = "0.3.3"
bitflags = "1" bitflags = "1"
@ -33,4 +33,5 @@ percent-encoding = "1.0"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0-alpha.1", features=["ssl"] } #actix-web = { version = "1.0.0-alpha.1", features=["ssl"] }
actix-web = { path = "..", features=["ssl"] }

View File

@ -16,7 +16,7 @@ edition = "2018"
workspace = ".." workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "fail", "cookie", "brotli", "flate2-zlib"] features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
[badges] [badges]
travis-ci = { repository = "actix/actix-web", branch = "master" } travis-ci = { repository = "actix/actix-web", branch = "master" }
@ -32,9 +32,6 @@ default = []
# openssl # openssl
ssl = ["openssl", "actix-connect/ssl"] ssl = ["openssl", "actix-connect/ssl"]
# cookies integration
cookies = ["cookie"]
# brotli encoding, requires c compiler # brotli encoding, requires c compiler
brotli = ["brotli2"] brotli = ["brotli2"]
@ -47,6 +44,9 @@ flate2-rust = ["flate2/rust_backend"]
# failure integration. actix does not use failure anymore # failure integration. actix does not use failure anymore
fail = ["failure"] fail = ["failure"]
# support for secure cookies
secure-cookies = ["ring"]
[dependencies] [dependencies]
actix-service = "0.3.4" actix-service = "0.3.4"
actix-codec = "0.1.2" actix-codec = "0.1.2"
@ -85,12 +85,14 @@ tokio-timer = "0.2"
tokio-current-thread = "0.1" tokio-current-thread = "0.1"
trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false }
# for secure cookie
ring = { version = "0.14.6", optional = true }
# compression # compression
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="^1.0.2", optional = true, default-features = false } flate2 = { version="^1.0.2", optional = true, default-features = false }
# optional deps # optional deps
cookie = { version="0.11", features=["percent-encode"], optional = true }
failure = { version = "0.1.5", optional = true } failure = { version = "0.1.5", optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }

View File

@ -184,11 +184,11 @@ where
match self { match self {
EitherConnection::A(con) => Box::new( EitherConnection::A(con) => Box::new(
con.open_tunnel(head) con.open_tunnel(head)
.map(|(head, framed)| (head, framed.map_io(|io| EitherIo::A(io)))), .map(|(head, framed)| (head, framed.map_io(EitherIo::A))),
), ),
EitherConnection::B(con) => Box::new( EitherConnection::B(con) => Box::new(
con.open_tunnel(head) con.open_tunnel(head)
.map(|(head, framed)| (head, framed.map_io(|io| EitherIo::B(io)))), .map(|(head, framed)| (head, framed.map_io(EitherIo::B))),
), ),
} }
} }

View File

@ -0,0 +1,240 @@
use std::borrow::Cow;
use time::{Duration, Tm};
use super::{Cookie, SameSite};
/// Structure that follows the builder pattern for building `Cookie` structs.
///
/// To construct a cookie:
///
/// 1. Call [`Cookie::build`](struct.Cookie.html#method.build) to start building.
/// 2. Use any of the builder methods to set fields in the cookie.
/// 3. Call [finish](#method.finish) to retrieve the built cookie.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
/// use time::Duration;
///
/// # fn main() {
/// let cookie: Cookie = Cookie::build("name", "value")
/// .domain("www.rust-lang.org")
/// .path("/")
/// .secure(true)
/// .http_only(true)
/// .max_age(Duration::days(1))
/// .finish();
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct CookieBuilder {
/// The cookie being built.
cookie: Cookie<'static>,
}
impl CookieBuilder {
/// Creates a new `CookieBuilder` instance from the given name and value.
///
/// This method is typically called indirectly via
/// [Cookie::build](struct.Cookie.html#method.build).
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar").finish();
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// ```
pub fn new<N, V>(name: N, value: V) -> CookieBuilder
where
N: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
CookieBuilder {
cookie: Cookie::new(name, value),
}
}
/// Sets the `expires` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .expires(time::now())
/// .finish();
///
/// assert!(c.expires().is_some());
/// # }
/// ```
#[inline]
pub fn expires(mut self, when: Tm) -> CookieBuilder {
self.cookie.set_expires(when);
self
}
/// Sets the `max_age` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .max_age(time::Duration::minutes(30))
/// .finish();
///
/// assert_eq!(c.max_age(), Some(time::Duration::seconds(30 * 60)));
/// # }
/// ```
#[inline]
pub fn max_age(mut self, value: Duration) -> CookieBuilder {
self.cookie.set_max_age(value);
self
}
/// Sets the `domain` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .domain("www.rust-lang.org")
/// .finish();
///
/// assert_eq!(c.domain(), Some("www.rust-lang.org"));
/// ```
pub fn domain<D: Into<Cow<'static, str>>>(mut self, value: D) -> CookieBuilder {
self.cookie.set_domain(value);
self
}
/// Sets the `path` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .path("/")
/// .finish();
///
/// assert_eq!(c.path(), Some("/"));
/// ```
pub fn path<P: Into<Cow<'static, str>>>(mut self, path: P) -> CookieBuilder {
self.cookie.set_path(path);
self
}
/// Sets the `secure` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .secure(true)
/// .finish();
///
/// assert_eq!(c.secure(), Some(true));
/// ```
#[inline]
pub fn secure(mut self, value: bool) -> CookieBuilder {
self.cookie.set_secure(value);
self
}
/// Sets the `http_only` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .http_only(true)
/// .finish();
///
/// assert_eq!(c.http_only(), Some(true));
/// ```
#[inline]
pub fn http_only(mut self, value: bool) -> CookieBuilder {
self.cookie.set_http_only(value);
self
}
/// Sets the `same_site` field in the cookie being built.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, SameSite};
///
/// let c = Cookie::build("foo", "bar")
/// .same_site(SameSite::Strict)
/// .finish();
///
/// assert_eq!(c.same_site(), Some(SameSite::Strict));
/// ```
#[inline]
pub fn same_site(mut self, value: SameSite) -> CookieBuilder {
self.cookie.set_same_site(value);
self
}
/// Makes the cookie being built 'permanent' by extending its expiration and
/// max age 20 years into the future.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
/// use time::Duration;
///
/// # fn main() {
/// let c = Cookie::build("foo", "bar")
/// .permanent()
/// .finish();
///
/// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
/// # assert!(c.expires().is_some());
/// # }
/// ```
#[inline]
pub fn permanent(mut self) -> CookieBuilder {
self.cookie.make_permanent();
self
}
/// Finishes building and returns the built `Cookie`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Cookie;
///
/// let c = Cookie::build("foo", "bar")
/// .domain("crates.io")
/// .path("/")
/// .finish();
///
/// assert_eq!(c.name_value(), ("foo", "bar"));
/// assert_eq!(c.domain(), Some("crates.io"));
/// assert_eq!(c.path(), Some("/"));
/// ```
#[inline]
pub fn finish(self) -> Cookie<'static> {
self.cookie
}
}

View File

@ -0,0 +1,71 @@
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use super::Cookie;
/// A `DeltaCookie` is a helper structure used in a cookie jar. It wraps a
/// `Cookie` so that it can be hashed and compared purely by name. It further
/// records whether the wrapped cookie is a "removal" cookie, that is, a cookie
/// that when sent to the client removes the named cookie on the client's
/// machine.
#[derive(Clone, Debug)]
pub struct DeltaCookie {
pub cookie: Cookie<'static>,
pub removed: bool,
}
impl DeltaCookie {
/// Create a new `DeltaCookie` that is being added to a jar.
#[inline]
pub fn added(cookie: Cookie<'static>) -> DeltaCookie {
DeltaCookie {
cookie,
removed: false,
}
}
/// Create a new `DeltaCookie` that is being removed from a jar. The
/// `cookie` should be a "removal" cookie.
#[inline]
pub fn removed(cookie: Cookie<'static>) -> DeltaCookie {
DeltaCookie {
cookie,
removed: true,
}
}
}
impl Deref for DeltaCookie {
type Target = Cookie<'static>;
fn deref(&self) -> &Cookie<'static> {
&self.cookie
}
}
impl DerefMut for DeltaCookie {
fn deref_mut(&mut self) -> &mut Cookie<'static> {
&mut self.cookie
}
}
impl PartialEq for DeltaCookie {
fn eq(&self, other: &DeltaCookie) -> bool {
self.name() == other.name()
}
}
impl Eq for DeltaCookie {}
impl Hash for DeltaCookie {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name().hash(state);
}
}
impl Borrow<str> for DeltaCookie {
fn borrow(&self) -> &str {
self.name()
}
}

View File

@ -0,0 +1,98 @@
//! This module contains types that represent cookie properties that are not yet
//! standardized. That is, _draft_ features.
use std::fmt;
/// The `SameSite` cookie attribute.
///
/// A cookie with a `SameSite` attribute is imposed restrictions on when it is
/// sent to the origin server in a cross-site request. If the `SameSite`
/// attribute is "Strict", then the cookie is never sent in cross-site requests.
/// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site
/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`.
/// If the `SameSite` attribute is not present (made explicit via the
/// `SameSite::None` variant), then the cookie will be sent as normal.
///
/// **Note:** This cookie attribute is an HTTP draft! Its meaning and definition
/// are subject to change.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SameSite {
/// The "Strict" `SameSite` attribute.
Strict,
/// The "Lax" `SameSite` attribute.
Lax,
/// No `SameSite` attribute.
None,
}
impl SameSite {
/// Returns `true` if `self` is `SameSite::Strict` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let strict = SameSite::Strict;
/// assert!(strict.is_strict());
/// assert!(!strict.is_lax());
/// assert!(!strict.is_none());
/// ```
#[inline]
pub fn is_strict(self) -> bool {
match self {
SameSite::Strict => true,
SameSite::Lax | SameSite::None => false,
}
}
/// Returns `true` if `self` is `SameSite::Lax` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let lax = SameSite::Lax;
/// assert!(lax.is_lax());
/// assert!(!lax.is_strict());
/// assert!(!lax.is_none());
/// ```
#[inline]
pub fn is_lax(self) -> bool {
match self {
SameSite::Lax => true,
SameSite::Strict | SameSite::None => false,
}
}
/// Returns `true` if `self` is `SameSite::None` and `false` otherwise.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::SameSite;
///
/// let none = SameSite::None;
/// assert!(none.is_none());
/// assert!(!none.is_lax());
/// assert!(!none.is_strict());
/// ```
#[inline]
pub fn is_none(self) -> bool {
match self {
SameSite::None => true,
SameSite::Lax | SameSite::Strict => false,
}
}
}
impl fmt::Display for SameSite {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
SameSite::Strict => write!(f, "Strict"),
SameSite::Lax => write!(f, "Lax"),
SameSite::None => Ok(()),
}
}
}

View File

@ -0,0 +1,653 @@
use std::collections::HashSet;
use std::mem::replace;
use time::{self, Duration};
use super::delta::DeltaCookie;
use super::Cookie;
#[cfg(feature = "secure-cookies")]
use super::secure::{Key, PrivateJar, SignedJar};
/// A collection of cookies that tracks its modifications.
///
/// A `CookieJar` provides storage for any number of cookies. Any changes made
/// to the jar are tracked; the changes can be retrieved via the
/// [delta](#method.delta) method which returns an interator over the changes.
///
/// # Usage
///
/// A jar's life begins via [new](#method.new) and calls to
/// [`add_original`](#method.add_original):
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "another"));
/// ```
///
/// Cookies can be added via [add](#method.add) and removed via
/// [remove](#method.remove). Finally, cookies can be looked up via
/// [get](#method.get):
///
/// ```rust
/// # use actix_http::cookie::{Cookie, CookieJar};
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("a", "one"));
/// jar.add(Cookie::new("b", "two"));
///
/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one"));
/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two"));
///
/// jar.remove(Cookie::named("b"));
/// assert!(jar.get("b").is_none());
/// ```
///
/// # Deltas
///
/// A jar keeps track of any modifications made to it over time. The
/// modifications are recorded as cookies. The modifications can be retrieved
/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add`
/// results in the same `Cookie` appearing in the `delta`; cookies added via
/// `add_original` do not count towards the delta. Any _original_ cookie that is
/// removed from a jar results in a "removal" cookie appearing in the delta. A
/// "removal" cookie is a cookie that a server sends so that the cookie is
/// removed from the client's machine.
///
/// Deltas are typically used to create `Set-Cookie` headers corresponding to
/// the changes made to a cookie jar over a period of time.
///
/// ```rust
/// # use actix_http::cookie::{Cookie, CookieJar};
/// let mut jar = CookieJar::new();
///
/// // original cookies don't affect the delta
/// jar.add_original(Cookie::new("original", "value"));
/// assert_eq!(jar.delta().count(), 0);
///
/// // new cookies result in an equivalent `Cookie` in the delta
/// jar.add(Cookie::new("a", "one"));
/// jar.add(Cookie::new("b", "two"));
/// assert_eq!(jar.delta().count(), 2);
///
/// // removing an original cookie adds a "removal" cookie to the delta
/// jar.remove(Cookie::named("original"));
/// assert_eq!(jar.delta().count(), 3);
///
/// // removing a new cookie that was added removes that `Cookie` from the delta
/// jar.remove(Cookie::named("a"));
/// assert_eq!(jar.delta().count(), 2);
/// ```
#[derive(Default, Debug, Clone)]
pub struct CookieJar {
original_cookies: HashSet<DeltaCookie>,
delta_cookies: HashSet<DeltaCookie>,
}
impl CookieJar {
/// Creates an empty cookie jar.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::CookieJar;
///
/// let jar = CookieJar::new();
/// assert_eq!(jar.iter().count(), 0);
/// ```
pub fn new() -> CookieJar {
CookieJar::default()
}
/// Returns a reference to the `Cookie` inside this jar with the name
/// `name`. If no such cookie exists, returns `None`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// assert!(jar.get("name").is_none());
///
/// jar.add(Cookie::new("name", "value"));
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// ```
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
self.delta_cookies
.get(name)
.or_else(|| self.original_cookies.get(name))
.and_then(|c| if !c.removed { Some(&c.cookie) } else { None })
}
/// Adds an "original" `cookie` to this jar. If an original cookie with the
/// same name already exists, it is replaced with `cookie`. Cookies added
/// with `add` take precedence and are not replaced by this method.
///
/// Adding an original cookie does not affect the [delta](#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
/// assert_eq!(jar.iter().count(), 2);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, cookie: Cookie<'static>) {
self.original_cookies.replace(DeltaCookie::added(cookie));
}
/// Adds `cookie` to this jar. If a cookie with the same name already
/// exists, it is replaced with `cookie`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("name", "value"));
/// jar.add(Cookie::new("second", "two"));
///
/// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
/// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
/// assert_eq!(jar.iter().count(), 2);
/// assert_eq!(jar.delta().count(), 2);
/// ```
pub fn add(&mut self, cookie: Cookie<'static>) {
self.delta_cookies.replace(DeltaCookie::added(cookie));
}
/// Removes `cookie` from this jar. If an _original_ cookie with the same
/// name as `cookie` is present in the jar, a _removal_ cookie will be
/// present in the `delta` computation. To properly generate the removal
/// cookie, `cookie` must contain the same `path` and `domain` as the cookie
/// that was initially set.
///
/// A "removal" cookie is a cookie that has the same name as the original
/// cookie but has an empty value, a max-age of 0, and an expiration date
/// far in the past.
///
/// # Example
///
/// Removing an _original_ cookie results in a _removal_ cookie:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
/// use time::Duration;
///
/// # fn main() {
/// let mut jar = CookieJar::new();
///
/// // Assume this cookie originally had a path of "/" and domain of "a.b".
/// jar.add_original(Cookie::new("name", "value"));
///
/// // If the path and domain were set, they must be provided to `remove`.
/// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish());
///
/// // The delta will contain the removal cookie.
/// let delta: Vec<_> = jar.delta().collect();
/// assert_eq!(delta.len(), 1);
/// assert_eq!(delta[0].name(), "name");
/// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
/// # }
/// ```
///
/// Removing a new cookie does not result in a _removal_ cookie:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add(Cookie::new("name", "value"));
/// assert_eq!(jar.delta().count(), 1);
///
/// jar.remove(Cookie::named("name"));
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn remove(&mut self, mut cookie: Cookie<'static>) {
if self.original_cookies.contains(cookie.name()) {
cookie.set_value("");
cookie.set_max_age(Duration::seconds(0));
cookie.set_expires(time::now() - Duration::days(365));
self.delta_cookies.replace(DeltaCookie::removed(cookie));
} else {
self.delta_cookies.remove(cookie.name());
}
}
/// Removes `cookie` from this jar completely. This method differs from
/// `remove` in that no delta cookie is created under any condition. Neither
/// the `delta` nor `iter` methods will return a cookie that is removed
/// using this method.
///
/// # Example
///
/// Removing an _original_ cookie; no _removal_ cookie is generated:
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
/// use time::Duration;
///
/// # fn main() {
/// let mut jar = CookieJar::new();
///
/// // Add an original cookie and a new cookie.
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 1);
/// assert_eq!(jar.iter().count(), 2);
///
/// // Now force remove the original cookie.
/// jar.force_remove(Cookie::new("name", "value"));
/// assert_eq!(jar.delta().count(), 1);
/// assert_eq!(jar.iter().count(), 1);
///
/// // Now force remove the new cookie.
/// jar.force_remove(Cookie::new("key", "value"));
/// assert_eq!(jar.delta().count(), 0);
/// assert_eq!(jar.iter().count(), 0);
/// # }
/// ```
pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) {
self.original_cookies.remove(cookie.name());
self.delta_cookies.remove(cookie.name());
}
/// Removes all cookies from this cookie jar.
#[deprecated(
since = "0.7.0",
note = "calling this method may not remove \
all cookies since the path and domain are not specified; use \
`remove` instead"
)]
pub fn clear(&mut self) {
self.delta_cookies.clear();
for delta in replace(&mut self.original_cookies, HashSet::new()) {
self.remove(delta.cookie);
}
}
/// Returns an iterator over cookies that represent the changes to this jar
/// over time. These cookies can be rendered directly as `Set-Cookie` header
/// values to affect the changes made to this jar on the client.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// // Add new cookies.
/// jar.add(Cookie::new("new", "third"));
/// jar.add(Cookie::new("another", "fourth"));
/// jar.add(Cookie::new("yac", "fifth"));
///
/// // Remove some cookies.
/// jar.remove(Cookie::named("name"));
/// jar.remove(Cookie::named("another"));
///
/// // Delta contains two new cookies ("new", "yac") and a removal ("name").
/// assert_eq!(jar.delta().count(), 3);
/// ```
pub fn delta(&self) -> Delta {
Delta {
iter: self.delta_cookies.iter(),
}
}
/// Returns an iterator over all of the cookies present in this jar.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie};
///
/// let mut jar = CookieJar::new();
///
/// jar.add_original(Cookie::new("name", "value"));
/// jar.add_original(Cookie::new("second", "two"));
///
/// jar.add(Cookie::new("new", "third"));
/// jar.add(Cookie::new("another", "fourth"));
/// jar.add(Cookie::new("yac", "fifth"));
///
/// jar.remove(Cookie::named("name"));
/// jar.remove(Cookie::named("another"));
///
/// // There are three cookies in the jar: "second", "new", and "yac".
/// # assert_eq!(jar.iter().count(), 3);
/// for cookie in jar.iter() {
/// match cookie.name() {
/// "second" => assert_eq!(cookie.value(), "two"),
/// "new" => assert_eq!(cookie.value(), "third"),
/// "yac" => assert_eq!(cookie.value(), "fifth"),
/// _ => unreachable!("there are only three cookies in the jar")
/// }
/// }
/// ```
pub fn iter(&self) -> Iter {
Iter {
delta_cookies: self
.delta_cookies
.iter()
.chain(self.original_cookies.difference(&self.delta_cookies)),
}
}
/// Returns a `PrivateJar` with `self` as its parent jar using the key `key`
/// to sign/encrypt and verify/decrypt cookies added/retrieved from the
/// child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar, Key};
///
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a private (signed + encrypted) cookie.
/// let mut jar = CookieJar::new();
/// jar.private(&key).add(Cookie::new("private", "text"));
///
/// // The cookie's contents are encrypted.
/// assert_ne!(jar.get("private").unwrap().value(), "text");
///
/// // They can be decrypted and verified through the child jar.
/// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text");
///
/// // A tampered with cookie does not validate but still exists.
/// let mut cookie = jar.get("private").unwrap().clone();
/// jar.add(Cookie::new("private", cookie.value().to_string() + "!"));
/// assert!(jar.private(&key).get("private").is_none());
/// assert!(jar.get("private").is_some());
/// ```
#[cfg(feature = "secure-cookies")]
pub fn private(&mut self, key: &Key) -> PrivateJar {
PrivateJar::new(self, key)
}
/// Returns a `SignedJar` with `self` as its parent jar using the key `key`
/// to sign/verify cookies added/retrieved from the child jar.
///
/// Any modifications to the child jar will be reflected on the parent jar,
/// and any retrievals from the child jar will be made from the parent jar.
///
/// This method is only available when the `secure` feature is enabled.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{Cookie, CookieJar, Key};
///
/// // Generate a secure key.
/// let key = Key::generate();
///
/// // Add a signed cookie.
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add(Cookie::new("signed", "text"));
///
/// // The cookie's contents are signed but still in plaintext.
/// assert_ne!(jar.get("signed").unwrap().value(), "text");
/// assert!(jar.get("signed").unwrap().value().contains("text"));
///
/// // They can be verified through the child jar.
/// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text");
///
/// // A tampered with cookie does not validate but still exists.
/// let mut cookie = jar.get("signed").unwrap().clone();
/// jar.add(Cookie::new("signed", cookie.value().to_string() + "!"));
/// assert!(jar.signed(&key).get("signed").is_none());
/// assert!(jar.get("signed").is_some());
/// ```
#[cfg(feature = "secure-cookies")]
pub fn signed(&mut self, key: &Key) -> SignedJar {
SignedJar::new(self, key)
}
}
use std::collections::hash_set::Iter as HashSetIter;
/// Iterator over the changes to a cookie jar.
pub struct Delta<'a> {
iter: HashSetIter<'a, DeltaCookie>,
}
impl<'a> Iterator for Delta<'a> {
type Item = &'a Cookie<'static>;
fn next(&mut self) -> Option<&'a Cookie<'static>> {
self.iter.next().map(|c| &c.cookie)
}
}
use std::collections::hash_map::RandomState;
use std::collections::hash_set::Difference;
use std::iter::Chain;
/// Iterator over all of the cookies in a jar.
pub struct Iter<'a> {
delta_cookies:
Chain<HashSetIter<'a, DeltaCookie>, Difference<'a, DeltaCookie, RandomState>>,
}
impl<'a> Iterator for Iter<'a> {
type Item = &'a Cookie<'static>;
fn next(&mut self) -> Option<&'a Cookie<'static>> {
for cookie in self.delta_cookies.by_ref() {
if !cookie.removed {
return Some(&*cookie);
}
}
None
}
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[test]
#[allow(deprecated)]
fn simple() {
let mut c = CookieJar::new();
c.add(Cookie::new("test", ""));
c.add(Cookie::new("test2", ""));
c.remove(Cookie::named("test"));
assert!(c.get("test").is_none());
assert!(c.get("test2").is_some());
c.add(Cookie::new("test3", ""));
c.clear();
assert!(c.get("test").is_none());
assert!(c.get("test2").is_none());
assert!(c.get("test3").is_none());
}
#[test]
fn jar_is_send() {
fn is_send<T: Send>(_: T) -> bool {
true
}
assert!(is_send(CookieJar::new()))
}
#[test]
#[cfg(feature = "secure-cookies")]
fn iter() {
let key = Key::generate();
let mut c = CookieJar::new();
c.add_original(Cookie::new("original", "original"));
c.add(Cookie::new("test", "test"));
c.add(Cookie::new("test2", "test2"));
c.add(Cookie::new("test3", "test3"));
assert_eq!(c.iter().count(), 4);
c.signed(&key).add(Cookie::new("signed", "signed"));
c.private(&key).add(Cookie::new("encrypted", "encrypted"));
assert_eq!(c.iter().count(), 6);
c.remove(Cookie::named("test"));
assert_eq!(c.iter().count(), 5);
c.remove(Cookie::named("signed"));
c.remove(Cookie::named("test2"));
assert_eq!(c.iter().count(), 3);
c.add(Cookie::new("test2", "test2"));
assert_eq!(c.iter().count(), 4);
c.remove(Cookie::named("test2"));
assert_eq!(c.iter().count(), 3);
}
#[test]
#[cfg(feature = "secure-cookies")]
fn delta() {
use std::collections::HashMap;
use time::Duration;
let mut c = CookieJar::new();
c.add_original(Cookie::new("original", "original"));
c.add_original(Cookie::new("original1", "original1"));
c.add(Cookie::new("test", "test"));
c.add(Cookie::new("test2", "test2"));
c.add(Cookie::new("test3", "test3"));
c.add(Cookie::new("test4", "test4"));
c.remove(Cookie::named("test"));
c.remove(Cookie::named("original"));
assert_eq!(c.delta().count(), 4);
let names: HashMap<_, _> = c.delta().map(|c| (c.name(), c.max_age())).collect();
assert!(names.get("test2").unwrap().is_none());
assert!(names.get("test3").unwrap().is_none());
assert!(names.get("test4").unwrap().is_none());
assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0)));
}
#[test]
fn replace_original() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("original_a", "a"));
jar.add_original(Cookie::new("original_b", "b"));
assert_eq!(jar.get("original_a").unwrap().value(), "a");
jar.add(Cookie::new("original_a", "av2"));
assert_eq!(jar.get("original_a").unwrap().value(), "av2");
}
#[test]
fn empty_delta() {
let mut jar = CookieJar::new();
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 0);
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 1);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().count(), 1);
}
#[test]
fn add_remove_add() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
// The cookie's been deleted. Another original doesn't change that.
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().count(), 1);
}
#[test]
fn replace_remove() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 0);
jar.add(Cookie::new("name", "val"));
assert_eq!(jar.delta().count(), 1);
assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
jar.remove(Cookie::named("name"));
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
}
#[test]
fn remove_with_path() {
let mut jar = CookieJar::new();
jar.add_original(Cookie::build("name", "val").finish());
assert_eq!(jar.iter().count(), 1);
assert_eq!(jar.delta().count(), 0);
assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1);
jar.remove(Cookie::build("name", "").path("/").finish());
assert_eq!(jar.iter().count(), 0);
assert_eq!(jar.delta().count(), 1);
assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1);
}
}

1087
actix-http/src/cookie/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,426 @@
use std::borrow::Cow;
use std::cmp;
use std::convert::From;
use std::error::Error;
use std::fmt;
use std::str::Utf8Error;
use percent_encoding::percent_decode;
use time::{self, Duration};
use super::{Cookie, CookieStr, SameSite};
/// Enum corresponding to a parsing error.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ParseError {
/// The cookie did not contain a name/value pair.
MissingPair,
/// The cookie's name was empty.
EmptyName,
/// Decoding the cookie's name or value resulted in invalid UTF-8.
Utf8Error(Utf8Error),
/// It is discouraged to exhaustively match on this enum as its variants may
/// grow without a breaking-change bump in version numbers.
#[doc(hidden)]
__Nonexhasutive,
}
impl ParseError {
/// Returns a description of this error as a string
pub fn as_str(&self) -> &'static str {
match *self {
ParseError::MissingPair => "the cookie is missing a name/value pair",
ParseError::EmptyName => "the cookie's name is empty",
ParseError::Utf8Error(_) => {
"decoding the cookie's name or value resulted in invalid UTF-8"
}
ParseError::__Nonexhasutive => unreachable!("__Nonexhasutive ParseError"),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl From<Utf8Error> for ParseError {
fn from(error: Utf8Error) -> ParseError {
ParseError::Utf8Error(error)
}
}
impl Error for ParseError {
fn description(&self) -> &str {
self.as_str()
}
}
fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
let haystack_start = haystack.as_ptr() as usize;
let needle_start = needle.as_ptr() as usize;
if needle_start < haystack_start {
return None;
}
if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
return None;
}
let start = needle_start - haystack_start;
let end = start + needle.len();
Some((start, end))
}
fn name_val_decoded(
name: &str,
val: &str,
) -> Result<(CookieStr, CookieStr), ParseError> {
let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
let name = CookieStr::Concrete(Cow::Owned(decoded_name.into_owned()));
let val = CookieStr::Concrete(Cow::Owned(decoded_value.into_owned()));
Ok((name, val))
}
// This function does the real parsing but _does not_ set the `cookie_string` in
// the returned cookie object. This only exists so that the borrow to `s` is
// returned at the end of the call, allowing the `cookie_string` field to be
// set in the outer `parse` function.
fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
let mut attributes = s.split(';');
let key_value = match attributes.next() {
Some(s) => s,
_ => panic!(),
};
// Determine the name = val.
let (name, value) = match key_value.find('=') {
Some(i) => (key_value[..i].trim(), key_value[(i + 1)..].trim()),
None => return Err(ParseError::MissingPair),
};
if name.is_empty() {
return Err(ParseError::EmptyName);
}
// Create a cookie with all of the defaults. We'll fill things in while we
// iterate through the parameters below.
let (name, value) = if decode {
name_val_decoded(name, value)?
} else {
let name_indexes = indexes_of(name, s).expect("name sub");
let value_indexes = indexes_of(value, s).expect("value sub");
let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
(name, value)
};
let mut cookie = Cookie {
name,
value,
cookie_string: None,
expires: None,
max_age: None,
domain: None,
path: None,
secure: None,
http_only: None,
same_site: None,
};
for attr in attributes {
let (key, value) = match attr.find('=') {
Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
None => (attr.trim(), None),
};
match (&*key.to_ascii_lowercase(), value) {
("secure", _) => cookie.secure = Some(true),
("httponly", _) => cookie.http_only = Some(true),
("max-age", Some(v)) => {
// See RFC 6265 Section 5.2.2, negative values indicate that the
// earliest possible expiration time should be used, so set the
// max age as 0 seconds.
cookie.max_age = match v.parse() {
Ok(val) if val <= 0 => Some(Duration::zero()),
Ok(val) => {
// Don't panic if the max age seconds is greater than what's supported by
// `Duration`.
let val = cmp::min(val, Duration::max_value().num_seconds());
Some(Duration::seconds(val))
}
Err(_) => continue,
};
}
("domain", Some(mut domain)) if !domain.is_empty() => {
if domain.starts_with('.') {
domain = &domain[1..];
}
let (i, j) = indexes_of(domain, s).expect("domain sub");
cookie.domain = Some(CookieStr::Indexed(i, j));
}
("path", Some(v)) => {
let (i, j) = indexes_of(v, s).expect("path sub");
cookie.path = Some(CookieStr::Indexed(i, j));
}
("samesite", Some(v)) => {
if v.eq_ignore_ascii_case("strict") {
cookie.same_site = Some(SameSite::Strict);
} else if v.eq_ignore_ascii_case("lax") {
cookie.same_site = Some(SameSite::Lax);
} else {
// We do nothing here, for now. When/if the `SameSite`
// attribute becomes standard, the spec says that we should
// ignore this cookie, i.e, fail to parse it, when an
// invalid value is passed in. The draft is at
// http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
}
}
("expires", Some(v)) => {
// Try strptime with three date formats according to
// http://tools.ietf.org/html/rfc2616#section-3.3.1. Try
// additional ones as encountered in the real world.
let tm = time::strptime(v, "%a, %d %b %Y %H:%M:%S %Z")
.or_else(|_| time::strptime(v, "%A, %d-%b-%y %H:%M:%S %Z"))
.or_else(|_| time::strptime(v, "%a, %d-%b-%Y %H:%M:%S %Z"))
.or_else(|_| time::strptime(v, "%a %b %d %H:%M:%S %Y"));
if let Ok(time) = tm {
cookie.expires = Some(time)
}
}
_ => {
// We're going to be permissive here. If we have no idea what
// this is, then it's something nonstandard. We're not going to
// store it (because it's not compliant), but we're also not
// going to emit an error.
}
}
}
Ok(cookie)
}
pub fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
where
S: Into<Cow<'c, str>>,
{
let s = cow.into();
let mut cookie = parse_inner(&s, decode)?;
cookie.cookie_string = Some(s);
Ok(cookie)
}
#[cfg(test)]
mod tests {
use super::{Cookie, SameSite};
use time::{strptime, Duration};
macro_rules! assert_eq_parse {
($string:expr, $expected:expr) => {
let cookie = match Cookie::parse($string) {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
};
assert_eq!(cookie, $expected);
};
}
macro_rules! assert_ne_parse {
($string:expr, $expected:expr) => {
let cookie = match Cookie::parse($string) {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e),
};
assert_ne!(cookie, $expected);
};
}
#[test]
fn parse_same_site() {
let expected = Cookie::build("foo", "bar")
.same_site(SameSite::Lax)
.finish();
assert_eq_parse!("foo=bar; SameSite=Lax", expected);
assert_eq_parse!("foo=bar; SameSite=lax", expected);
assert_eq_parse!("foo=bar; SameSite=LAX", expected);
assert_eq_parse!("foo=bar; samesite=Lax", expected);
assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
let expected = Cookie::build("foo", "bar")
.same_site(SameSite::Strict)
.finish();
assert_eq_parse!("foo=bar; SameSite=Strict", expected);
assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
assert_eq_parse!("foo=bar; SameSite=strict", expected);
assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
}
#[test]
fn parse() {
assert!(Cookie::parse("bar").is_err());
assert!(Cookie::parse("=bar").is_err());
assert!(Cookie::parse(" =bar").is_err());
assert!(Cookie::parse("foo=").is_ok());
let expected = Cookie::build("foo", "bar=baz").finish();
assert_eq_parse!("foo=bar=baz", expected);
let mut expected = Cookie::build("foo", "bar").finish();
assert_eq_parse!("foo=bar", expected);
assert_eq_parse!("foo = bar", expected);
assert_eq_parse!(" foo=bar ", expected);
assert_eq_parse!(" foo=bar ;Domain=", expected);
assert_eq_parse!(" foo=bar ;Domain= ", expected);
assert_eq_parse!(" foo=bar ;Ignored", expected);
let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
assert_ne_parse!(" foo=bar; httponly", unexpected);
expected.set_http_only(true);
assert_eq_parse!(" foo=bar ;HttpOnly", expected);
assert_eq_parse!(" foo=bar ;httponly", expected);
assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
expected.set_secure(true);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
unexpected.set_http_only(true);
unexpected.set_secure(true);
assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
unexpected.set_secure(false);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
expected.set_max_age(Duration::zero());
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
expected.set_max_age(Duration::minutes(1));
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
expected.set_max_age(Duration::seconds(4));
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
unexpected.set_secure(true);
unexpected.set_max_age(Duration::minutes(1));
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
expected.set_path("/");
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
expected.set_path("/foo");
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
unexpected.set_max_age(Duration::seconds(4));
unexpected.set_path("/bar");
assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
expected.set_domain("www.foo.com");
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=www.foo.com",
expected
);
expected.set_domain("foo.com");
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com",
expected
);
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=FOO.COM",
expected
);
unexpected.set_path("/foo");
unexpected.set_domain("bar.com");
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com",
unexpected
);
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=FOO.COM",
unexpected
);
let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
let expires = strptime(time_str, "%a, %d %b %Y %H:%M:%S %Z").unwrap();
expected.set_expires(expires);
assert_eq_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
expected
);
unexpected.set_domain("foo.com");
let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap();
expected.set_expires(bad_expires);
assert_ne_parse!(
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
unexpected
);
}
#[test]
fn odd_characters() {
let expected = Cookie::new("foo", "b%2Fr");
assert_eq_parse!("foo=b%2Fr", expected);
}
#[test]
fn odd_characters_encoded() {
let expected = Cookie::new("foo", "b/r");
let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
Ok(cookie) => cookie,
Err(e) => panic!("Failed to parse: {:?}", e),
};
assert_eq!(cookie, expected);
}
#[test]
fn do_not_panic_on_large_max_ages() {
let max_seconds = Duration::max_value().num_seconds();
let expected = Cookie::build("foo", "bar")
.max_age(Duration::seconds(max_seconds))
.finish();
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
}
}

View File

@ -0,0 +1,180 @@
use ring::digest::{Algorithm, SHA256};
use ring::hkdf::expand;
use ring::hmac::SigningKey;
use ring::rand::{SecureRandom, SystemRandom};
use super::private::KEY_LEN as PRIVATE_KEY_LEN;
use super::signed::KEY_LEN as SIGNED_KEY_LEN;
static HKDF_DIGEST: &'static Algorithm = &SHA256;
const KEYS_INFO: &'static str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM";
/// A cryptographic master key for use with `Signed` and/or `Private` jars.
///
/// This structure encapsulates secure, cryptographic keys for use with both
/// [PrivateJar](struct.PrivateJar.html) and [SignedJar](struct.SignedJar.html).
/// It can be derived from a single master key via
/// [from_master](#method.from_master) or generated from a secure random source
/// via [generate](#method.generate). A single instance of `Key` can be used for
/// both a `PrivateJar` and a `SignedJar`.
///
/// This type is only available when the `secure` feature is enabled.
#[derive(Clone)]
pub struct Key {
signing_key: [u8; SIGNED_KEY_LEN],
encryption_key: [u8; PRIVATE_KEY_LEN],
}
impl Key {
/// Derives new signing/encryption keys from a master key.
///
/// The master key must be at least 256-bits (32 bytes). For security, the
/// master key _must_ be cryptographically random. The keys are derived
/// deterministically from the master key.
///
/// # Panics
///
/// Panics if `key` is less than 32 bytes in length.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// # /*
/// let master_key = { /* a cryptographically random key >= 32 bytes */ };
/// # */
/// # let master_key: &Vec<u8> = &(0..32).collect();
///
/// let key = Key::from_master(master_key);
/// ```
pub fn from_master(key: &[u8]) -> Key {
if key.len() < 32 {
panic!(
"bad master key length: expected at least 32 bytes, found {}",
key.len()
);
}
// Expand the user's key into two.
let prk = SigningKey::new(HKDF_DIGEST, key);
let mut both_keys = [0; SIGNED_KEY_LEN + PRIVATE_KEY_LEN];
expand(&prk, KEYS_INFO.as_bytes(), &mut both_keys);
// Copy the keys into their respective arrays.
let mut signing_key = [0; SIGNED_KEY_LEN];
let mut encryption_key = [0; PRIVATE_KEY_LEN];
signing_key.copy_from_slice(&both_keys[..SIGNED_KEY_LEN]);
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);
Key {
signing_key: signing_key,
encryption_key: encryption_key,
}
}
/// Generates signing/encryption keys from a secure, random source. Keys are
/// generated nondeterministically.
///
/// # Panics
///
/// Panics if randomness cannot be retrieved from the operating system. See
/// [try_generate](#method.try_generate) for a non-panicking version.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// ```
pub fn generate() -> Key {
Self::try_generate().expect("failed to generate `Key` from randomness")
}
/// Attempts to generate signing/encryption keys from a secure, random
/// source. Keys are generated nondeterministically. If randomness cannot be
/// retrieved from the underlying operating system, returns `None`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::try_generate();
/// ```
pub fn try_generate() -> Option<Key> {
let mut sign_key = [0; SIGNED_KEY_LEN];
let mut enc_key = [0; PRIVATE_KEY_LEN];
let rng = SystemRandom::new();
if rng.fill(&mut sign_key).is_err() || rng.fill(&mut enc_key).is_err() {
return None;
}
Some(Key {
signing_key: sign_key,
encryption_key: enc_key,
})
}
/// Returns the raw bytes of a key suitable for signing cookies.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// let signing_key = key.signing();
/// ```
pub fn signing(&self) -> &[u8] {
&self.signing_key[..]
}
/// Returns the raw bytes of a key suitable for encrypting cookies.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::Key;
///
/// let key = Key::generate();
/// let encryption_key = key.encryption();
/// ```
pub fn encryption(&self) -> &[u8] {
&self.encryption_key[..]
}
}
#[cfg(test)]
mod test {
use super::Key;
#[test]
fn deterministic_from_master() {
let master_key: Vec<u8> = (0..32).collect();
let key_a = Key::from_master(&master_key);
let key_b = Key::from_master(&master_key);
assert_eq!(key_a.signing(), key_b.signing());
assert_eq!(key_a.encryption(), key_b.encryption());
assert_ne!(key_a.encryption(), key_a.signing());
let master_key_2: Vec<u8> = (32..64).collect();
let key_2 = Key::from_master(&master_key_2);
assert_ne!(key_2.signing(), key_a.signing());
assert_ne!(key_2.encryption(), key_a.encryption());
}
#[test]
fn non_deterministic_generate() {
let key_a = Key::generate();
let key_b = Key::generate();
assert_ne!(key_a.signing(), key_b.signing());
assert_ne!(key_a.encryption(), key_b.encryption());
}
}

View File

@ -0,0 +1,40 @@
#[cfg(test)]
macro_rules! assert_simple_behaviour {
($clear:expr, $secure:expr) => {{
assert_eq!($clear.iter().count(), 0);
$secure.add(Cookie::new("name", "val"));
assert_eq!($clear.iter().count(), 1);
assert_eq!($secure.get("name").unwrap().value(), "val");
assert_ne!($clear.get("name").unwrap().value(), "val");
$secure.add(Cookie::new("another", "two"));
assert_eq!($clear.iter().count(), 2);
$clear.remove(Cookie::named("another"));
assert_eq!($clear.iter().count(), 1);
$secure.remove(Cookie::named("name"));
assert_eq!($clear.iter().count(), 0);
}};
}
#[cfg(test)]
macro_rules! assert_secure_behaviour {
($clear:expr, $secure:expr) => {{
$secure.add(Cookie::new("secure", "secure"));
assert!($clear.get("secure").unwrap().value() != "secure");
assert!($secure.get("secure").unwrap().value() == "secure");
let mut cookie = $clear.get("secure").unwrap().clone();
let new_val = format!("{}l", cookie.value());
cookie.set_value(new_val);
$clear.add(cookie);
assert!($secure.get("secure").is_none());
let mut cookie = $clear.get("secure").unwrap().clone();
cookie.set_value("foobar");
$clear.add(cookie);
assert!($secure.get("secure").is_none());
}};
}

View File

@ -0,0 +1,10 @@
//! Fork of https://github.com/alexcrichton/cookie-rs
#[macro_use]
mod macros;
mod key;
mod private;
mod signed;
pub use self::key::*;
pub use self::private::*;
pub use self::signed::*;

View File

@ -0,0 +1,226 @@
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey};
use ring::rand::{SecureRandom, SystemRandom};
use super::Key;
use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `private` docs as
// well as the `KEYS_INFO` const in secure::Key.
static ALGO: &'static Algorithm = &AES_256_GCM;
const NONCE_LEN: usize = 12;
pub const KEY_LEN: usize = 32;
/// A child cookie jar that provides authenticated encryption for its cookies.
///
/// A _private_ child jar signs and encrypts all the cookies added to it and
/// verifies and decrypts cookies retrieved from it. Any cookies stored in a
/// `PrivateJar` are simultaneously assured confidentiality, integrity, and
/// authenticity. In other words, clients cannot discover nor tamper with the
/// contents of a cookie, nor can they fabricate cookie data.
///
/// This type is only available when the `secure` feature is enabled.
pub struct PrivateJar<'a> {
parent: &'a mut CookieJar,
key: [u8; KEY_LEN],
}
impl<'a> PrivateJar<'a> {
/// Creates a new child `PrivateJar` with parent `parent` and key `key`.
/// This method is typically called indirectly via the `signed` method of
/// `CookieJar`.
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> PrivateJar<'a> {
let mut key_array = [0u8; KEY_LEN];
key_array.copy_from_slice(key.encryption());
PrivateJar {
parent: parent,
key: key_array,
}
}
/// Given a sealed value `str` and a key name `name`, where the nonce is
/// prepended to the original value and then both are Base64 encoded,
/// verifies and decrypts the sealed value and returns it. If there's a
/// problem, returns an `Err` with a string describing the issue.
fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> {
let mut data = base64::decode(value).map_err(|_| "bad base64 value")?;
if data.len() <= NONCE_LEN {
return Err("length of decoded data is <= NONCE_LEN");
}
let ad = Aad::from(name.as_bytes());
let key = OpeningKey::new(ALGO, &self.key).expect("opening key");
let (nonce, sealed) = data.split_at_mut(NONCE_LEN);
let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
::std::str::from_utf8(unsealed)
.map(|s| s.to_string())
.map_err(|_| "bad unsealed utf8")
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
/// and authenticates and decrypts the cookie's value, returning a `Cookie`
/// with the decrypted value. If the cookie cannot be found, or the cookie
/// fails to authenticate or decrypt, `None` is returned.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
/// assert!(private_jar.get("name").is_none());
///
/// private_jar.add(Cookie::new("name", "value"));
/// assert_eq!(private_jar.get("name").unwrap().value(), "value");
/// ```
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
if let Some(cookie_ref) = self.parent.get(name) {
let mut cookie = cookie_ref.clone();
if let Ok(value) = self.unseal(name, cookie.value()) {
cookie.set_value(value);
return Some(cookie);
}
}
None
}
/// Adds `cookie` to the parent jar. The cookie's value is encrypted with
/// authenticated encryption assuring confidentiality, integrity, and
/// authenticity.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.private(&key).add(Cookie::new("name", "value"));
///
/// assert_ne!(jar.get("name").unwrap().value(), "value");
/// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
/// ```
pub fn add(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
// Add the sealed cookie to the parent.
self.parent.add(cookie);
}
/// Adds an "original" `cookie` to parent jar. The cookie's value is
/// encrypted with authenticated encryption assuring confidentiality,
/// integrity, and authenticity. Adding an original cookie does not affect
/// the [`CookieJar::delta()`](struct.CookieJar.html#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.private(&key).add_original(Cookie::new("name", "value"));
///
/// assert_eq!(jar.iter().count(), 1);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
self.encrypt_cookie(&mut cookie);
// Add the sealed cookie to the parent.
self.parent.add_original(cookie);
}
/// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) {
let mut data;
let output_len = {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let cookie_val = cookie.value().as_bytes();
data = vec![0; NONCE_LEN + cookie_val.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.fill(nonce)
.expect("couldn't random fill nonce");
in_out[..cookie_val.len()].copy_from_slice(cookie_val);
let nonce = Nonce::try_assume_unique_for_key(nonce)
.expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(cookie.name().as_bytes());
// Perform the actual sealing operation and get the output length.
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal")
};
// Base64 encode the nonce and encrypted value.
let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]);
cookie.set_value(sealed_value);
}
/// Removes `cookie` from the parent jar.
///
/// For correct removal, the passed in `cookie` must contain the same `path`
/// and `domain` as the cookie that was initially set.
///
/// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more
/// details.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut private_jar = jar.private(&key);
///
/// private_jar.add(Cookie::new("name", "value"));
/// assert!(private_jar.get("name").is_some());
///
/// private_jar.remove(Cookie::named("name"));
/// assert!(private_jar.get("name").is_none());
/// ```
pub fn remove(&mut self, cookie: Cookie<'static>) {
self.parent.remove(cookie);
}
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[test]
fn simple() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.private(&key));
}
#[test]
fn private() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
}

View File

@ -0,0 +1,185 @@
use ring::digest::{Algorithm, SHA256};
use ring::hmac::{sign, verify_with_own_key as verify, SigningKey};
use super::Key;
use crate::cookie::{Cookie, CookieJar};
// Keep these in sync, and keep the key len synced with the `signed` docs as
// well as the `KEYS_INFO` const in secure::Key.
static HMAC_DIGEST: &'static Algorithm = &SHA256;
const BASE64_DIGEST_LEN: usize = 44;
pub const KEY_LEN: usize = 32;
/// A child cookie jar that authenticates its cookies.
///
/// A _signed_ child jar signs all the cookies added to it and verifies cookies
/// retrieved from it. Any cookies stored in a `SignedJar` are assured integrity
/// and authenticity. In other words, clients cannot tamper with the contents of
/// a cookie nor can they fabricate cookie values, but the data is visible in
/// plaintext.
///
/// This type is only available when the `secure` feature is enabled.
pub struct SignedJar<'a> {
parent: &'a mut CookieJar,
key: SigningKey,
}
impl<'a> SignedJar<'a> {
/// Creates a new child `SignedJar` with parent `parent` and key `key`. This
/// method is typically called indirectly via the `signed` method of
/// `CookieJar`.
#[doc(hidden)]
pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
SignedJar {
parent: parent,
key: SigningKey::new(HMAC_DIGEST, key.signing()),
}
}
/// Given a signed value `str` where the signature is prepended to `value`,
/// verifies the signed value and returns it. If there's a problem, returns
/// an `Err` with a string describing the issue.
fn verify(&self, cookie_value: &str) -> Result<String, &'static str> {
if cookie_value.len() < BASE64_DIGEST_LEN {
return Err("length of value is <= BASE64_DIGEST_LEN");
}
let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
let sig = base64::decode(digest_str).map_err(|_| "bad base64 digest")?;
verify(&self.key, value.as_bytes(), &sig)
.map(|_| value.to_string())
.map_err(|_| "value did not verify")
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
/// and verifies the authenticity and integrity of the cookie's value,
/// returning a `Cookie` with the authenticated value. If the cookie cannot
/// be found, or the cookie fails to verify, `None` is returned.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut signed_jar = jar.signed(&key);
/// assert!(signed_jar.get("name").is_none());
///
/// signed_jar.add(Cookie::new("name", "value"));
/// assert_eq!(signed_jar.get("name").unwrap().value(), "value");
/// ```
pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
if let Some(cookie_ref) = self.parent.get(name) {
let mut cookie = cookie_ref.clone();
if let Ok(value) = self.verify(cookie.value()) {
cookie.set_value(value);
return Some(cookie);
}
}
None
}
/// Adds `cookie` to the parent jar. The cookie's value is signed assuring
/// integrity and authenticity.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add(Cookie::new("name", "value"));
///
/// assert_ne!(jar.get("name").unwrap().value(), "value");
/// assert!(jar.get("name").unwrap().value().contains("value"));
/// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
/// ```
pub fn add(&mut self, mut cookie: Cookie<'static>) {
self.sign_cookie(&mut cookie);
self.parent.add(cookie);
}
/// Adds an "original" `cookie` to this jar. The cookie's value is signed
/// assuring integrity and authenticity. Adding an original cookie does not
/// affect the [`CookieJar::delta()`](struct.CookieJar.html#method.delta)
/// computation. This method is intended to be used to seed the cookie jar
/// with cookies received from a client's HTTP message.
///
/// For accurate `delta` computations, this method should not be called
/// after calling `remove`.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// jar.signed(&key).add_original(Cookie::new("name", "value"));
///
/// assert_eq!(jar.iter().count(), 1);
/// assert_eq!(jar.delta().count(), 0);
/// ```
pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
self.sign_cookie(&mut cookie);
self.parent.add_original(cookie);
}
/// Signs the cookie's value assuring integrity and authenticity.
fn sign_cookie(&self, cookie: &mut Cookie) {
let digest = sign(&self.key, cookie.value().as_bytes());
let mut new_value = base64::encode(digest.as_ref());
new_value.push_str(cookie.value());
cookie.set_value(new_value);
}
/// Removes `cookie` from the parent jar.
///
/// For correct removal, the passed in `cookie` must contain the same `path`
/// and `domain` as the cookie that was initially set.
///
/// See [CookieJar::remove](struct.CookieJar.html#method.remove) for more
/// details.
///
/// # Example
///
/// ```rust
/// use actix_http::cookie::{CookieJar, Cookie, Key};
///
/// let key = Key::generate();
/// let mut jar = CookieJar::new();
/// let mut signed_jar = jar.signed(&key);
///
/// signed_jar.add(Cookie::new("name", "value"));
/// assert!(signed_jar.get("name").is_some());
///
/// signed_jar.remove(Cookie::named("name"));
/// assert!(signed_jar.get("name").is_none());
/// ```
pub fn remove(&mut self, cookie: Cookie<'static>) {
self.parent.remove(cookie);
}
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[test]
fn simple() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_simple_behaviour!(jar, jar.signed(&key));
}
#[test]
fn private() {
let key = Key::generate();
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.signed(&key));
}
}

View File

@ -6,8 +6,6 @@ use std::{fmt, io, result};
pub use actix_threadpool::BlockingError; pub use actix_threadpool::BlockingError;
use actix_utils::timeout::TimeoutError; use actix_utils::timeout::TimeoutError;
#[cfg(feature = "cookies")]
use cookie;
use derive_more::{Display, From}; use derive_more::{Display, From};
use futures::Canceled; use futures::Canceled;
use http::uri::InvalidUri; use http::uri::InvalidUri;
@ -19,8 +17,7 @@ use serde_urlencoded::ser::Error as FormError;
use tokio_timer::Error as TimerError; use tokio_timer::Error as TimerError;
// re-export for convinience // re-export for convinience
#[cfg(feature = "cookies")] pub use crate::cookie::ParseError as CookieParseError;
pub use cookie::ParseError as CookieParseError;
use crate::body::Body; use crate::body::Body;
use crate::response::Response; use crate::response::Response;
@ -79,7 +76,7 @@ impl fmt::Display for Error {
impl fmt::Debug for Error { impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}\n", &self.cause) writeln!(f, "{:?}", &self.cause)
} }
} }
@ -319,8 +316,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 cookie::ParseError {
fn error_response(&self) -> Response { fn error_response(&self) -> Response {
Response::new(StatusCode::BAD_REQUEST) Response::new(StatusCode::BAD_REQUEST)
} }
@ -895,10 +891,8 @@ mod tests {
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
#[cfg(feature = "cookies")]
#[test] #[test]
fn test_cookie_parse() { fn test_cookie_parse() {
use cookie::ParseError as CookieParseError;
let resp: Response = CookieParseError::EmptyName.error_response(); let resp: Response = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
} }

View File

@ -525,7 +525,7 @@ where
// keep-alive and stream errors // keep-alive and stream errors
if inner.state.is_empty() && inner.framed.is_write_buf_empty() { if inner.state.is_empty() && inner.framed.is_write_buf_empty() {
if let Some(err) = inner.error.take() { if let Some(err) = inner.error.take() {
return Err(err); Err(err)
} }
// disconnect if keep-alive is not enabled // disconnect if keep-alive is not enabled
else if inner.flags.contains(Flags::STARTED) else if inner.flags.contains(Flags::STARTED)
@ -538,10 +538,10 @@ where
else if inner.flags.contains(Flags::SHUTDOWN) { else if inner.flags.contains(Flags::SHUTDOWN) {
self.poll() self.poll()
} else { } else {
return Ok(Async::NotReady); Ok(Async::NotReady)
} }
} else { } else {
return Ok(Async::NotReady); Ok(Async::NotReady)
} }
} }
} }

View File

@ -220,7 +220,7 @@ where
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => { Err(err) => {
trace!("H2 handshake error: {}", err); trace!("H2 handshake error: {}", err);
return Err(err.into()); Err(err.into())
} }
} }
} }

View File

@ -7,17 +7,12 @@ use encoding::EncodingRef;
use http::{header, HeaderMap}; use http::{header, HeaderMap};
use mime::Mime; use mime::Mime;
use crate::error::{ContentTypeError, ParseError}; use crate::cookie::Cookie;
use crate::error::{ContentTypeError, CookieParseError, ParseError};
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::Header; use crate::header::Header;
use crate::payload::Payload; use crate::payload::Payload;
#[cfg(feature = "cookies")]
use crate::error::CookieParseError;
#[cfg(feature = "cookies")]
use cookie::Cookie;
#[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
@ -110,7 +105,6 @@ pub trait HttpMessage: Sized {
/// Load request cookies. /// Load request cookies.
#[inline] #[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();
@ -131,7 +125,6 @@ pub trait HttpMessage: Sized {
} }
/// 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,9 +1,5 @@
//! Basic http primitives for actix-net framework. //! Basic http primitives for actix-net framework.
#![allow( #![allow(clippy::type_complexity, clippy::new_without_default)]
clippy::type_complexity,
clippy::new_without_default,
clippy::new_without_default_derive
)]
#[macro_use] #[macro_use]
extern crate log; extern crate log;
@ -24,6 +20,7 @@ mod request;
mod response; mod response;
mod service; mod service;
pub mod cookie;
pub mod error; pub mod error;
pub mod h1; pub mod h1;
pub mod h2; pub mod h2;
@ -46,16 +43,11 @@ pub mod http {
// re-exports // re-exports
pub use http::header::{HeaderName, HeaderValue}; pub use http::header::{HeaderName, HeaderValue};
pub use http::uri::PathAndQuery;
pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri};
pub use http::{Method, StatusCode, Version}; pub use http::{Method, StatusCode, Version};
#[doc(hidden)] pub use crate::cookie::{Cookie, CookieBuilder};
pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri};
#[doc(hidden)]
pub use http::uri::PathAndQuery;
#[cfg(feature = "cookies")]
pub use cookie::{Cookie, CookieBuilder};
/// Various http headers /// Various http headers
pub mod header { pub mod header {

View File

@ -4,8 +4,6 @@ use std::io::Write;
use std::{fmt, str}; use std::{fmt, str};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use futures::future::{ok, FutureResult, IntoFuture}; use futures::future::{ok, FutureResult, IntoFuture};
use futures::Stream; use futures::Stream;
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
@ -14,6 +12,7 @@ use serde::Serialize;
use serde_json; use serde_json;
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::{Header, IntoHeaderValue}; use crate::header::{Header, IntoHeaderValue};
@ -131,7 +130,6 @@ impl<B> Response<B> {
/// Get an iterator for the cookies set by this response /// Get an iterator for the cookies set by this response
#[inline] #[inline]
#[cfg(feature = "cookies")]
pub fn cookies(&self) -> CookieIter { pub fn cookies(&self) -> CookieIter {
CookieIter { CookieIter {
iter: self.head.headers.get_all(header::SET_COOKIE).iter(), iter: self.head.headers.get_all(header::SET_COOKIE).iter(),
@ -140,7 +138,6 @@ impl<B> Response<B> {
/// Add a cookie to this response /// Add a cookie to this response
#[inline] #[inline]
#[cfg(feature = "cookies")]
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;
HeaderValue::from_str(&cookie.to_string()) HeaderValue::from_str(&cookie.to_string())
@ -153,7 +150,6 @@ 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.
#[inline] #[inline]
#[cfg(feature = "cookies")]
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;
let vals: Vec<HeaderValue> = h let vals: Vec<HeaderValue> = h
@ -245,8 +241,8 @@ impl<B> Response<B> {
let body = f(&mut self.head, self.body); let body = f(&mut self.head, self.body);
Response { Response {
body,
head: self.head, head: self.head,
body: body,
error: self.error, error: self.error,
} }
} }
@ -285,12 +281,10 @@ impl IntoFuture for Response {
} }
} }
#[cfg(feature = "cookies")]
pub struct CookieIter<'a> { pub struct CookieIter<'a> {
iter: header::ValueIter<'a, HeaderValue>, iter: header::ValueIter<'a, HeaderValue>,
} }
#[cfg(feature = "cookies")]
impl<'a> Iterator for CookieIter<'a> { impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>; type Item = Cookie<'a>;
@ -312,7 +306,6 @@ impl<'a> Iterator for CookieIter<'a> {
pub struct ResponseBuilder { pub struct ResponseBuilder {
head: Option<Message<ResponseHead>>, head: Option<Message<ResponseHead>>,
err: Option<HttpError>, err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
} }
@ -325,7 +318,6 @@ impl ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: Some(head), head: Some(head),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: None, cookies: None,
} }
} }
@ -525,7 +517,6 @@ 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();
@ -553,7 +544,6 @@ 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() {
@ -620,20 +610,16 @@ impl ResponseBuilder {
return Response::from(Error::from(e)).into_body(); return Response::from(Error::from(e)).into_body();
} }
#[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 {
{ for cookie in jar.delta() {
if let Some(ref jar) = self.cookies { match HeaderValue::from_str(&cookie.to_string()) {
for cookie in jar.delta() { Ok(val) => {
match HeaderValue::from_str(&cookie.to_string()) { let _ = response.headers.append(header::SET_COOKIE, val);
Ok(val) => { }
let _ = response.headers.append(header::SET_COOKIE, val); Err(e) => return Response::from(Error::from(e)).into_body(),
} };
Err(e) => return Response::from(Error::from(e)).into_body(),
};
}
} }
} }
@ -697,7 +683,6 @@ 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(),
} }
} }
@ -718,9 +703,7 @@ fn parts<'a>(
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 // If this response has cookies, load them into a jar
#[cfg(feature = "cookies")]
let mut jar: Option<CookieJar> = None; let mut jar: Option<CookieJar> = None;
#[cfg(feature = "cookies")]
for c in res.cookies() { for c in res.cookies() {
if let Some(ref mut j) = jar { if let Some(ref mut j) = jar {
j.add_original(c.into_owned()); j.add_original(c.into_owned());
@ -734,7 +717,6 @@ impl<B> From<Response<B>> for ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: Some(res.head), head: Some(res.head),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: jar, cookies: jar,
} }
} }
@ -744,22 +726,18 @@ impl<B> From<Response<B>> for 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 // If this response has cookies, load them into a jar
#[cfg(feature = "cookies")]
let mut jar: Option<CookieJar> = None; let mut jar: Option<CookieJar> = None;
#[cfg(feature = "cookies")] let cookies = CookieIter {
{ iter: head.headers.get_all(header::SET_COOKIE).iter(),
let cookies = CookieIter { };
iter: head.headers.get_all(header::SET_COOKIE).iter(), for c in cookies {
}; if let Some(ref mut j) = jar {
for c in cookies { j.add_original(c.into_owned());
if let Some(ref mut j) = jar { } else {
j.add_original(c.into_owned()); let mut j = CookieJar::new();
} else { j.add_original(c.into_owned());
let mut j = CookieJar::new(); jar = Some(j);
j.add_original(c.into_owned());
jar = Some(j);
}
} }
} }
@ -773,7 +751,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
ResponseBuilder { ResponseBuilder {
head: Some(msg), head: Some(msg),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: jar, cookies: jar,
} }
} }
@ -870,7 +847,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "cookies")]
fn test_response_cookies() { fn test_response_cookies() {
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
@ -907,7 +883,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "cookies")]
fn test_update_response_cookies() { fn test_update_response_cookies() {
let mut r = Response::Ok() let mut r = Response::Ok()
.cookie(crate::http::Cookie::new("original", "val100")) .cookie(crate::http::Cookie::new("original", "val100"))
@ -1068,7 +1043,6 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "cookies")]
fn test_into_builder() { fn test_into_builder() {
let mut resp: Response = "test".into(); let mut resp: Response = "test".into();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);

View File

@ -1,12 +1,13 @@
//! Test Various helpers for Actix applications to use during testing. //! Test Various helpers for Actix applications to use during testing.
use std::fmt::Write as FmtWrite;
use std::str::FromStr; use std::str::FromStr;
use bytes::Bytes; use bytes::Bytes;
#[cfg(feature = "cookies")] use http::header::{self, HeaderName, HeaderValue};
use cookie::{Cookie, CookieJar};
use http::header::HeaderName;
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use crate::cookie::{Cookie, CookieJar};
use crate::header::{Header, IntoHeaderValue}; use crate::header::{Header, IntoHeaderValue};
use crate::payload::Payload; use crate::payload::Payload;
use crate::Request; use crate::Request;
@ -45,7 +46,6 @@ 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>,
} }
@ -57,7 +57,6 @@ 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,
})) }))
@ -127,7 +126,6 @@ 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
@ -161,25 +159,17 @@ impl TestRequest {
head.version = inner.version; head.version = inner.version;
head.headers = inner.headers; head.headers = inner.headers;
#[cfg(feature = "cookies")] let mut cookie = String::new();
{ for c in inner.cookies.delta() {
use std::fmt::Write as FmtWrite; let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
use http::header::{self, HeaderValue}; let _ = write!(&mut cookie, "; {}={}", name, value);
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; }
if !cookie.is_empty() {
let mut cookie = String::new(); head.headers.insert(
for c in inner.cookies.delta() { header::COOKIE,
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); );
let _ = write!(&mut cookie, "; {}={}", name, value);
}
if !cookie.is_empty() {
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
} }
req req

View File

@ -1,11 +1,8 @@
use actix_service::NewService; use actix_service::NewService;
use bytes::{Bytes, BytesMut}; use bytes::Bytes;
use futures::future::{self, ok}; use futures::future::{self, ok};
use futures::{Future, Stream};
use actix_http::{ use actix_http::{http, HttpService, Request, Response};
error::PayloadError, http, HttpMessage, HttpService, Request, Response,
};
use actix_http_test::TestServer; use actix_http_test::TestServer;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@ -30,16 +27,6 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World"; Hello World Hello World Hello World Hello World Hello World";
fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
stream.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
}
#[test] #[test]
fn test_h1_v2() { fn test_h1_v2() {
env_logger::init(); env_logger::init();

View File

@ -21,13 +21,13 @@ path = "src/lib.rs"
default = ["cookie-session"] default = ["cookie-session"]
# sessions feature, session require "ring" crate and c compiler # sessions feature, session require "ring" crate and c compiler
cookie-session = ["cookie/secure"] cookie-session = ["actix-web/secure-cookies"]
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.1" #actix-web = "1.0.0-alpha.1"
actix-web = { path = ".." }
actix-service = "0.3.3" actix-service = "0.3.3"
bytes = "0.4" bytes = "0.4"
cookie = { version="0.11", features=["percent-encode"], optional=true }
derive_more = "0.14" derive_more = "0.14"
futures = "0.1.25" futures = "0.1.25"
hashbrown = "0.1.8" hashbrown = "0.1.8"

View File

@ -19,10 +19,10 @@ use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_web::cookie::{Cookie, CookieJar, Key, SameSite};
use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::http::{header::SET_COOKIE, HeaderValue}; use actix_web::http::{header::SET_COOKIE, HeaderValue};
use actix_web::{Error, HttpMessage, ResponseError}; use actix_web::{Error, HttpMessage, ResponseError};
use cookie::{Cookie, CookieJar, Key, SameSite};
use derive_more::{Display, From}; use derive_more::{Display, From};
use futures::future::{ok, Future, FutureResult}; use futures::future::{ok, Future, FutureResult};
use futures::Poll; use futures::Poll;

View File

@ -18,14 +18,16 @@ name = "actix_web_actors"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.8.0-alpha.1" actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.1" #actix-web = "1.0.0-alpha.1"
actix-http = "0.1.0-alpha.1" #actix-http = "0.1.0-alpha.1"
actix-web = { path=".." }
actix-http = { path="../actix-http" }
actix-codec = "0.1.2" actix-codec = "0.1.2"
bytes = "0.4" bytes = "0.4"
futures = "0.1.25" futures = "0.1.25"
[dev-dependencies] [dev-dependencies]
env_logger = "0.6" env_logger = "0.6"
actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } #actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } actix-http-test = { path = "../test-server", features=["ssl"] }

View File

@ -16,6 +16,9 @@ quote = "0.6"
syn = { version = "0.15", features = ["full", "parsing"] } syn = { version = "0.15", features = ["full", "parsing"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0-alpha.1" } #actix-web = { version = "1.0.0-alpha.1" }
actix-http = { version = "0.1.0-alpha.1", features=["ssl"] } #actix-http = { version = "0.1.0-alpha.1", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] } #actix-http-test = { version = "0.1.0-alpha.1", features=["ssl"] }
actix-web = { path = ".." }
actix-http = { path = "../actix-http", features=["ssl"] }
actix-http-test = { path = "../test-server", features=["ssl"] }

View File

@ -18,17 +18,14 @@ name = "awc"
path = "src/lib.rs" path = "src/lib.rs"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "cookies"] features = ["ssl", "brotli", "flate2-zlib"]
[features] [features]
default = ["cookies", "brotli", "flate2-zlib"] default = ["brotli", "flate2-zlib"]
# openssl # openssl
ssl = ["openssl", "actix-http/ssl"] ssl = ["openssl", "actix-http/ssl"]
# cookies integration
cookies = ["cookie", "actix-http/cookies"]
# brotli encoding, requires c compiler # brotli encoding, requires c compiler
brotli = ["actix-http/brotli"] brotli = ["actix-http/brotli"]
@ -53,8 +50,6 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_urlencoded = "0.5.3" serde_urlencoded = "0.5.3"
tokio-timer = "0.2.8" tokio-timer = "0.2.8"
cookie = { version="0.11", features=["percent-encode"], optional = true }
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]

View File

@ -24,7 +24,7 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
pub use actix_http::{client::Connector, http}; pub use actix_http::{client::Connector, cookie, http};
use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri}; use actix_http::http::{HeaderMap, HttpTryFrom, Method, Uri};
use actix_http::RequestHead; use actix_http::RequestHead;

View File

@ -1,18 +1,19 @@
use std::fmt; use std::fmt;
use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use futures::future::{err, Either}; use futures::future::{err, Either};
use futures::{Future, Stream}; use futures::{Future, Stream};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
use tokio_timer::Timeout; use tokio_timer::Timeout;
use actix_http::body::{Body, BodyStream}; use actix_http::body::{Body, BodyStream};
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::encoding::Decoder; use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::{ use actix_http::http::{
@ -59,7 +60,6 @@ const HTTPS_ENCODING: &str = "gzip, deflate";
pub struct ClientRequest { pub struct ClientRequest {
pub(crate) head: RequestHead, pub(crate) head: RequestHead,
err: Option<HttpError>, err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
default_headers: bool, default_headers: bool,
response_decompress: bool, response_decompress: bool,
@ -77,7 +77,6 @@ impl ClientRequest {
config, config,
head: RequestHead::default(), head: RequestHead::default(),
err: None, err: None,
#[cfg(feature = "cookies")]
cookies: None, cookies: None,
timeout: None, timeout: None,
default_headers: true, default_headers: true,
@ -268,7 +267,6 @@ impl ClientRequest {
self.header(header::AUTHORIZATION, format!("Bearer {}", token)) self.header(header::AUTHORIZATION, format!("Bearer {}", token))
} }
#[cfg(feature = "cookies")]
/// Set a cookie /// Set a cookie
/// ///
/// ```rust /// ```rust
@ -437,28 +435,20 @@ impl ClientRequest {
} }
}; };
#[allow(unused_mut)]
let mut head = slf.head; let mut head = slf.head;
#[cfg(feature = "cookies")] // set cookies
{ if let Some(ref mut jar) = slf.cookies {
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; let mut cookie = String::new();
use std::fmt::Write; for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
// set cookies let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
if let Some(ref mut jar) = slf.cookies { let _ = write!(&mut cookie, "; {}={}", name, value);
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value =
percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
} }
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
} }
let config = slf.config; let config = slf.config;

View File

@ -9,10 +9,8 @@ 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};
#[cfg(feature = "cookies")] use actix_http::cookie::Cookie;
use actix_http::error::CookieParseError; use actix_http::error::CookieParseError;
#[cfg(feature = "cookies")]
use cookie::Cookie;
/// Client Response /// Client Response
pub struct ClientResponse<S = PayloadStream> { pub struct ClientResponse<S = PayloadStream> {
@ -41,7 +39,6 @@ impl<S> HttpMessage for ClientResponse<S> {
/// Load request cookies. /// Load request cookies.
#[inline] #[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>>);

View File

@ -1,10 +1,12 @@
//! Test helpers for actix http client to use during testing. //! Test helpers for actix http client to use during testing.
use actix_http::http::header::{Header, IntoHeaderValue}; use std::fmt::Write as FmtWrite;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue};
use actix_http::http::{HeaderName, HttpTryFrom, Version}; use actix_http::http::{HeaderName, HttpTryFrom, Version};
use actix_http::{h1, Payload, ResponseHead}; use actix_http::{h1, Payload, ResponseHead};
use bytes::Bytes; use bytes::Bytes;
#[cfg(feature = "cookies")] use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use cookie::{Cookie, CookieJar};
use crate::ClientResponse; use crate::ClientResponse;
@ -30,7 +32,6 @@ where
/// 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>,
} }
@ -39,7 +40,6 @@ impl Default for TestResponse {
fn default() -> TestResponse { fn default() -> TestResponse {
TestResponse { TestResponse {
head: ResponseHead::default(), head: ResponseHead::default(),
#[cfg(feature = "cookies")]
cookies: CookieJar::new(), cookies: CookieJar::new(),
payload: None, payload: None,
} }
@ -87,7 +87,6 @@ impl TestResponse {
} }
/// Set cookie for this response /// Set cookie for this response
#[cfg(feature = "cookies")]
pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self {
self.cookies.add(cookie.into_owned()); self.cookies.add(cookie.into_owned());
self self
@ -105,25 +104,17 @@ impl TestResponse {
pub fn finish(self) -> ClientResponse { pub fn finish(self) -> ClientResponse {
let mut head = self.head; let mut head = self.head;
#[cfg(feature = "cookies")] let mut cookie = String::new();
{ for c in self.cookies.delta() {
use std::fmt::Write as FmtWrite; let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
use actix_http::http::header::{self, HeaderValue}; let _ = write!(&mut cookie, "; {}={}", name, value);
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; }
if !cookie.is_empty() {
let mut cookie = String::new(); head.headers.insert(
for c in self.cookies.delta() { header::SET_COOKIE,
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); );
let _ = write!(&mut cookie, "; {}={}", name, value);
}
if !cookie.is_empty() {
head.headers.insert(
header::SET_COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
} }
if let Some(pl) = self.payload { if let Some(pl) = self.payload {

View File

@ -1,14 +1,15 @@
//! Websockets client //! Websockets client
use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, str}; use std::{fmt, str};
use actix_codec::Framed; use actix_codec::Framed;
use actix_http::cookie::{Cookie, CookieJar};
use actix_http::{ws, Payload, RequestHead}; use actix_http::{ws, Payload, RequestHead};
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
#[cfg(feature = "cookies")]
use cookie::{Cookie, CookieJar};
use futures::future::{err, Either, Future}; use futures::future::{err, Either, Future};
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use tokio_timer::Timeout; use tokio_timer::Timeout;
pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message};
@ -33,7 +34,6 @@ pub struct WebsocketsRequest {
max_size: usize, max_size: usize,
server_mode: bool, server_mode: bool,
default_headers: bool, default_headers: bool,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>, cookies: Option<CookieJar>,
config: Rc<ClientConfig>, config: Rc<ClientConfig>,
} }
@ -62,7 +62,6 @@ 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,
default_headers: true, default_headers: true,
} }
@ -82,7 +81,6 @@ impl WebsocketsRequest {
self self
} }
#[cfg(feature = "cookies")]
/// Set a cookie /// Set a cookie
pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self {
if self.cookies.is_none() { if self.cookies.is_none() {
@ -264,28 +262,20 @@ impl WebsocketsRequest {
self self
}; };
#[allow(unused_mut)]
let mut head = slf.head; let mut head = slf.head;
#[cfg(feature = "cookies")] // set cookies
{ if let Some(ref mut jar) = slf.cookies {
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; let mut cookie = String::new();
use std::fmt::Write; for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
// set cookies let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
if let Some(ref mut jar) = slf.cookies { let _ = write!(&mut cookie, "; {}={}", name, value);
let mut cookie = String::new();
for c in jar.delta() {
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
let value =
percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
let _ = write!(&mut cookie, "; {}={}", name, value);
}
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
} }
head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
} }
// origin // origin

View File

@ -108,7 +108,7 @@ pub use actix_web_codegen::*;
// re-export for convenience // re-export for convenience
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;
pub use actix_http::{http, Error, HttpMessage, ResponseError, Result}; pub use actix_http::{cookie, http, Error, HttpMessage, ResponseError, Result};
pub use crate::app::App; pub use crate::app::App;
pub use crate::extract::FromRequest; pub use crate::extract::FromRequest;

View File

@ -51,11 +51,11 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use cookie::{Cookie, CookieJar, Key, SameSite};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Future, IntoFuture, Poll}; use futures::{Future, IntoFuture, Poll};
use time::Duration; use time::Duration;
use crate::cookie::{Cookie, CookieJar, Key, SameSite};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::http::header::{self, HeaderValue}; use crate::http::header::{self, HeaderValue};
use crate::request::HttpRequest; use crate::request::HttpRequest;

View File

@ -18,5 +18,5 @@ mod logger;
pub use self::defaultheaders::DefaultHeaders; pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger; pub use self::logger::Logger;
#[cfg(feature = "cookies")] #[cfg(feature = "secure-cookies")]
pub mod identity; pub mod identity;

View File

@ -263,14 +263,12 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "cookies")]
fn test_no_request_cookies() { fn test_no_request_cookies() {
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
assert!(req.cookies().unwrap().is_empty()); assert!(req.cookies().unwrap().is_empty());
} }
#[test] #[test]
#[cfg(feature = "cookies")]
fn test_request_cookies() { fn test_request_cookies() {
let req = TestRequest::default() let req = TestRequest::default()
.header(header::COOKIE, "cookie1=value1") .header(header::COOKIE, "cookie1=value1")

View File

@ -2,6 +2,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_http::cookie::Cookie;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Version};
use actix_http::test::TestRequest as HttpTestRequest; use actix_http::test::TestRequest as HttpTestRequest;
@ -11,8 +12,6 @@ use actix_rt::Runtime;
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::{FnService, IntoNewService, NewService, Service}; use actix_service::{FnService, IntoNewService, NewService, Service};
use bytes::Bytes; use bytes::Bytes;
#[cfg(feature = "cookies")]
use cookie::Cookie;
use futures::future::{lazy, Future}; use futures::future::{lazy, Future};
use crate::config::{AppConfig, AppConfigInner}; use crate::config::{AppConfig, AppConfigInner};
@ -285,7 +284,6 @@ impl TestRequest {
self self
} }
#[cfg(feature = "cookies")]
/// Set cookie for this request /// Set cookie for this request
pub fn cookie(mut self, cookie: Cookie) -> Self { pub fn cookie(mut self, cookie: Cookie) -> Self {
self.req.cookie(cookie); self.req.cookie(cookie);

View File

@ -17,17 +17,14 @@ edition = "2018"
workspace = ".." workspace = ".."
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["session"] features = []
[lib] [lib]
name = "actix_http_test" name = "actix_http_test"
path = "src/lib.rs" path = "src/lib.rs"
[features] [features]
default = ["session"] default = []
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
# openssl # openssl
ssl = ["openssl", "actix-server/ssl", "awc/ssl"] ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
@ -42,7 +39,6 @@ awc = { path = "../awc" }
base64 = "0.10" base64 = "0.10"
bytes = "0.4" bytes = "0.4"
cookie = { version="0.11", features=["percent-encode"] }
futures = "0.1" futures = "0.1"
http = "0.1.8" http = "0.1.8"
log = "0.4" log = "0.4"
@ -60,4 +56,5 @@ tokio-timer = "0.2"
openssl = { version="0.10", optional = true } openssl = { version="0.10", optional = true }
[dev-dependencies] [dev-dependencies]
actix-web = "1.0.0-alpha.1" actix-web = { path = ".." }
actix-http = { path = "../actix-http" }

View File

@ -23,7 +23,7 @@ use net2::TcpBuilder;
/// use actix_http::HttpService; /// use actix_http::HttpService;
/// use actix_http_test::TestServer; /// use actix_http_test::TestServer;
/// use actix_web::{web, App, HttpResponse}; /// use actix_web::{web, App, HttpResponse};
/// # ///
/// fn my_handler() -> HttpResponse { /// fn my_handler() -> HttpResponse {
/// HttpResponse::Ok().into() /// HttpResponse::Ok().into()
/// } /// }