diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fed4ce031..36b224ba6 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 diff --git a/CHANGES.md b/CHANGES.md index 29f78e0b1..b42635b86 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 9f0748e0c..a6783a6db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ee3dae5d5..511ef4f1c 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -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. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 93aaa756e..cd813e49f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -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 } diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 850f97ee4..881764439 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -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>> { - 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>> { - 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), + )); + 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")), + ); + } + } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index acf76559a..8c94423ac 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -488,10 +488,12 @@ where } } +#[pin_project::pin_project(PinnedDrop)] struct OpenWaitingConnection where Io: AsyncRead + AsyncWrite + Unpin + 'static, { + #[pin] fut: F, key: Key, h2: Option< @@ -525,12 +527,13 @@ where } } -impl Drop for OpenWaitingConnection +#[pin_project::pinned_drop] +impl PinnedDrop for OpenWaitingConnection 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 { - let this = unsafe { self.get_unchecked_mut() }; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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, diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index be949aaef..a38a80e76 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -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); struct DateServiceInner { - current: UnsafeCell>, + current: Cell>, } 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(&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()); diff --git a/actix-http/src/cookie/builder.rs b/actix-http/src/cookie/builder.rs index f99d02b02..c3820abf0 100644 --- a/actix-http/src/cookie/builder.rs +++ b/actix-http/src/cookie/builder.rs @@ -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() diff --git a/actix-http/src/cookie/jar.rs b/actix-http/src/cookie/jar.rs index dc2de4dfe..64922897b 100644 --- a/actix-http/src/cookie/jar.rs +++ b/actix-http/src/cookie/jar.rs @@ -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] diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index d9db600ea..09120e19f 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -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, + expires: Option, /// The cookie's maximum age, if any. max_age: Option, /// 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 { @@ -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 { + pub fn expires(&self) -> Option { 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> 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(), diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index 20aee9507..28eb4f8b6 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -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, 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, 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); } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index a4ec15fab..8b17e9479 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -158,14 +158,16 @@ where #[pin_project::pin_project] struct ServiceResponse { + #[pin] state: ServiceResponseState, config: ServiceConfig, buffer: Option, _t: PhantomData<(I, E)>, } +#[pin_project::pin_project] enum ServiceResponseState { - ServiceCall(F, Option>), + ServiceCall(#[pin] F, Option>), SendPayload(SendStream, ResponseBody), } @@ -247,12 +249,14 @@ where { type Output = (); + #[pin_project::project] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 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) } } diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 28d6a25ec..1b52f0de4 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -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 { - 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 for HttpDate { - fn from(tm: time::Tm) -> HttpDate { - HttpDate(tm) +impl From for HttpDate { + fn from(dt: OffsetDateTime) -> HttpDate { + HttpDate(dt) } } impl From 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 { 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 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::().unwrap(), - NOV_07 + nov_07 ); assert_eq!( "Sunday, 07-Nov-94 08:48:37 GMT" .parse::() .unwrap(), - NOV_07 + nov_07 ); assert_eq!( "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 + nov_07 ); assert!("this-is-no-date".parse::().is_err()); } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 7a47012f8..a5ae4b447 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -27,6 +27,7 @@ mod payload; mod request; mod response; mod service; +mod time_parser; pub mod cookie; pub mod error; diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs new file mode 100644 index 000000000..f6623d24e --- /dev/null +++ b/actix-http/src/time_parser.rs @@ -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 { + 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 { + 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 { + 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 { + time::parse(time, "%a %b %_d %H:%M:%S %Y").ok() +} diff --git a/actix-identity/CHANGES.md b/actix-identity/CHANGES.md index 594c21388..0c9809ea1 100644 --- a/actix-identity/CHANGES.md +++ b/actix-identity/CHANGES.md @@ -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 diff --git a/actix-identity/Cargo.toml b/actix-identity/Cargo.toml index 8cd6b1271..efeb24bda 100644 --- a/actix-identity/Cargo.toml +++ b/actix-identity/Cargo.toml @@ -21,9 +21,9 @@ 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" actix-http = "1.0.1" -bytes = "0.5.3" \ No newline at end of file +bytes = "0.5.3" diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 3b9626991..b584b1af7 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -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, diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 31f326d05..d73a69393 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## [0.2.1] - 2020-01-xx + +* Remove the unused `time` dependency + ## [0.2.0] - 2019-12-20 * Release @@ -44,4 +48,4 @@ * Split multipart support to separate crate -* Optimize multipart handling #634, #769 \ No newline at end of file +* Optimize multipart handling #634, #769 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6c683cb1a..f9cd7cfd2 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -25,9 +25,8 @@ httparse = "1.3" futures = "0.3.1" log = "0.4" mime = "0.3" -time = "0.1" twoway = "0.2" [dev-dependencies] actix-rt = "1.0.0" -actix-http = "1.0.0" \ No newline at end of file +actix-http = "1.0.0" diff --git a/actix-session/CHANGES.md b/actix-session/CHANGES.md index e4306fa9d..f6753ae58 100644 --- a/actix-session/CHANGES.md +++ b/actix-session/CHANGES.md @@ -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 diff --git a/actix-session/Cargo.toml b/actix-session/Cargo.toml index 5989cc0d6..b279c9d89 100644 --- a/actix-session/Cargo.toml +++ b/actix-session/Cargo.toml @@ -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" diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 75eef0c01..b5297f561 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -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, secure: bool, http_only: bool, - max_age: Option, + max_age: Option, + expires_in: Option, same_site: Option, } @@ -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(&self, res: &mut ServiceResponse) -> 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 Transform 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)); + } } diff --git a/src/extract.rs b/src/extract.rs index c189bbf97..5289bd7db 100644 --- a/src/extract.rs +++ b/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 { - let this = self.project(); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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 + } + } } } }); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 97fa7463f..d692132ce 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -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, _t: PhantomData<(B,)>, } @@ -242,7 +242,7 @@ pub struct StreamLog { body: ResponseBody, format: Option, size: usize, - time: time::Tm, + time: OffsetDateTime, } impl Drop for StreamLog { @@ -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")))); } } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 5690afc64..617b8092f 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,10 @@ # Changes +## [Unreleased] - 2020-xx-xx + +* Update the `time` dependency to 0.2.5 + + ## [1.0.0] - 2019-12-13 ### Changed diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 52a2da8da..b22414e29 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -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]