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:
parent
193f8fb2d9
commit
d846328f36
12
Cargo.toml
12
Cargo.toml
@ -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
|
||||||
|
@ -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"] }
|
||||||
|
@ -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 }
|
||||||
|
|
||||||
|
@ -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))),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
240
actix-http/src/cookie/builder.rs
Normal file
240
actix-http/src/cookie/builder.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
71
actix-http/src/cookie/delta.rs
Normal file
71
actix-http/src/cookie/delta.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
98
actix-http/src/cookie/draft.rs
Normal file
98
actix-http/src/cookie/draft.rs
Normal 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(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
653
actix-http/src/cookie/jar.rs
Normal file
653
actix-http/src/cookie/jar.rs
Normal 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
1087
actix-http/src/cookie/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
426
actix-http/src/cookie/parse.rs
Normal file
426
actix-http/src/cookie/parse.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
180
actix-http/src/cookie/secure/key.rs
Normal file
180
actix-http/src/cookie/secure/key.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
40
actix-http/src/cookie/secure/macros.rs
Normal file
40
actix-http/src/cookie/secure/macros.rs
Normal 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());
|
||||||
|
}};
|
||||||
|
}
|
10
actix-http/src/cookie/secure/mod.rs
Normal file
10
actix-http/src/cookie/secure/mod.rs
Normal 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::*;
|
226
actix-http/src/cookie/secure/private.rs
Normal file
226
actix-http/src/cookie/secure/private.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
185
actix-http/src/cookie/secure/signed.rs
Normal file
185
actix-http/src/cookie/secure/signed.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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"] }
|
||||||
|
@ -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"] }
|
||||||
|
@ -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]
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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>>);
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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")
|
||||||
|
@ -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);
|
||||||
|
@ -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" }
|
||||||
|
@ -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()
|
||||||
/// }
|
/// }
|
||||||
|
Loading…
Reference in New Issue
Block a user