Merge branch 'master' into fix/pin-stream

This commit is contained in:
Yuki Okushi 2020-01-31 01:59:42 +09:00 committed by GitHub
commit 8f9f9348e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 36 deletions

View File

@ -27,26 +27,6 @@ jobs:
profile: minimal profile: minimal
override: true 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 - name: Install OpenSSL
run: | run: |
vcpkg integrate install vcpkg integrate install
@ -74,8 +54,5 @@ jobs:
--skip=test_expect_continue --skip=test_expect_continue
--skip=test_http10_keepalive --skip=test_http10_keepalive
--skip=test_slow_request --skip=test_slow_request
--skip=test_connection_force_close
- name: Clear the cargo caches --skip=test_connection_server_close
run: |
cargo install cargo-cache --no-default-features --features ci-autoclean
cargo-cache

View File

@ -1,4 +1,4 @@
use std::cell::UnsafeCell; use std::cell::Cell;
use std::fmt::Write; use std::fmt::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
@ -228,24 +228,24 @@ impl fmt::Write for Date {
struct DateService(Rc<DateServiceInner>); struct DateService(Rc<DateServiceInner>);
struct DateServiceInner { struct DateServiceInner {
current: UnsafeCell<Option<(Date, Instant)>>, current: Cell<Option<(Date, Instant)>>,
} }
impl DateServiceInner { impl DateServiceInner {
fn new() -> Self { fn new() -> Self {
DateServiceInner { DateServiceInner {
current: UnsafeCell::new(None), current: Cell::new(None),
} }
} }
fn reset(&self) { fn reset(&self) {
unsafe { (&mut *self.current.get()).take() }; self.current.take();
} }
fn update(&self) { fn update(&self) {
let now = Instant::now(); let now = Instant::now();
let date = Date::new(); 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) { fn check_date(&self) {
if unsafe { (&*self.0.current.get()).is_none() } { if self.0.current.get().is_none() {
self.0.update(); self.0.update();
// periodic date update // periodic date update
@ -269,12 +269,12 @@ impl DateService {
fn now(&self) -> Instant { fn now(&self) -> Instant {
self.check_date(); 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) { fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
self.check_date(); 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 { mod tests {
use super::*; 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] #[test]
fn test_date_len() { fn test_date_len() {
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len()); assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());

View File

@ -60,6 +60,12 @@ impl Error {
} }
} }
/// A struct with a private constructor, for use with
/// `__private_get_type_id__`. Its single field is private,
/// ensuring that it can only be constructed from this module
#[doc(hidden)]
pub struct PrivateHelper(());
/// Error that can be converted to `Response` /// Error that can be converted to `Response`
pub trait ResponseError: fmt::Debug + fmt::Display { pub trait ResponseError: fmt::Debug + fmt::Display {
/// Response's status code /// Response's status code
@ -83,19 +89,37 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
resp.set_body(Body::from(buf)) resp.set_body(Body::from(buf))
} }
/// A helper method to get the type ID of the type
/// this trait is implemented on.
/// This method is unsafe to *implement*, since `downcast_ref` relies
/// on the returned `TypeId` to perform a cast.
///
/// Unfortunately, Rust has no notion of a trait method that is
/// unsafe to implement (marking it as `unsafe` makes it unsafe
/// to *call*). As a workaround, we require this method
/// to return a private type along with the `TypeId`. This
/// private type (`PrivateHelper`) has a private constructor,
/// making it impossible for safe code to construct outside of
/// this module. This ensures that safe code cannot violate
/// type-safety by implementing this method.
#[doc(hidden)] #[doc(hidden)]
fn __private_get_type_id__(&self) -> TypeId fn __private_get_type_id__(&self) -> (TypeId, PrivateHelper)
where where
Self: 'static, Self: 'static,
{ {
TypeId::of::<Self>() (TypeId::of::<Self>(), PrivateHelper(()))
} }
} }
impl dyn ResponseError + 'static { impl dyn ResponseError + 'static {
/// Downcasts a response error to a specific type. /// Downcasts a response error to a specific type.
pub fn downcast_ref<T: ResponseError + 'static>(&self) -> Option<&T> { pub fn downcast_ref<T: ResponseError + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__() == TypeId::of::<T>() { if self.__private_get_type_id__().0 == TypeId::of::<T>() {
// Safety: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
// it requires returning a private type. We can therefore
// rely on the returned `TypeId`, which ensures that this
// case is correct.
unsafe { Some(&*(self as *const dyn ResponseError as *const T)) } unsafe { Some(&*(self as *const dyn ResponseError as *const T)) }
} else { } else {
None None

View File

@ -3,6 +3,7 @@
## [Unreleased] - 2020-01-xx ## [Unreleased] - 2020-01-xx
* Update the `time` dependency to 0.2.5 * 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 ## [0.3.0] - 2019-12-20

View File

@ -58,6 +58,7 @@ struct CookieSessionInner {
secure: bool, secure: bool,
http_only: bool, http_only: bool,
max_age: Option<Duration>, max_age: Option<Duration>,
expires_in: Option<Duration>,
same_site: Option<SameSite>, same_site: Option<SameSite>,
} }
@ -72,6 +73,7 @@ impl CookieSessionInner {
secure: true, secure: true,
http_only: true, http_only: true,
max_age: None, max_age: None,
expires_in: None,
same_site: None, same_site: None,
} }
} }
@ -97,6 +99,10 @@ impl CookieSessionInner {
cookie.set_domain(domain.clone()); 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 { if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age); cookie.set_max_age(max_age);
} }
@ -272,6 +278,17 @@ impl CookieSession {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value); Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self 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 impl<S, B: 'static> Transform<S> for CookieSession
@ -324,6 +341,7 @@ where
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
let (is_new, state) = self.inner.load(&req); 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); Session::set_session(state.into_iter(), &mut req);
let fut = self.service.call(req); let fut = self.service.call(req);
@ -335,6 +353,9 @@ where
| (SessionStatus::Renewed, Some(state)) => { | (SessionStatus::Renewed, Some(state)) => {
res.checked_expr(|res| inner.set_cookie(res, 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, _) => (SessionStatus::Unchanged, _) =>
// set a new session cookie upon first request (new client) // set a new session cookie upon first request (new client)
{ {
@ -478,4 +499,47 @@ mod tests {
let body = test::read_response(&mut app, request).await; let body = test::read_response(&mut app, request).await;
assert_eq!(body, Bytes::from_static(b"counter: 100")); 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));
}
} }