mirror of
https://github.com/actix/actix-web.git
synced 2024-12-21 07:36:43 +00:00
add time parser year shift tests
This commit is contained in:
parent
31d9ed81c5
commit
81bef93e5e
5 changed files with 116 additions and 51 deletions
|
@ -116,6 +116,9 @@ brotli2 = "0.3.2"
|
||||||
flate2 = "1.0.13"
|
flate2 = "1.0.13"
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
debug = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
//! Stream decoders.
|
//! Stream decoders.
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
use std::{
|
||||||
use std::{future::Future, io, io::Write as _, pin::Pin};
|
future::Future,
|
||||||
|
io::{self, Write as _},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliDecoder;
|
use brotli2::write::BrotliDecoder;
|
||||||
|
@ -9,11 +13,13 @@ use bytes::Bytes;
|
||||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||||
use futures_core::{ready, Stream};
|
use futures_core::{ready, Stream};
|
||||||
|
|
||||||
use super::Writer;
|
use crate::{
|
||||||
use crate::error::{BlockingError, PayloadError};
|
encoding::Writer,
|
||||||
use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING};
|
error::{BlockingError, PayloadError},
|
||||||
|
http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING},
|
||||||
|
};
|
||||||
|
|
||||||
const INPLACE: usize = 2049;
|
const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049;
|
||||||
|
|
||||||
pub struct Decoder<S> {
|
pub struct Decoder<S> {
|
||||||
decoder: Option<ContentDecoder>,
|
decoder: Option<ContentDecoder>,
|
||||||
|
@ -41,6 +47,7 @@ where
|
||||||
))),
|
))),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Decoder {
|
Decoder {
|
||||||
decoder,
|
decoder,
|
||||||
stream,
|
stream,
|
||||||
|
@ -53,15 +60,11 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
|
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
|
||||||
// check content-encoding
|
// check content-encoding
|
||||||
let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) {
|
let encoding = headers
|
||||||
if let Ok(enc) = enc.to_str() {
|
.get(&CONTENT_ENCODING)
|
||||||
ContentEncoding::from(enc)
|
.and_then(|val| val.to_str().ok())
|
||||||
} else {
|
.map(ContentEncoding::from)
|
||||||
ContentEncoding::Identity
|
.unwrap_or(ContentEncoding::Identity);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ContentEncoding::Identity
|
|
||||||
};
|
|
||||||
|
|
||||||
Self::new(stream, encoding)
|
Self::new(stream, encoding)
|
||||||
}
|
}
|
||||||
|
@ -81,8 +84,10 @@ where
|
||||||
if let Some(ref mut fut) = self.fut {
|
if let Some(ref mut fut) = self.fut {
|
||||||
let (chunk, decoder) =
|
let (chunk, decoder) =
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
self.fut.take();
|
self.fut.take();
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -92,13 +97,15 @@ where
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
match Pin::new(&mut self.stream).poll_next(cx) {
|
match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
|
||||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
|
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
||||||
Poll::Ready(Some(Ok(chunk))) => {
|
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
if let Some(mut decoder) = self.decoder.take() {
|
if let Some(mut decoder) = self.decoder.take() {
|
||||||
if chunk.len() < INPLACE {
|
if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE {
|
||||||
let chunk = decoder.feed_data(chunk)?;
|
let chunk = decoder.feed_data(chunk)?;
|
||||||
self.decoder = Some(decoder);
|
self.decoder = Some(decoder);
|
||||||
|
|
||||||
if let Some(chunk) = chunk {
|
if let Some(chunk) = chunk {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -108,13 +115,16 @@ where
|
||||||
Ok((chunk, decoder))
|
Ok((chunk, decoder))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(None) => {
|
|
||||||
|
None => {
|
||||||
self.eof = true;
|
self.eof = true;
|
||||||
|
|
||||||
return if let Some(mut decoder) = self.decoder.take() {
|
return if let Some(mut decoder) = self.decoder.take() {
|
||||||
match decoder.feed_eof() {
|
match decoder.feed_eof() {
|
||||||
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
Ok(Some(res)) => Poll::Ready(Some(Ok(res))),
|
||||||
|
@ -125,10 +135,8 @@ where
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Poll::Pending => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Pending
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +152,7 @@ impl ContentDecoder {
|
||||||
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
|
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,9 +161,11 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -163,6 +174,7 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
@ -183,6 +195,7 @@ impl ContentDecoder {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,10 +204,12 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
|
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,9 +218,11 @@ impl ContentDecoder {
|
||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
|
||||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
|
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
if !b.is_empty() {
|
if !b.is_empty() {
|
||||||
Ok(Some(b))
|
Ok(Some(b))
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
//! Stream encoders.
|
//! Stream encoders.
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
use std::{
|
||||||
use std::{future::Future, io, io::Write as _, pin::Pin};
|
future::Future,
|
||||||
|
io::{self, Write as _},
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
|
@ -10,15 +14,19 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
|
|
||||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
use crate::{
|
||||||
use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
|
body::{Body, BodySize, MessageBody, ResponseBody},
|
||||||
use crate::http::{HeaderValue, StatusCode};
|
http::{
|
||||||
use crate::{Error, ResponseHead};
|
header::{ContentEncoding, CONTENT_ENCODING},
|
||||||
|
HeaderValue, StatusCode,
|
||||||
|
},
|
||||||
|
Error, ResponseHead,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Writer;
|
use super::Writer;
|
||||||
use crate::error::BlockingError;
|
use crate::error::BlockingError;
|
||||||
|
|
||||||
const INPLACE: usize = 1024;
|
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
|
||||||
|
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct Encoder<B> {
|
pub struct Encoder<B> {
|
||||||
|
@ -137,23 +145,28 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
if let Some(ref mut fut) = this.fut {
|
if let Some(ref mut fut) = this.fut {
|
||||||
let mut encoder =
|
let mut encoder =
|
||||||
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??;
|
||||||
|
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
this.fut.take();
|
this.fut.take();
|
||||||
|
|
||||||
if !chunk.is_empty() {
|
if !chunk.is_empty() {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = this.body.as_mut().poll_next(cx);
|
let result = ready!(this.body.as_mut().poll_next(cx));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Poll::Ready(Some(Ok(chunk))) => {
|
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
|
||||||
|
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
if let Some(mut encoder) = this.encoder.take() {
|
if let Some(mut encoder) = this.encoder.take() {
|
||||||
if chunk.len() < INPLACE {
|
if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE {
|
||||||
encoder.write(&chunk)?;
|
encoder.write(&chunk)?;
|
||||||
let chunk = encoder.take();
|
let chunk = encoder.take();
|
||||||
*this.encoder = Some(encoder);
|
*this.encoder = Some(encoder);
|
||||||
|
|
||||||
if !chunk.is_empty() {
|
if !chunk.is_empty() {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
|
@ -167,7 +180,8 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
return Poll::Ready(Some(Ok(chunk)));
|
return Poll::Ready(Some(Ok(chunk)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Poll::Ready(None) => {
|
|
||||||
|
None => {
|
||||||
if let Some(encoder) = this.encoder.take() {
|
if let Some(encoder) = this.encoder.take() {
|
||||||
let chunk = encoder.finish()?;
|
let chunk = encoder.finish()?;
|
||||||
if chunk.is_empty() {
|
if chunk.is_empty() {
|
||||||
|
@ -180,7 +194,6 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||||
return Poll::Ready(None);
|
return Poll::Ready(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val => return val,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,10 @@ use crate::http::{header, Method, StatusCode, Uri, Version};
|
||||||
pub enum ConnectionType {
|
pub enum ConnectionType {
|
||||||
/// Close connection after response
|
/// Close connection after response
|
||||||
Close,
|
Close,
|
||||||
|
|
||||||
/// Keep connection alive after response
|
/// Keep connection alive after response
|
||||||
KeepAlive,
|
KeepAlive,
|
||||||
|
|
||||||
/// Connection is upgraded to different type
|
/// Connection is upgraded to different type
|
||||||
Upgrade,
|
Upgrade,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,35 +8,65 @@ pub fn parse_http_date(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to parse a `time` string as a RFC 1123 formatted date time string.
|
/// 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<PrimitiveDateTime> {
|
fn try_parse_rfc_1123(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
time::parse(time, "%a, %d %b %Y %H:%M:%S").ok()
|
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.
|
/// 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<PrimitiveDateTime> {
|
fn try_parse_rfc_850(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") {
|
let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?;
|
||||||
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 = 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 {
|
// If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3,
|
||||||
expanded_year -= 100;
|
// 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.
|
||||||
|
|
||||||
match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) {
|
let now = OffsetDateTime::now_utc();
|
||||||
Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())),
|
let century_start_year = (now.year() / 100) * 100;
|
||||||
Err(_) => None,
|
let mut expanded_year = century_start_year + dt.year();
|
||||||
}
|
|
||||||
}
|
if expanded_year > now.year() + 50 {
|
||||||
Err(_) => None,
|
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.
|
/// 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<PrimitiveDateTime> {
|
fn try_parse_asctime(time: &str) -> Option<PrimitiveDateTime> {
|
||||||
time::parse(time, "%a %b %_d %H:%M:%S %Y").ok()
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue