mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into feature/type-id-private
This commit is contained in:
commit
1799e40f42
|
@ -27,26 +27,6 @@ jobs:
|
|||
profile: minimal
|
||||
override: true
|
||||
|
||||
- name: Generate Cargo.lock
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: generate-lockfile
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: target
|
||||
key: ${{ matrix.version }}-x86_64-pc-windows-msvc-cargo-build-trimmed-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install OpenSSL
|
||||
run: |
|
||||
vcpkg integrate install
|
||||
|
@ -74,8 +54,5 @@ jobs:
|
|||
--skip=test_expect_continue
|
||||
--skip=test_http10_keepalive
|
||||
--skip=test_slow_request
|
||||
|
||||
- name: Clear the cargo caches
|
||||
run: |
|
||||
cargo install cargo-cache --no-default-features --features ci-autoclean
|
||||
cargo-cache
|
||||
--skip=test_connection_force_close
|
||||
--skip=test_connection_server_close
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
# Changes
|
||||
|
||||
|
||||
## [2.0.NEXT] - 2020-01-xx
|
||||
|
||||
### Changed
|
||||
|
||||
* Use `sha-1` crate instead of unmaintained `sha1` crate
|
||||
* Use `sha-1` crate instead of unmaintained `sha1` crate
|
||||
|
||||
* Skip empty chunks when returning response from a `Stream` #1308
|
||||
|
||||
* Update the `time` dependency to 0.2.5
|
||||
|
||||
## [2.0.0] - 2019-12-25
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ regex = "1.3"
|
|||
serde = { version = "1.0", features=["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.6.1"
|
||||
time = "0.1.42"
|
||||
time = { version = "0.2.5", default-features = false, features = ["std"] }
|
||||
url = "2.1"
|
||||
open-ssl = { version="0.10", package = "openssl", optional = true }
|
||||
rust-tls = { version = "0.16.0", package = "rustls", optional = true }
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
# [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
* Update the `time` dependency to 0.2.5
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow `SameSite=None` cookies to be sent in a response.
|
||||
|
|
|
@ -52,7 +52,6 @@ base64 = "0.11"
|
|||
bitflags = "1.2"
|
||||
bytes = "0.5.3"
|
||||
copyless = "0.1.4"
|
||||
chrono = "0.4.6"
|
||||
derive_more = "0.99.2"
|
||||
either = "1.5.3"
|
||||
encoding_rs = "0.8"
|
||||
|
@ -77,7 +76,7 @@ serde_json = "1.0"
|
|||
sha-1 = "0.8"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.6.1"
|
||||
time = "0.1.42"
|
||||
time = { version = "0.2.5", default-features = false, features = ["std"] }
|
||||
|
||||
# for secure cookie
|
||||
ring = { version = "0.16.9", optional = true }
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::{fmt, mem};
|
|||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures_core::Stream;
|
||||
use futures_util::ready;
|
||||
use pin_project::{pin_project, project};
|
||||
|
||||
use crate::error::Error;
|
||||
|
@ -389,12 +390,19 @@ where
|
|||
BodySize::Stream
|
||||
}
|
||||
|
||||
/// Attempts to pull out the next value of the underlying [`Stream`].
|
||||
///
|
||||
/// Empty values are skipped to prevent [`BodyStream`]'s transmission being
|
||||
/// ended on a zero-length chunk, but rather proceed until the underlying
|
||||
/// [`Stream`] ends.
|
||||
fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
|
||||
unsafe { Pin::new_unchecked(self) }
|
||||
.project()
|
||||
.stream
|
||||
.poll_next(cx)
|
||||
.map(|res| res.map(|res| res.map_err(std::convert::Into::into)))
|
||||
let mut stream = unsafe { Pin::new_unchecked(self) }.project().stream;
|
||||
loop {
|
||||
return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
||||
opt => opt.map(|res| res.map_err(Into::into)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,17 +432,26 @@ where
|
|||
BodySize::Sized64(self.size)
|
||||
}
|
||||
|
||||
/// Attempts to pull out the next value of the underlying [`Stream`].
|
||||
///
|
||||
/// Empty values are skipped to prevent [`SizedStream`]'s transmission being
|
||||
/// ended on a zero-length chunk, but rather proceed until the underlying
|
||||
/// [`Stream`] ends.
|
||||
fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
|
||||
unsafe { Pin::new_unchecked(self) }
|
||||
.project()
|
||||
.stream
|
||||
.poll_next(cx)
|
||||
let mut stream = unsafe { Pin::new_unchecked(self) }.project().stream;
|
||||
loop {
|
||||
return Poll::Ready(match ready!(stream.as_mut().poll_next(cx)) {
|
||||
Some(Ok(ref bytes)) if bytes.is_empty() => continue,
|
||||
val => val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::stream;
|
||||
use futures_util::future::poll_fn;
|
||||
|
||||
impl Body {
|
||||
|
@ -589,4 +606,45 @@ mod tests {
|
|||
BodySize::Sized(25)
|
||||
);
|
||||
}
|
||||
|
||||
mod body_stream {
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn skips_empty_chunks() {
|
||||
let mut body = BodyStream::new(stream::iter(
|
||||
["1", "", "2"]
|
||||
.iter()
|
||||
.map(|&v| Ok(Bytes::from(v)) as Result<Bytes, ()>),
|
||||
));
|
||||
assert_eq!(
|
||||
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("1")),
|
||||
);
|
||||
assert_eq!(
|
||||
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("2")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod sized_stream {
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn skips_empty_chunks() {
|
||||
let mut body = SizedStream::new(
|
||||
2,
|
||||
stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))),
|
||||
);
|
||||
assert_eq!(
|
||||
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("1")),
|
||||
);
|
||||
assert_eq!(
|
||||
poll_fn(|cx| body.poll_next(cx)).await.unwrap().ok(),
|
||||
Some(Bytes::from("2")),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -488,10 +488,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project(PinnedDrop)]
|
||||
struct OpenWaitingConnection<F, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
{
|
||||
#[pin]
|
||||
fut: F,
|
||||
key: Key,
|
||||
h2: Option<
|
||||
|
@ -525,12 +527,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<F, Io> Drop for OpenWaitingConnection<F, Io>
|
||||
#[pin_project::pinned_drop]
|
||||
impl<F, Io> PinnedDrop for OpenWaitingConnection<F, Io>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.inner.take() {
|
||||
fn drop(self: Pin<&mut Self>) {
|
||||
if let Some(inner) = self.project().inner.take() {
|
||||
let mut inner = inner.as_ref().borrow_mut();
|
||||
inner.release();
|
||||
inner.check_availibility();
|
||||
|
@ -545,8 +548,8 @@ where
|
|||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
|
||||
if let Some(ref mut h2) = this.h2 {
|
||||
return match Pin::new(h2).poll(cx) {
|
||||
|
@ -571,7 +574,7 @@ where
|
|||
};
|
||||
}
|
||||
|
||||
match unsafe { Pin::new_unchecked(&mut this.fut) }.poll(cx) {
|
||||
match this.fut.poll(cx) {
|
||||
Poll::Ready(Err(err)) => {
|
||||
let _ = this.inner.take();
|
||||
if let Some(rx) = this.rx.take() {
|
||||
|
@ -589,8 +592,8 @@ where
|
|||
)));
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
this.h2 = Some(handshake(io).boxed_local());
|
||||
unsafe { Pin::new_unchecked(this) }.poll(cx)
|
||||
*this.h2 = Some(handshake(io).boxed_local());
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::cell::UnsafeCell;
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
@ -7,7 +7,7 @@ use std::{fmt, net};
|
|||
use actix_rt::time::{delay_for, delay_until, Delay, Instant};
|
||||
use bytes::BytesMut;
|
||||
use futures_util::{future, FutureExt};
|
||||
use time;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||
const DATE_VALUE_LENGTH: usize = 29;
|
||||
|
@ -211,7 +211,7 @@ impl Date {
|
|||
}
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
|
||||
write!(self, "{}", OffsetDateTime::now().format("%a, %d %b %Y %H:%M:%S GMT")).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,24 +228,24 @@ impl fmt::Write for Date {
|
|||
struct DateService(Rc<DateServiceInner>);
|
||||
|
||||
struct DateServiceInner {
|
||||
current: UnsafeCell<Option<(Date, Instant)>>,
|
||||
current: Cell<Option<(Date, Instant)>>,
|
||||
}
|
||||
|
||||
impl DateServiceInner {
|
||||
fn new() -> Self {
|
||||
DateServiceInner {
|
||||
current: UnsafeCell::new(None),
|
||||
current: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
unsafe { (&mut *self.current.get()).take() };
|
||||
self.current.take();
|
||||
}
|
||||
|
||||
fn update(&self) {
|
||||
let now = Instant::now();
|
||||
let date = Date::new();
|
||||
*(unsafe { &mut *self.current.get() }) = Some((date, now));
|
||||
self.current.set(Some((date, now)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,7 +255,7 @@ impl DateService {
|
|||
}
|
||||
|
||||
fn check_date(&self) {
|
||||
if unsafe { (&*self.0.current.get()).is_none() } {
|
||||
if self.0.current.get().is_none() {
|
||||
self.0.update();
|
||||
|
||||
// periodic date update
|
||||
|
@ -269,12 +269,12 @@ impl DateService {
|
|||
|
||||
fn now(&self) -> Instant {
|
||||
self.check_date();
|
||||
unsafe { (&*self.0.current.get()).as_ref().unwrap().1 }
|
||||
self.0.current.get().unwrap().1
|
||||
}
|
||||
|
||||
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
|
||||
self.check_date();
|
||||
f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 })
|
||||
f(&self.0.current.get().unwrap().0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,19 @@ impl DateService {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
// Test modifying the date from within the closure
|
||||
// passed to `set_date`
|
||||
#[test]
|
||||
fn test_evil_date() {
|
||||
let service = DateService::new();
|
||||
// Make sure that `check_date` doesn't try to spawn a task
|
||||
service.0.update();
|
||||
service.set_date(|_| {
|
||||
service.0.reset()
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_len() {
|
||||
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use chrono::Duration;
|
||||
use time::Tm;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use super::{Cookie, SameSite};
|
||||
|
||||
|
@ -64,13 +63,13 @@ impl CookieBuilder {
|
|||
/// use actix_http::cookie::Cookie;
|
||||
///
|
||||
/// let c = Cookie::build("foo", "bar")
|
||||
/// .expires(time::now())
|
||||
/// .expires(time::OffsetDateTime::now())
|
||||
/// .finish();
|
||||
///
|
||||
/// assert!(c.expires().is_some());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn expires(mut self, when: Tm) -> CookieBuilder {
|
||||
pub fn expires(mut self, when: OffsetDateTime) -> CookieBuilder {
|
||||
self.cookie.set_expires(when);
|
||||
self
|
||||
}
|
||||
|
@ -108,7 +107,9 @@ impl CookieBuilder {
|
|||
/// ```
|
||||
#[inline]
|
||||
pub fn max_age_time(mut self, value: Duration) -> CookieBuilder {
|
||||
self.cookie.set_max_age(value);
|
||||
// Truncate any nanoseconds from the Duration, as they aren't represented within `Max-Age`
|
||||
// and would cause two otherwise identical `Cookie` instances to not be equivalent to one another.
|
||||
self.cookie.set_max_age(Duration::seconds(value.whole_seconds()));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -212,7 +213,7 @@ impl CookieBuilder {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use chrono::Duration;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// let c = Cookie::build("foo", "bar")
|
||||
/// .permanent()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashSet;
|
||||
use std::mem::replace;
|
||||
|
||||
use chrono::Duration;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use super::delta::DeltaCookie;
|
||||
use super::Cookie;
|
||||
|
@ -188,7 +188,7 @@ impl CookieJar {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::{CookieJar, Cookie};
|
||||
/// use chrono::Duration;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// let mut jar = CookieJar::new();
|
||||
///
|
||||
|
@ -202,7 +202,7 @@ impl CookieJar {
|
|||
/// 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)));
|
||||
/// assert_eq!(delta[0].max_age(), Some(Duration::zero()));
|
||||
/// ```
|
||||
///
|
||||
/// Removing a new cookie does not result in a _removal_ cookie:
|
||||
|
@ -220,8 +220,8 @@ impl CookieJar {
|
|||
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));
|
||||
cookie.set_max_age(Duration::zero());
|
||||
cookie.set_expires(OffsetDateTime::now() - Duration::days(365));
|
||||
self.delta_cookies.replace(DeltaCookie::removed(cookie));
|
||||
} else {
|
||||
self.delta_cookies.remove(cookie.name());
|
||||
|
@ -239,7 +239,7 @@ impl CookieJar {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::{CookieJar, Cookie};
|
||||
/// use chrono::Duration;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// let mut jar = CookieJar::new();
|
||||
///
|
||||
|
@ -533,7 +533,7 @@ mod test {
|
|||
#[test]
|
||||
#[cfg(feature = "secure-cookies")]
|
||||
fn delta() {
|
||||
use chrono::Duration;
|
||||
use time::Duration;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut c = CookieJar::new();
|
||||
|
@ -556,7 +556,7 @@ mod test {
|
|||
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)));
|
||||
assert_eq!(names.get("original").unwrap(), &Some(Duration::zero()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -65,9 +65,8 @@ use std::borrow::Cow;
|
|||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::Duration;
|
||||
use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
|
||||
use time::Tm;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
pub use self::builder::CookieBuilder;
|
||||
pub use self::draft::*;
|
||||
|
@ -172,7 +171,7 @@ pub struct Cookie<'c> {
|
|||
/// The cookie's value.
|
||||
value: CookieStr,
|
||||
/// The cookie's expiration, if any.
|
||||
expires: Option<Tm>,
|
||||
expires: Option<OffsetDateTime>,
|
||||
/// The cookie's maximum age, if any.
|
||||
max_age: Option<Duration>,
|
||||
/// The cookie's domain, if any.
|
||||
|
@ -479,7 +478,7 @@ impl<'c> Cookie<'c> {
|
|||
/// assert_eq!(c.max_age(), None);
|
||||
///
|
||||
/// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
|
||||
/// assert_eq!(c.max_age().map(|age| age.num_hours()), Some(1));
|
||||
/// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn max_age(&self) -> Option<Duration> {
|
||||
|
@ -544,10 +543,10 @@ impl<'c> Cookie<'c> {
|
|||
/// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
|
||||
/// let cookie_str = format!("name=value; Expires={}", expire_time);
|
||||
/// let c = Cookie::parse(cookie_str).unwrap();
|
||||
/// assert_eq!(c.expires().map(|t| t.tm_year), Some(117));
|
||||
/// assert_eq!(c.expires().map(|t| t.year()), Some(2017));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn expires(&self) -> Option<Tm> {
|
||||
pub fn expires(&self) -> Option<OffsetDateTime> {
|
||||
self.expires
|
||||
}
|
||||
|
||||
|
@ -645,7 +644,7 @@ impl<'c> Cookie<'c> {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use chrono::Duration;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// let mut c = Cookie::new("name", "value");
|
||||
/// assert_eq!(c.max_age(), None);
|
||||
|
@ -698,18 +697,19 @@ impl<'c> Cookie<'c> {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use time::{Duration, OffsetDateTime};
|
||||
///
|
||||
/// let mut c = Cookie::new("name", "value");
|
||||
/// assert_eq!(c.expires(), None);
|
||||
///
|
||||
/// let mut now = time::now();
|
||||
/// now.tm_year += 1;
|
||||
/// let mut now = OffsetDateTime::now();
|
||||
/// now += Duration::week();
|
||||
///
|
||||
/// c.set_expires(now);
|
||||
/// assert!(c.expires().is_some())
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set_expires(&mut self, time: Tm) {
|
||||
pub fn set_expires(&mut self, time: OffsetDateTime) {
|
||||
self.expires = Some(time);
|
||||
}
|
||||
|
||||
|
@ -720,7 +720,7 @@ impl<'c> Cookie<'c> {
|
|||
///
|
||||
/// ```rust
|
||||
/// use actix_http::cookie::Cookie;
|
||||
/// use chrono::Duration;
|
||||
/// use time::Duration;
|
||||
///
|
||||
/// let mut c = Cookie::new("foo", "bar");
|
||||
/// assert!(c.expires().is_none());
|
||||
|
@ -733,7 +733,7 @@ impl<'c> Cookie<'c> {
|
|||
pub fn make_permanent(&mut self) {
|
||||
let twenty_years = Duration::days(365 * 20);
|
||||
self.set_max_age(twenty_years);
|
||||
self.set_expires(time::now() + twenty_years);
|
||||
self.set_expires(OffsetDateTime::now() + twenty_years);
|
||||
}
|
||||
|
||||
fn fmt_parameters(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -758,11 +758,11 @@ impl<'c> Cookie<'c> {
|
|||
}
|
||||
|
||||
if let Some(max_age) = self.max_age() {
|
||||
write!(f, "; Max-Age={}", max_age.num_seconds())?;
|
||||
write!(f, "; Max-Age={}", max_age.whole_seconds())?;
|
||||
}
|
||||
|
||||
if let Some(time) = self.expires() {
|
||||
write!(f, "; Expires={}", time.rfc822())?;
|
||||
write!(f, "; Expires={}", time.format("%a, %d %b %Y %H:%M:%S GMT"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -990,7 +990,7 @@ impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Cookie, SameSite};
|
||||
use time::strptime;
|
||||
use time::{offset, PrimitiveDateTime};
|
||||
|
||||
#[test]
|
||||
fn format() {
|
||||
|
@ -1015,7 +1015,7 @@ mod tests {
|
|||
assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
|
||||
|
||||
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();
|
||||
let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().using_offset(offset!(UTC));
|
||||
let cookie = Cookie::build("foo", "bar").expires(expires).finish();
|
||||
assert_eq!(
|
||||
&cookie.to_string(),
|
||||
|
|
|
@ -5,11 +5,13 @@ use std::error::Error;
|
|||
use std::fmt;
|
||||
use std::str::Utf8Error;
|
||||
|
||||
use chrono::Duration;
|
||||
use percent_encoding::percent_decode;
|
||||
use time::{Duration, offset};
|
||||
|
||||
use super::{Cookie, CookieStr, SameSite};
|
||||
|
||||
use crate::time_parser;
|
||||
|
||||
/// Enum corresponding to a parsing error.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ParseError {
|
||||
|
@ -147,7 +149,7 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
|
|||
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());
|
||||
let val = cmp::min(val, Duration::max_value().whole_seconds());
|
||||
Some(Duration::seconds(val))
|
||||
}
|
||||
Err(_) => continue,
|
||||
|
@ -179,16 +181,14 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
|
|||
}
|
||||
}
|
||||
("expires", Some(v)) => {
|
||||
// Try strptime with three date formats according to
|
||||
// Try parsing 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"));
|
||||
let tm = time_parser::parse_http_date(v)
|
||||
.or_else(|| time::parse(v, "%a, %d-%b-%Y %H:%M:%S").ok());
|
||||
|
||||
if let Ok(time) = tm {
|
||||
cookie.expires = Some(time)
|
||||
if let Some(time) = tm {
|
||||
cookie.expires = Some(time.using_offset(offset!(UTC)))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -216,8 +216,7 @@ where
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Cookie, SameSite};
|
||||
use chrono::Duration;
|
||||
use time::strptime;
|
||||
use time::{offset, Duration, PrimitiveDateTime};
|
||||
|
||||
macro_rules! assert_eq_parse {
|
||||
($string:expr, $expected:expr) => {
|
||||
|
@ -377,7 +376,7 @@ mod tests {
|
|||
);
|
||||
|
||||
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();
|
||||
let expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%M:%S").unwrap().using_offset(offset!(UTC));
|
||||
expected.set_expires(expires);
|
||||
assert_eq_parse!(
|
||||
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
|
||||
|
@ -386,7 +385,7 @@ mod tests {
|
|||
);
|
||||
|
||||
unexpected.set_domain("foo.com");
|
||||
let bad_expires = strptime(time_str, "%a, %d %b %Y %H:%S:%M %Z").unwrap();
|
||||
let bad_expires = PrimitiveDateTime::parse(time_str, "%a, %d %b %Y %H:%S:%M").unwrap().using_offset(offset!(UTC));
|
||||
expected.set_expires(bad_expires);
|
||||
assert_ne_parse!(
|
||||
" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
|
||||
|
@ -414,8 +413,9 @@ mod tests {
|
|||
|
||||
#[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(max_seconds).finish();
|
||||
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", max_seconds + 1), expected);
|
||||
let max_duration = Duration::max_value();
|
||||
let expected = Cookie::build("foo", "bar").max_age_time(max_duration).finish();
|
||||
let overflow_duration = max_duration.checked_add(Duration::nanoseconds(1)).unwrap_or(max_duration);
|
||||
assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", overflow_duration.whole_seconds()), expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,14 +158,16 @@ where
|
|||
|
||||
#[pin_project::pin_project]
|
||||
struct ServiceResponse<F, I, E, B> {
|
||||
#[pin]
|
||||
state: ServiceResponseState<F, B>,
|
||||
config: ServiceConfig,
|
||||
buffer: Option<Bytes>,
|
||||
_t: PhantomData<(I, E)>,
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
enum ServiceResponseState<F, B> {
|
||||
ServiceCall(F, Option<SendResponse<Bytes>>),
|
||||
ServiceCall(#[pin] F, Option<SendResponse<Bytes>>),
|
||||
SendPayload(SendStream<Bytes>, ResponseBody<B>),
|
||||
}
|
||||
|
||||
|
@ -247,12 +249,14 @@ where
|
|||
{
|
||||
type Output = ();
|
||||
|
||||
#[pin_project::project]
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.as_mut().project();
|
||||
|
||||
match this.state {
|
||||
ServiceResponseState::ServiceCall(ref mut call, ref mut send) => {
|
||||
match unsafe { Pin::new_unchecked(call) }.poll(cx) {
|
||||
#[project]
|
||||
match this.state.project() {
|
||||
ServiceResponseState::ServiceCall(call, send) => {
|
||||
match call.poll(cx) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
let (res, body) = res.into().replace_body(());
|
||||
|
||||
|
@ -273,8 +277,7 @@ where
|
|||
if size.is_eof() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
*this.state =
|
||||
ServiceResponseState::SendPayload(stream, body);
|
||||
this.state.set(ServiceResponseState::SendPayload(stream, body));
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
|
@ -300,10 +303,10 @@ where
|
|||
if size.is_eof() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
*this.state = ServiceResponseState::SendPayload(
|
||||
this.state.set(ServiceResponseState::SendPayload(
|
||||
stream,
|
||||
body.into_body(),
|
||||
);
|
||||
));
|
||||
self.poll(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,46 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::io::Write;
|
||||
use std::str::FromStr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use bytes::{buf::BufMutExt, BytesMut};
|
||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||
use time::{PrimitiveDateTime, OffsetDateTime, offset};
|
||||
|
||||
use crate::error::ParseError;
|
||||
use crate::header::IntoHeaderValue;
|
||||
use crate::time_parser;
|
||||
|
||||
/// A timestamp with HTTP formatting and parsing
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct HttpDate(time::Tm);
|
||||
pub struct HttpDate(OffsetDateTime);
|
||||
|
||||
impl FromStr for HttpDate {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<HttpDate, ParseError> {
|
||||
match time::strptime(s, "%a, %d %b %Y %T %Z")
|
||||
.or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z"))
|
||||
.or_else(|_| time::strptime(s, "%c"))
|
||||
{
|
||||
Ok(t) => Ok(HttpDate(t)),
|
||||
Err(_) => Err(ParseError::Header),
|
||||
match time_parser::parse_http_date(s) {
|
||||
Some(t) => Ok(HttpDate(t.using_offset(offset!(UTC)))),
|
||||
None => Err(ParseError::Header)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HttpDate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
|
||||
fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::Tm> for HttpDate {
|
||||
fn from(tm: time::Tm) -> HttpDate {
|
||||
HttpDate(tm)
|
||||
impl From<OffsetDateTime> for HttpDate {
|
||||
fn from(dt: OffsetDateTime) -> HttpDate {
|
||||
HttpDate(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SystemTime> for HttpDate {
|
||||
fn from(sys: SystemTime) -> HttpDate {
|
||||
let tmspec = match sys.duration_since(UNIX_EPOCH) {
|
||||
Ok(dur) => {
|
||||
time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32)
|
||||
}
|
||||
Err(err) => {
|
||||
let neg = err.duration();
|
||||
time::Timespec::new(
|
||||
-(neg.as_secs() as i64),
|
||||
-(neg.subsec_nanos() as i32),
|
||||
)
|
||||
}
|
||||
};
|
||||
HttpDate(time::at_utc(tmspec))
|
||||
HttpDate(PrimitiveDateTime::from(sys).using_offset(offset!(UTC)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,56 +49,45 @@ impl IntoHeaderValue for HttpDate {
|
|||
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let mut wrt = BytesMut::with_capacity(29).writer();
|
||||
write!(wrt, "{}", self.0.rfc822()).unwrap();
|
||||
write!(wrt, "{}", self.0.to_offset(offset!(UTC)).format("%a, %d %b %Y %H:%M:%S GMT")).unwrap();
|
||||
HeaderValue::from_maybe_shared(wrt.get_mut().split().freeze())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpDate> for SystemTime {
|
||||
fn from(date: HttpDate) -> SystemTime {
|
||||
let spec = date.0.to_timespec();
|
||||
if spec.sec >= 0 {
|
||||
UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32)
|
||||
} else {
|
||||
UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32)
|
||||
}
|
||||
let dt = date.0;
|
||||
let epoch = OffsetDateTime::unix_epoch();
|
||||
|
||||
UNIX_EPOCH + (dt - epoch)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::HttpDate;
|
||||
use time::Tm;
|
||||
|
||||
const NOV_07: HttpDate = HttpDate(Tm {
|
||||
tm_nsec: 0,
|
||||
tm_sec: 37,
|
||||
tm_min: 48,
|
||||
tm_hour: 8,
|
||||
tm_mday: 7,
|
||||
tm_mon: 10,
|
||||
tm_year: 94,
|
||||
tm_wday: 0,
|
||||
tm_isdst: 0,
|
||||
tm_yday: 0,
|
||||
tm_utcoff: 0,
|
||||
});
|
||||
use time::{PrimitiveDateTime, date, time, offset};
|
||||
|
||||
#[test]
|
||||
fn test_date() {
|
||||
let nov_07 = HttpDate(PrimitiveDateTime::new(
|
||||
date!(1994-11-07),
|
||||
time!(8:48:37)
|
||||
).using_offset(offset!(UTC)));
|
||||
|
||||
assert_eq!(
|
||||
"Sun, 07 Nov 1994 08:48:37 GMT".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
nov_07
|
||||
);
|
||||
assert_eq!(
|
||||
"Sunday, 07-Nov-94 08:48:37 GMT"
|
||||
.parse::<HttpDate>()
|
||||
.unwrap(),
|
||||
NOV_07
|
||||
nov_07
|
||||
);
|
||||
assert_eq!(
|
||||
"Sun Nov 7 08:48:37 1994".parse::<HttpDate>().unwrap(),
|
||||
NOV_07
|
||||
nov_07
|
||||
);
|
||||
assert!("this-is-no-date".parse::<HttpDate>().is_err());
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ mod payload;
|
|||
mod request;
|
||||
mod response;
|
||||
mod service;
|
||||
mod time_parser;
|
||||
|
||||
pub mod cookie;
|
||||
pub mod error;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
use time::{PrimitiveDateTime, Date};
|
||||
|
||||
/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime.
|
||||
pub fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
|
||||
try_parse_rfc_1123(time)
|
||||
.or_else(|| try_parse_rfc_850(time))
|
||||
.or_else(|| try_parse_asctime(time))
|
||||
}
|
||||
|
||||
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
|
||||
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
|
||||
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
|
||||
}
|
||||
|
||||
/// Attempt to parse a `time` string as a RFC 850 formatted date time string.
|
||||
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
|
||||
match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") {
|
||||
Ok(dt) => {
|
||||
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
|
||||
// we consider the year as part of this century if it's within the next 50 years,
|
||||
// otherwise we consider as part of the previous century.
|
||||
let now = PrimitiveDateTime::now();
|
||||
let century_start_year = (now.year() / 100) * 100;
|
||||
let mut expanded_year = century_start_year + dt.year();
|
||||
|
||||
if expanded_year > now.year() + 50 {
|
||||
expanded_year -= 100;
|
||||
}
|
||||
|
||||
match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) {
|
||||
Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())),
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to parse a `time` string using ANSI C's `asctime` format.
|
||||
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
|
||||
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
# Changes
|
||||
|
||||
## [Unreleased] - 2020-xx-xx
|
||||
|
||||
* Update the `time` dependency to 0.2.5
|
||||
|
||||
## [0.2.1] - 2020-01-10
|
||||
|
||||
* Fix panic with already borrowed: BorrowMutError #1263
|
||||
|
|
|
@ -21,7 +21,7 @@ actix-service = "1.0.2"
|
|||
futures = "0.3.1"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1.42"
|
||||
time = { version = "0.2.5", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
|
|
|
@ -428,14 +428,14 @@ impl CookieIdentityInner {
|
|||
let now = SystemTime::now();
|
||||
if let Some(visit_deadline) = self.visit_deadline {
|
||||
if now.duration_since(value.visit_timestamp?).ok()?
|
||||
> visit_deadline.to_std().ok()?
|
||||
> visit_deadline
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
if let Some(login_deadline) = self.login_deadline {
|
||||
if now.duration_since(value.login_timestamp?).ok()?
|
||||
> login_deadline.to_std().ok()?
|
||||
> login_deadline
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -855,7 +855,7 @@ mod tests {
|
|||
let cv: CookieValue = serde_json::from_str(cookie.value()).unwrap();
|
||||
assert_eq!(cv.identity, identity);
|
||||
let now = SystemTime::now();
|
||||
let t30sec_ago = now - Duration::seconds(30).to_std().unwrap();
|
||||
let t30sec_ago = now - Duration::seconds(30);
|
||||
match login_timestamp {
|
||||
LoginTimestampCheck::NoTimestamp => assert_eq!(cv.login_timestamp, None),
|
||||
LoginTimestampCheck::NewTimestamp => assert!(
|
||||
|
@ -997,7 +997,7 @@ mod tests {
|
|||
create_identity_server(|c| c.login_deadline(Duration::days(90))).await;
|
||||
let cookie = login_cookie(
|
||||
COOKIE_LOGIN,
|
||||
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
|
||||
Some(SystemTime::now() - Duration::days(180)),
|
||||
None,
|
||||
);
|
||||
let mut resp = test::call_service(
|
||||
|
@ -1023,7 +1023,7 @@ mod tests {
|
|||
let cookie = login_cookie(
|
||||
COOKIE_LOGIN,
|
||||
None,
|
||||
Some(SystemTime::now() - Duration::days(180).to_std().unwrap()),
|
||||
Some(SystemTime::now() - Duration::days(180)),
|
||||
);
|
||||
let mut resp = test::call_service(
|
||||
&mut srv,
|
||||
|
@ -1065,7 +1065,7 @@ mod tests {
|
|||
.login_deadline(Duration::days(90))
|
||||
})
|
||||
.await;
|
||||
let timestamp = SystemTime::now() - Duration::days(1).to_std().unwrap();
|
||||
let timestamp = SystemTime::now() - Duration::days(1);
|
||||
let cookie = login_cookie(COOKIE_LOGIN, Some(timestamp), Some(timestamp));
|
||||
let mut resp = test::call_service(
|
||||
&mut srv,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Changes
|
||||
|
||||
## [0.2.1] - 2020-01-xx
|
||||
|
||||
* Remove the unused `time` dependency
|
||||
|
||||
## [0.2.0] - 2019-12-20
|
||||
|
||||
* Release
|
||||
|
|
|
@ -25,7 +25,6 @@ httparse = "1.3"
|
|||
futures = "0.3.1"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
time = "0.1"
|
||||
twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Changes
|
||||
|
||||
## [Unreleased] - 2020-01-xx
|
||||
|
||||
* Update the `time` dependency to 0.2.5
|
||||
* [#1292](https://github.com/actix/actix-web/pull/1292) Long lasting auto-prolonged session
|
||||
|
||||
## [0.3.0] - 2019-12-20
|
||||
|
||||
* Release
|
||||
|
|
|
@ -29,7 +29,7 @@ derive_more = "0.99.2"
|
|||
futures = "0.3.1"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1.42"
|
||||
time = { version = "0.2.5", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "1.0.0"
|
||||
|
|
|
@ -27,6 +27,7 @@ use actix_web::{Error, HttpMessage, ResponseError};
|
|||
use derive_more::{Display, From};
|
||||
use futures::future::{ok, FutureExt, LocalBoxFuture, Ready};
|
||||
use serde_json::error::Error as JsonError;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use crate::{Session, SessionStatus};
|
||||
|
||||
|
@ -56,7 +57,8 @@ struct CookieSessionInner {
|
|||
domain: Option<String>,
|
||||
secure: bool,
|
||||
http_only: bool,
|
||||
max_age: Option<time::Duration>,
|
||||
max_age: Option<Duration>,
|
||||
expires_in: Option<Duration>,
|
||||
same_site: Option<SameSite>,
|
||||
}
|
||||
|
||||
|
@ -71,6 +73,7 @@ impl CookieSessionInner {
|
|||
secure: true,
|
||||
http_only: true,
|
||||
max_age: None,
|
||||
expires_in: None,
|
||||
same_site: None,
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +99,10 @@ impl CookieSessionInner {
|
|||
cookie.set_domain(domain.clone());
|
||||
}
|
||||
|
||||
if let Some(expires_in) = self.expires_in {
|
||||
cookie.set_expires(OffsetDateTime::now() + expires_in);
|
||||
}
|
||||
|
||||
if let Some(max_age) = self.max_age {
|
||||
cookie.set_max_age(max_age);
|
||||
}
|
||||
|
@ -123,8 +130,8 @@ impl CookieSessionInner {
|
|||
fn remove_cookie<B>(&self, res: &mut ServiceResponse<B>) -> Result<(), Error> {
|
||||
let mut cookie = Cookie::named(self.name.clone());
|
||||
cookie.set_value("");
|
||||
cookie.set_max_age(time::Duration::seconds(0));
|
||||
cookie.set_expires(time::now() - time::Duration::days(365));
|
||||
cookie.set_max_age(Duration::zero());
|
||||
cookie.set_expires(OffsetDateTime::now() - Duration::days(365));
|
||||
|
||||
let val = HeaderValue::from_str(&cookie.to_string())?;
|
||||
res.headers_mut().append(SET_COOKIE, val);
|
||||
|
@ -263,7 +270,7 @@ impl CookieSession {
|
|||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
pub fn max_age(self, seconds: i64) -> CookieSession {
|
||||
self.max_age_time(time::Duration::seconds(seconds))
|
||||
self.max_age_time(Duration::seconds(seconds))
|
||||
}
|
||||
|
||||
/// Sets the `max-age` field in the session cookie being built.
|
||||
|
@ -271,6 +278,17 @@ impl CookieSession {
|
|||
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `expires` field in the session cookie being built.
|
||||
pub fn expires_in(self, seconds: i64) -> CookieSession {
|
||||
self.expires_in_time(Duration::seconds(seconds))
|
||||
}
|
||||
|
||||
/// Sets the `expires` field in the session cookie being built.
|
||||
pub fn expires_in_time(mut self, value: Duration) -> CookieSession {
|
||||
Rc::get_mut(&mut self.0).unwrap().expires_in = Some(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B: 'static> Transform<S> for CookieSession
|
||||
|
@ -323,6 +341,7 @@ where
|
|||
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
|
||||
let inner = self.inner.clone();
|
||||
let (is_new, state) = self.inner.load(&req);
|
||||
let prolong_expiration = self.inner.expires_in.is_some();
|
||||
Session::set_session(state.into_iter(), &mut req);
|
||||
|
||||
let fut = self.service.call(req);
|
||||
|
@ -334,6 +353,9 @@ where
|
|||
| (SessionStatus::Renewed, Some(state)) => {
|
||||
res.checked_expr(|res| inner.set_cookie(res, state))
|
||||
}
|
||||
(SessionStatus::Unchanged, Some(state)) if prolong_expiration => {
|
||||
res.checked_expr(|res| inner.set_cookie(res, state))
|
||||
}
|
||||
(SessionStatus::Unchanged, _) =>
|
||||
// set a new session cookie upon first request (new client)
|
||||
{
|
||||
|
@ -477,4 +499,47 @@ mod tests {
|
|||
let body = test::read_response(&mut app, request).await;
|
||||
assert_eq!(body, Bytes::from_static(b"counter: 100"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn prolong_expiration() {
|
||||
let mut app = test::init_service(
|
||||
App::new()
|
||||
.wrap(CookieSession::signed(&[0; 32]).secure(false).expires_in(60))
|
||||
.service(web::resource("/").to(|ses: Session| {
|
||||
async move {
|
||||
let _ = ses.set("counter", 100);
|
||||
"test"
|
||||
}
|
||||
}))
|
||||
.service(
|
||||
web::resource("/test/")
|
||||
.to(|| async move { "no-changes-in-session" }),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
let request = test::TestRequest::get().to_request();
|
||||
let response = app.call(request).await.unwrap();
|
||||
let expires_1 = response
|
||||
.response()
|
||||
.cookies()
|
||||
.find(|c| c.name() == "actix-session")
|
||||
.expect("Cookie is set")
|
||||
.expires()
|
||||
.expect("Expiration is set");
|
||||
|
||||
actix_rt::time::delay_for(std::time::Duration::from_secs(1)).await;
|
||||
|
||||
let request = test::TestRequest::with_uri("/test/").to_request();
|
||||
let response = app.call(request).await.unwrap();
|
||||
let expires_2 = response
|
||||
.response()
|
||||
.cookies()
|
||||
.find(|c| c.name() == "actix-session")
|
||||
.expect("Cookie is set")
|
||||
.expires()
|
||||
.expect("Expiration is set");
|
||||
|
||||
assert!(expires_2 - expires_1 >= Duration::seconds(1));
|
||||
}
|
||||
}
|
||||
|
|
108
src/extract.rs
108
src/extract.rs
|
@ -193,57 +193,83 @@ impl FromRequest for () {
|
|||
|
||||
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
|
||||
/// FromRequest implementation for tuple
|
||||
#[doc(hidden)]
|
||||
#[allow(unused_parens)]
|
||||
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = $fut_type<$($T),+>;
|
||||
type Config = ($($T::Config),+);
|
||||
// This module is a trick to get around the inability of
|
||||
// `macro_rules!` macros to make new idents. We want to make
|
||||
// a new `FutWrapper` struct for each distinct invocation of
|
||||
// this macro. Ideally, we would name it something like
|
||||
// `FutWrapper_$fut_type`, but this can't be done in a macro_rules
|
||||
// macro.
|
||||
//
|
||||
// Instead, we put everything in a module named `$fut_type`, thus allowing
|
||||
// us to use the name `FutWrapper` without worrying about conflicts.
|
||||
// This macro only exists to generate trait impls for tuples - these
|
||||
// are inherently global, so users don't have to care about this
|
||||
// weird trick.
|
||||
#[allow(non_snake_case)]
|
||||
mod $fut_type {
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$fut_type {
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: ($($T::from_request(req, payload),)+),
|
||||
// Bring everything into scope, so we don't need
|
||||
// redundant imports
|
||||
use super::*;
|
||||
|
||||
/// A helper struct to allow us to pin-project through
|
||||
/// to individual fields
|
||||
#[pin_project::pin_project]
|
||||
struct FutWrapper<$($T: FromRequest),+>($(#[pin] $T::Future),+);
|
||||
|
||||
/// FromRequest implementation for tuple
|
||||
#[doc(hidden)]
|
||||
#[allow(unused_parens)]
|
||||
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+)
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = $fut_type<$($T),+>;
|
||||
type Config = ($($T::Config),+);
|
||||
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
|
||||
$fut_type {
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: FutWrapper($($T::from_request(req, payload),)+),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[pin_project::pin_project]
|
||||
pub struct $fut_type<$($T: FromRequest),+> {
|
||||
items: ($(Option<$T>,)+),
|
||||
futs: ($($T::Future,)+),
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[pin_project::pin_project]
|
||||
pub struct $fut_type<$($T: FromRequest),+> {
|
||||
items: ($(Option<$T>,)+),
|
||||
#[pin]
|
||||
futs: FutWrapper<$($T,)+>,
|
||||
}
|
||||
|
||||
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
|
||||
{
|
||||
type Output = Result<($($T,)+), Error>;
|
||||
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+>
|
||||
{
|
||||
type Output = Result<($($T,)+), Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
||||
let mut ready = true;
|
||||
$(
|
||||
if this.items.$n.is_none() {
|
||||
match unsafe { Pin::new_unchecked(&mut this.futs.$n) }.poll(cx) {
|
||||
Poll::Ready(Ok(item)) => {
|
||||
this.items.$n = Some(item);
|
||||
let mut ready = true;
|
||||
$(
|
||||
if this.items.$n.is_none() {
|
||||
match this.futs.as_mut().project().$n.poll(cx) {
|
||||
Poll::Ready(Ok(item)) => {
|
||||
this.items.$n = Some(item);
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
|
||||
}
|
||||
Poll::Pending => ready = false,
|
||||
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
|
||||
}
|
||||
}
|
||||
)+
|
||||
)+
|
||||
|
||||
if ready {
|
||||
Poll::Ready(Ok(
|
||||
($(this.items.$n.take().unwrap(),)+)
|
||||
))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
if ready {
|
||||
Poll::Ready(Ok(
|
||||
($(this.items.$n.take().unwrap(),)+)
|
||||
))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ use bytes::Bytes;
|
|||
use futures::future::{ok, Ready};
|
||||
use log::debug;
|
||||
use regex::Regex;
|
||||
use time;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
||||
use crate::error::{Error, Result};
|
||||
|
@ -163,11 +163,11 @@ where
|
|||
LoggerResponse {
|
||||
fut: self.service.call(req),
|
||||
format: None,
|
||||
time: time::now(),
|
||||
time: OffsetDateTime::now(),
|
||||
_t: PhantomData,
|
||||
}
|
||||
} else {
|
||||
let now = time::now();
|
||||
let now = OffsetDateTime::now();
|
||||
let mut format = self.inner.format.clone();
|
||||
|
||||
for unit in &mut format.0 {
|
||||
|
@ -192,7 +192,7 @@ where
|
|||
{
|
||||
#[pin]
|
||||
fut: S::Future,
|
||||
time: time::Tm,
|
||||
time: OffsetDateTime,
|
||||
format: Option<Format>,
|
||||
_t: PhantomData<(B,)>,
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ pub struct StreamLog<B> {
|
|||
body: ResponseBody<B>,
|
||||
format: Option<Format>,
|
||||
size: usize,
|
||||
time: time::Tm,
|
||||
time: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl<B> Drop for StreamLog<B> {
|
||||
|
@ -366,20 +366,20 @@ impl FormatText {
|
|||
&self,
|
||||
fmt: &mut Formatter<'_>,
|
||||
size: usize,
|
||||
entry_time: time::Tm,
|
||||
entry_time: OffsetDateTime,
|
||||
) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
FormatText::Str(ref string) => fmt.write_str(string),
|
||||
FormatText::Percent => "%".fmt(fmt),
|
||||
FormatText::ResponseSize => size.fmt(fmt),
|
||||
FormatText::Time => {
|
||||
let rt = time::now() - entry_time;
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000_000.0;
|
||||
let rt = OffsetDateTime::now() - entry_time;
|
||||
let rt = rt.as_seconds_f64();
|
||||
fmt.write_fmt(format_args!("{:.6}", rt))
|
||||
}
|
||||
FormatText::TimeMillis => {
|
||||
let rt = time::now() - entry_time;
|
||||
let rt = (rt.num_nanoseconds().unwrap_or(0) as f64) / 1_000_000.0;
|
||||
let rt = OffsetDateTime::now() - entry_time;
|
||||
let rt = (rt.whole_nanoseconds() as f64) / 1_000_000.0;
|
||||
fmt.write_fmt(format_args!("{:.6}", rt))
|
||||
}
|
||||
FormatText::EnvironHeader(ref name) => {
|
||||
|
@ -414,7 +414,7 @@ impl FormatText {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_request(&mut self, now: time::Tm, req: &ServiceRequest) {
|
||||
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
|
||||
match *self {
|
||||
FormatText::RequestLine => {
|
||||
*self = if req.query_string().is_empty() {
|
||||
|
@ -436,7 +436,7 @@ impl FormatText {
|
|||
}
|
||||
FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()),
|
||||
FormatText::RequestTime => {
|
||||
*self = FormatText::Str(now.rfc3339().to_string())
|
||||
*self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S"))
|
||||
}
|
||||
FormatText::RequestHeader(ref name) => {
|
||||
let s = if let Some(val) = req.headers().get(name) {
|
||||
|
@ -513,7 +513,7 @@ mod tests {
|
|||
.uri("/test/route/yeah")
|
||||
.to_srv_request();
|
||||
|
||||
let now = time::now();
|
||||
let now = OffsetDateTime::now();
|
||||
for unit in &mut format.0 {
|
||||
unit.render_request(now, &req);
|
||||
}
|
||||
|
@ -544,7 +544,7 @@ mod tests {
|
|||
)
|
||||
.to_srv_request();
|
||||
|
||||
let now = time::now();
|
||||
let now = OffsetDateTime::now();
|
||||
for unit in &mut format.0 {
|
||||
unit.render_request(now, &req);
|
||||
}
|
||||
|
@ -554,7 +554,7 @@ mod tests {
|
|||
unit.render_response(&resp);
|
||||
}
|
||||
|
||||
let entry_time = time::now();
|
||||
let entry_time = OffsetDateTime::now();
|
||||
let render = |fmt: &mut Formatter<'_>| {
|
||||
for unit in &format.0 {
|
||||
unit.render(fmt, 1024, entry_time)?;
|
||||
|
@ -572,7 +572,7 @@ mod tests {
|
|||
let mut format = Format::new("%t");
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
|
||||
let now = time::now();
|
||||
let now = OffsetDateTime::now();
|
||||
for unit in &mut format.0 {
|
||||
unit.render_request(now, &req);
|
||||
}
|
||||
|
@ -589,6 +589,6 @@ mod tests {
|
|||
Ok(())
|
||||
};
|
||||
let s = format!("{}", FormatDisplay(&render));
|
||||
assert!(s.contains(&format!("{}", now.rfc3339())));
|
||||
assert!(s.contains(&format!("{}", now.format("%Y-%m-%dT%H:%M:%S"))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Changes
|
||||
|
||||
## [Unreleased] - 2020-xx-xx
|
||||
|
||||
* Update the `time` dependency to 0.2.5
|
||||
|
||||
|
||||
## [1.0.0] - 2019-12-13
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -51,7 +51,7 @@ serde_json = "1.0"
|
|||
sha1 = "0.6"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.6.1"
|
||||
time = "0.1"
|
||||
time = { version = "0.2.5", default-features = false, features = ["std"] }
|
||||
open-ssl = { version="0.10", package="openssl", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
Loading…
Reference in New Issue