mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 11:31:09 +00:00
modernize ContentLength
This commit is contained in:
parent
6a0ea51b15
commit
ff8fd2f7b5
1 changed files with 232 additions and 50 deletions
|
@ -1,56 +1,238 @@
|
||||||
use super::common_header;
|
use std::{convert::Infallible, str};
|
||||||
use crate::http::header;
|
|
||||||
|
|
||||||
common_header! {
|
use derive_more::{Deref, DerefMut};
|
||||||
/// `Content-Length` header, defined in [RFC 7230 §3.3.2].
|
|
||||||
///
|
|
||||||
/// The Content-Length
|
|
||||||
///
|
|
||||||
/// # ABNF
|
|
||||||
///
|
|
||||||
/// ```plain
|
|
||||||
/// Content-Length = 1*DIGIT
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Example Values
|
|
||||||
///
|
|
||||||
/// - `0`
|
|
||||||
/// - `3495`
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use actix_web::{http::header::ContentLength, HttpResponse};
|
|
||||||
///
|
|
||||||
/// let res_empty = HttpResponse::Ok()
|
|
||||||
/// .insert_header(ContentLength(0));
|
|
||||||
///
|
|
||||||
/// let res_fake_cl = HttpResponse::Ok()
|
|
||||||
/// .insert_header(ContentLength(3_495));
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [RFC 7230 §3.3.2]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
|
|
||||||
(ContentLength, header::CONTENT_LENGTH) => [usize]
|
|
||||||
|
|
||||||
test_parse_and_format {
|
use crate::{
|
||||||
common_header_test!(no_header, [b""; 0], None);
|
error::ParseError,
|
||||||
common_header_test!(empty_header, [b""; 1], None);
|
http::header::{
|
||||||
|
from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue, CONTENT_LENGTH,
|
||||||
|
},
|
||||||
|
HttpMessage,
|
||||||
|
};
|
||||||
|
|
||||||
common_header_test!(zero, [b"0"], Some(ContentLength(0)));
|
/// `Content-Length` header, defined in [RFC 9110 §8.6].
|
||||||
common_header_test!(one, [b"1"], Some(ContentLength(1)));
|
///
|
||||||
common_header_test!(one_two_three, [b"123"], Some(ContentLength(123)));
|
/// The Content-Length
|
||||||
common_header_test!(
|
///
|
||||||
thirty_two_power_plus_one,
|
/// # ABNF
|
||||||
[b"4294967297"],
|
///
|
||||||
Some(ContentLength(4_294_967_297))
|
/// ```plain
|
||||||
);
|
/// Content-Length = 1*DIGIT
|
||||||
common_header_test!(
|
/// ```
|
||||||
sixty_four_power_minus_one,
|
///
|
||||||
[b"18446744073709551615"],
|
/// # Example Values
|
||||||
Some(ContentLength(18_446_744_073_709_551_615))
|
///
|
||||||
);
|
/// - `0`
|
||||||
|
/// - `3495`
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::{http::header::ContentLength, HttpResponse};
|
||||||
|
///
|
||||||
|
/// let res_empty = HttpResponse::Ok()
|
||||||
|
/// .insert_header(ContentLength(0));
|
||||||
|
///
|
||||||
|
/// let res_fake_cl = HttpResponse::Ok()
|
||||||
|
/// .insert_header(ContentLength(3_495));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [RFC 9110 §8.6]: https://www.rfc-editor.org/rfc/rfc9110#name-content-length
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
|
||||||
|
pub struct ContentLength(pub usize);
|
||||||
|
|
||||||
common_header_test!(invalid1, [b"123,567"], None);
|
impl ContentLength {
|
||||||
common_header_test!(invalid2, [b"123_567"], None);
|
/// Returns Content-Length value.
|
||||||
|
pub fn into_inner(&self) -> usize {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl str::FromStr for ContentLength {
|
||||||
|
type Err = <usize as str::FromStr>::Err;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||||
|
let val = val.trim();
|
||||||
|
|
||||||
|
// decoder prevents this case
|
||||||
|
debug_assert!(!val.starts_with('+'));
|
||||||
|
|
||||||
|
val.parse().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryIntoHeaderValue for ContentLength {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
Ok(HeaderValue::from(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for ContentLength {
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
CONTENT_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError> {
|
||||||
|
let val = from_one_raw_str(msg.headers().get(Self::name()))?;
|
||||||
|
|
||||||
|
// decoder prevents multiple CL headers
|
||||||
|
debug_assert_eq!(msg.headers().get_all(Self::name()).count(), 1);
|
||||||
|
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContentLength> for usize {
|
||||||
|
fn from(ContentLength(len): ContentLength) -> Self {
|
||||||
|
len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<usize> for ContentLength {
|
||||||
|
fn from(len: usize) -> Self {
|
||||||
|
ContentLength(len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<usize> for ContentLength {
|
||||||
|
fn eq(&self, other: &usize) -> bool {
|
||||||
|
self.0 == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<ContentLength> for usize {
|
||||||
|
fn eq(&self, other: &ContentLength) -> bool {
|
||||||
|
*self == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd<usize> for ContentLength {
|
||||||
|
fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> {
|
||||||
|
self.0.partial_cmp(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd<ContentLength> for usize {
|
||||||
|
fn partial_cmp(&self, other: &ContentLength) -> Option<std::cmp::Ordering> {
|
||||||
|
self.partial_cmp(&other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{http::header::Header, test::TestRequest, HttpRequest};
|
||||||
|
|
||||||
|
fn req_from_raw_headers<H: Header, I: IntoIterator<Item = V>, V: AsRef<[u8]>>(
|
||||||
|
header_lines: I,
|
||||||
|
) -> HttpRequest {
|
||||||
|
header_lines
|
||||||
|
.into_iter()
|
||||||
|
.fold(TestRequest::default(), |req, item| {
|
||||||
|
req.append_header((H::name(), item.as_ref().to_vec()))
|
||||||
|
})
|
||||||
|
.to_http_request()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn assert_parse_fail<
|
||||||
|
H: Header + fmt::Debug,
|
||||||
|
I: IntoIterator<Item = V>,
|
||||||
|
V: AsRef<[u8]>,
|
||||||
|
>(
|
||||||
|
headers: I,
|
||||||
|
) {
|
||||||
|
let req = req_from_raw_headers::<H, _, _>(headers);
|
||||||
|
H::parse(&req).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn assert_parse_eq<
|
||||||
|
H: Header + fmt::Debug + PartialEq,
|
||||||
|
I: IntoIterator<Item = V>,
|
||||||
|
V: AsRef<[u8]>,
|
||||||
|
>(
|
||||||
|
headers: I,
|
||||||
|
expect: H,
|
||||||
|
) {
|
||||||
|
let req = req_from_raw_headers::<H, _, _>(headers);
|
||||||
|
assert_eq!(H::parse(&req).unwrap(), expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_header() {
|
||||||
|
assert_parse_fail::<ContentLength, _, _>([""; 0]);
|
||||||
|
assert_parse_fail::<ContentLength, _, _>([""]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bad_header() {
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["-123"]);
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["123_456"]);
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["123.456"]);
|
||||||
|
|
||||||
|
// too large for u64 (2^64, 2^64 + 1)
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["18446744073709551616"]);
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["18446744073709551617"]);
|
||||||
|
|
||||||
|
// hex notation
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["0x123"]);
|
||||||
|
|
||||||
|
// multi-value
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["0, 123"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn bad_header_plus() {
|
||||||
|
// prevented by HTTP decoder anyway
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["+123"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn bad_multiple_value() {
|
||||||
|
// prevented by HTTP decoder anyway
|
||||||
|
assert_parse_fail::<ContentLength, _, _>(["0", "123"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn good_header() {
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(["0"], ContentLength(0));
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(["1"], ContentLength(1));
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(["123"], ContentLength(123));
|
||||||
|
|
||||||
|
// value that looks like octal notation is not interpreted as such
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(["0123"], ContentLength(123));
|
||||||
|
|
||||||
|
// whitespace variations
|
||||||
|
assert_parse_eq::<ContentLength, _, _>([" 0"], ContentLength(0));
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(["0 "], ContentLength(0));
|
||||||
|
assert_parse_eq::<ContentLength, _, _>([" 0 "], ContentLength(0));
|
||||||
|
|
||||||
|
// large value (2^64 - 1)
|
||||||
|
assert_parse_eq::<ContentLength, _, _>(
|
||||||
|
["18446744073709551615"],
|
||||||
|
ContentLength(18_446_744_073_709_551_615),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equality() {
|
||||||
|
assert!(ContentLength(0) == ContentLength(0));
|
||||||
|
assert!(ContentLength(0) == 0);
|
||||||
|
assert!(0 != ContentLength(123));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordering() {
|
||||||
|
assert!(ContentLength(0) < ContentLength(123));
|
||||||
|
assert!(ContentLength(0) < 123);
|
||||||
|
assert!(0 < ContentLength(123));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue