diff --git a/actix-web/src/http/header/content_length.rs b/actix-web/src/http/header/content_length.rs index b2769e5fa..ad16dc409 100644 --- a/actix-web/src/http/header/content_length.rs +++ b/actix-web/src/http/header/content_length.rs @@ -1,56 +1,238 @@ -use super::common_header; -use crate::http::header; +use std::{convert::Infallible, str}; -common_header! { - /// `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] +use derive_more::{Deref, DerefMut}; - test_parse_and_format { - common_header_test!(no_header, [b""; 0], None); - common_header_test!(empty_header, [b""; 1], None); +use crate::{ + error::ParseError, + http::header::{ + from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue, CONTENT_LENGTH, + }, + HttpMessage, +}; - common_header_test!(zero, [b"0"], Some(ContentLength(0))); - common_header_test!(one, [b"1"], Some(ContentLength(1))); - common_header_test!(one_two_three, [b"123"], Some(ContentLength(123))); - common_header_test!( - thirty_two_power_plus_one, - [b"4294967297"], - Some(ContentLength(4_294_967_297)) - ); - common_header_test!( - sixty_four_power_minus_one, - [b"18446744073709551615"], - Some(ContentLength(18_446_744_073_709_551_615)) - ); +/// `Content-Length` header, defined in [RFC 9110 §8.6]. +/// +/// 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 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); - common_header_test!(invalid2, [b"123_567"], None); +impl ContentLength { + /// Returns Content-Length value. + pub fn into_inner(&self) -> usize { + self.0 + } +} + +impl str::FromStr for ContentLength { + type Err = ::Err; + + #[inline] + fn from_str(val: &str) -> Result { + 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 { + Ok(HeaderValue::from(self.0)) + } +} + +impl Header for ContentLength { + fn name() -> HeaderName { + CONTENT_LENGTH + } + + fn parse(msg: &M) -> Result { + 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 for usize { + fn from(ContentLength(len): ContentLength) -> Self { + len + } +} + +impl From for ContentLength { + fn from(len: usize) -> Self { + ContentLength(len) + } +} + +impl PartialEq for ContentLength { + fn eq(&self, other: &usize) -> bool { + self.0 == *other + } +} + +impl PartialEq for usize { + fn eq(&self, other: &ContentLength) -> bool { + *self == other.0 + } +} + +impl PartialOrd for ContentLength { + fn partial_cmp(&self, other: &usize) -> Option { + self.0.partial_cmp(other) + } +} + +impl PartialOrd for usize { + fn partial_cmp(&self, other: &ContentLength) -> Option { + 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, 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, + V: AsRef<[u8]>, + >( + headers: I, + ) { + let req = req_from_raw_headers::(headers); + H::parse(&req).unwrap_err(); + } + + #[track_caller] + pub(crate) fn assert_parse_eq< + H: Header + fmt::Debug + PartialEq, + I: IntoIterator, + V: AsRef<[u8]>, + >( + headers: I, + expect: H, + ) { + let req = req_from_raw_headers::(headers); + assert_eq!(H::parse(&req).unwrap(), expect); + } + + #[test] + fn missing_header() { + assert_parse_fail::([""; 0]); + assert_parse_fail::([""]); + } + + #[test] + fn bad_header() { + assert_parse_fail::(["-123"]); + assert_parse_fail::(["123_456"]); + assert_parse_fail::(["123.456"]); + + // too large for u64 (2^64, 2^64 + 1) + assert_parse_fail::(["18446744073709551616"]); + assert_parse_fail::(["18446744073709551617"]); + + // hex notation + assert_parse_fail::(["0x123"]); + + // multi-value + assert_parse_fail::(["0, 123"]); + } + + #[test] + #[should_panic] + fn bad_header_plus() { + // prevented by HTTP decoder anyway + assert_parse_fail::(["+123"]); + } + + #[test] + #[should_panic] + fn bad_multiple_value() { + // prevented by HTTP decoder anyway + assert_parse_fail::(["0", "123"]); + } + + #[test] + fn good_header() { + assert_parse_eq::(["0"], ContentLength(0)); + assert_parse_eq::(["1"], ContentLength(1)); + assert_parse_eq::(["123"], ContentLength(123)); + + // value that looks like octal notation is not interpreted as such + assert_parse_eq::(["0123"], ContentLength(123)); + + // whitespace variations + assert_parse_eq::([" 0"], ContentLength(0)); + assert_parse_eq::(["0 "], ContentLength(0)); + assert_parse_eq::([" 0 "], ContentLength(0)); + + // large value (2^64 - 1) + assert_parse_eq::( + ["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)); } }