diff --git a/Cargo.toml b/Cargo.toml index 60525f3ac..73a52182c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,7 @@ serde_json = "1.0" serde_urlencoded = "0.7" smallvec = "1.6.1" socket2 = "0.4.0" -time = { version = "0.2.23", default-features = false, features = ["std"] } +time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e7fe7adc0..ee4971a1e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -47,7 +47,6 @@ serde = "1.0" serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 0f126c99a..ec7b46ffb 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -7,8 +7,7 @@ #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; -use std::sync::mpsc; -use std::{net, thread, time}; +use std::{net, sync::mpsc, thread, time::Duration}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::{net::TcpStream, System}; @@ -95,15 +94,15 @@ pub async fn test_server_with_addr>( .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) .ssl(builder.build()) } #[cfg(not(feature = "openssl"))] { Connector::new() - .conn_lifetime(time::Duration::from_secs(0)) - .timeout(time::Duration::from_millis(30000)) + .conn_lifetime(Duration::from_secs(0)) + .timeout(Duration::from_millis(30000)) } }; diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0e0da8f43..889c91331 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -60,6 +60,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["alloc h2 = "0.3.1" http = "0.2.2" httparse = "1.5.1" +httpdate = "1.0.1" itoa = "0.4" language-tags = "0.3" local-channel = "0.1" @@ -70,11 +71,8 @@ percent-encoding = "2.1" pin-project = "1.0.0" pin-project-lite = "0.2" rand = "0.8" -regex = "1.3" -serde = "1.0" sha-1 = "0.9" smallvec = "1.6.1" -time = { version = "0.2.23", default-features = false, features = ["std"] } tokio = { version = "1.2", features = ["sync"] } # compression @@ -92,11 +90,12 @@ async-stream = "0.3" criterion = { version = "0.3", features = ["html_reports"] } env_logger = "0.8" rcgen = "0.8" +regex = "1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } -webpki = { version = "0.21.0" } +webpki = { version = "0.21" } [[example]] name = "ws" diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 97750ff76..069099b8c 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,18 +1,19 @@ -use std::cell::Cell; -use std::fmt::Write; -use std::rc::Rc; -use std::time::Duration; -use std::{fmt, net}; +use std::{ + cell::Cell, + fmt::{self, Write}, + net, + rc::Rc, + time::{Duration, SystemTime}, +}; use actix_rt::{ task::JoinHandle, time::{interval, sleep_until, Instant, Sleep}, }; use bytes::BytesMut; -use time::OffsetDateTime; /// "Sun, 06 Nov 1994 08:49:37 GMT".len() -const DATE_VALUE_LENGTH: usize = 29; +pub(crate) const DATE_VALUE_LENGTH: usize = 29; #[derive(Debug, PartialEq, Clone, Copy)] /// Server keep-alive setting @@ -206,12 +207,7 @@ impl Date { fn update(&mut self) { self.pos = 0; - write!( - self, - "{}", - OffsetDateTime::now_utc().format("%a, %d %b %Y %H:%M:%S GMT") - ) - .unwrap(); + write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); } } @@ -269,11 +265,11 @@ impl DateService { } // TODO: move to a util module for testing all spawn handle drop style tasks. -#[cfg(test)] /// Test Module for checking the drop state of certain async tasks that are spawned /// with `actix_rt::spawn` /// /// The target task must explicitly generate `NotifyOnDrop` when spawn the task +#[cfg(test)] mod notify_on_drop { use std::cell::RefCell; @@ -283,9 +279,8 @@ mod notify_on_drop { /// Check if the spawned task is dropped. /// - /// # Panic: - /// - /// When there was no `NotifyOnDrop` instance on current thread + /// # Panics + /// Panics when there was no `NotifyOnDrop` instance on current thread. pub(crate) fn is_dropped() -> bool { NOTIFY_DROPPED.with(|bool| { bool.borrow() diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs new file mode 100644 index 000000000..3441f90af --- /dev/null +++ b/actix-http/src/header/shared/http_date.rs @@ -0,0 +1,82 @@ +use std::{fmt, io::Write, str::FromStr, time::SystemTime}; + +use bytes::BytesMut; +use http::header::{HeaderValue, InvalidHeaderValue}; + +use crate::{ + config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, + helpers::MutWriter, +}; + +/// A timestamp with HTTP formatting and parsing. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct HttpDate(SystemTime); + +impl FromStr for HttpDate { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + match httpdate::parse_http_date(s) { + Ok(sys_time) => Ok(HttpDate(sys_time)), + Err(_) => Err(ParseError::Header), + } + } +} + +impl fmt::Display for HttpDate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let date_str = httpdate::fmt_http_date(self.0); + f.write_str(&date_str) + } +} + +impl IntoHeaderValue for HttpDate { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH); + let mut wrt = MutWriter(&mut buf); + + // unwrap: date output is known to be well formed and of known length + write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap(); + + HeaderValue::from_maybe_shared(buf.split().freeze()) + } +} + +impl From for HttpDate { + fn from(sys_time: SystemTime) -> HttpDate { + HttpDate(sys_time) + } +} + +impl From for SystemTime { + fn from(HttpDate(sys_time): HttpDate) -> SystemTime { + sys_time + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use super::*; + + #[test] + fn date_header() { + macro_rules! assert_parsed_date { + ($case:expr, $exp:expr) => { + assert_eq!($case.parse::().unwrap(), $exp); + }; + } + + // 784198117 = SystemTime::from(datetime!(1994-11-07 08:48:37).assume_utc()).duration_since(SystemTime::UNIX_EPOCH)); + let nov_07 = HttpDate(SystemTime::UNIX_EPOCH + Duration::from_secs(784198117)); + + assert_parsed_date!("Mon, 07 Nov 1994 08:48:37 GMT", nov_07); + assert_parsed_date!("Monday, 07-Nov-94 08:48:37 GMT", nov_07); + assert_parsed_date!("Mon Nov 7 08:48:37 1994", nov_07); + + assert!("this-is-no-date".parse::().is_err()); + } +} diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs deleted file mode 100644 index 18278a6d8..000000000 --- a/actix-http/src/header/shared/httpdate.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::{ - fmt, - io::Write, - str::FromStr, - time::{SystemTime, UNIX_EPOCH}, -}; - -use bytes::buf::BufMut; -use bytes::BytesMut; -use http::header::{HeaderValue, InvalidHeaderValue}; -use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset}; - -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(OffsetDateTime); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time_parser::parse_http_date(s) { - Some(t) => Ok(HttpDate(t.assume_utc())), - None => Err(ParseError::Header), - } - } -} - -impl fmt::Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0.format("%a, %d %b %Y %H:%M:%S GMT"), f) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - HttpDate(PrimitiveDateTime::from(sys).assume_utc()) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValue; - - fn try_into_value(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!( - wrt, - "{}", - self.0 - .to_offset(UtcOffset::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 dt = date.0; - let epoch = OffsetDateTime::unix_epoch(); - - UNIX_EPOCH + (dt - epoch) - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::{date, time, PrimitiveDateTime}; - - #[test] - fn test_date() { - let nov_07 = HttpDate( - PrimitiveDateTime::new(date!(1994 - 11 - 07), time!(8:48:37)).assume_utc(), - ); - - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - nov_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - nov_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - nov_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index b8f9173f9..274e13146 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -3,12 +3,12 @@ mod charset; mod content_encoding; mod extended; -mod httpdate; +mod http_date; mod quality_item; pub use self::charset::Charset; pub use self::content_encoding::ContentEncoding; pub use self::extended::{parse_extended_value, ExtendedValue}; -pub use self::httpdate::HttpDate; +pub use self::http_date::HttpDate; pub use self::quality_item::{q, qitem, Quality, QualityItem}; pub use language_tags::LanguageTag; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 17ee3ff29..3ad8d095e 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -44,7 +44,6 @@ mod request; mod response; mod response_builder; mod service; -mod time_parser; pub mod error; pub mod h1; diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs deleted file mode 100644 index fd82fd42e..000000000 --- a/actix-http/src/time_parser.rs +++ /dev/null @@ -1,72 +0,0 @@ -use time::{Date, OffsetDateTime, PrimitiveDateTime}; - -/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. -pub(crate) 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. -/// -/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT` -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. -/// -/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC` -fn try_parse_rfc_850(time: &str) -> Option { - let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?; - - // 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 = OffsetDateTime::now_utc(); - 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; - } - - let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?; - Some(PrimitiveDateTime::new(date, dt.time())) -} - -/// Attempt to parse a `time` string using ANSI C's `asctime` format. -/// -/// Eg: `Wed Feb 13 15:46:11 2013` -fn try_parse_asctime(time: &str) -> Option { - time::parse(time, "%a %b %_d %H:%M:%S %Y").ok() -} - -#[cfg(test)] -mod tests { - use time::{date, time}; - - use super::*; - - #[test] - fn test_rfc_850_year_shift() { - let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap(); - assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap(); - assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41))); - - let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap(); - assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41))); - } -} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 1e6d0b637..c04aeae00 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -183,6 +183,7 @@ async fn test_chunked_payload() { Some(caps) => caps.get(1).unwrap().as_str().parse().unwrap(), None => panic!("Failed to find size in HTTP Response: {}", data), }; + size }; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 9574b02f7..961eca496 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -18,7 +18,7 @@ use bytes::Bytes; use futures_core::ready; use log::{debug, warn}; use regex::{Regex, RegexSet}; -use time::OffsetDateTime; +use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use crate::{ dev::{BodySize, MessageBody}, @@ -538,7 +538,7 @@ impl FormatText { }; } FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), - FormatText::RequestTime => *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")), + FormatText::RequestTime => *self = FormatText::Str(now.format(&Rfc3339).unwrap()), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -767,7 +767,7 @@ mod tests { Ok(()) }; let s = format!("{}", FormatDisplay(&render)); - assert!(s.contains(&now.format("%Y-%m-%dT%H:%M:%S"))); + assert!(s.contains(&now.format(&Rfc3339).unwrap())); } #[actix_rt::test]