From 15ea560863a552193745f290957aacc4218f3345 Mon Sep 17 00:00:00 2001 From: Nivesh Dandyan Date: Thu, 5 Feb 2026 16:06:49 +0000 Subject: [PATCH] fix: change ContentLength internal type from usize to u64 This fixes the test failure on 32-bit platforms where the large value test (u64::MAX) would fail because `usize` cannot hold values larger than 4GB on 32-bit systems. According to RFC 9110, Content-Length can be any valid unsigned 64-bit integer, and it's reasonable to have content larger than 4GB even on 32-bit systems (e.g., streaming large files). Changes: - Change ContentLength inner type from usize to u64 - Update into_inner() return type to u64 - Update FromStr to use u64 parsing - Add From implementation - Update From to convert to u64 - Add PartialEq/PartialOrd implementations for u64 - Update usize comparisons to properly convert - Fix test to use u64 literal suffix - Update json.rs to convert u64 to usize with try_from Fixes #3812 Co-Authored-By: Claude (claude-opus-4-5) --- actix-web/src/http/header/content_length.rs | 56 ++++++++++++++++----- actix-web/src/types/json.rs | 4 +- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/actix-web/src/http/header/content_length.rs b/actix-web/src/http/header/content_length.rs index 557c7c9f5..f2f284cce 100644 --- a/actix-web/src/http/header/content_length.rs +++ b/actix-web/src/http/header/content_length.rs @@ -39,17 +39,17 @@ use crate::{ /// /// [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); +pub struct ContentLength(pub u64); impl ContentLength { /// Returns Content-Length value. - pub fn into_inner(&self) -> usize { + pub fn into_inner(&self) -> u64 { self.0 } } impl str::FromStr for ContentLength { - type Err = ::Err; + type Err = ::Err; #[inline] fn from_str(val: &str) -> Result { @@ -85,39 +85,69 @@ impl Header for ContentLength { } } -impl From for usize { +impl From for u64 { fn from(ContentLength(len): ContentLength) -> Self { len } } -impl From for ContentLength { - fn from(len: usize) -> Self { +impl From for ContentLength { + fn from(len: u64) -> Self { ContentLength(len) } } -impl PartialEq for ContentLength { - fn eq(&self, other: &usize) -> bool { +impl From for ContentLength { + fn from(len: usize) -> Self { + ContentLength(len as u64) + } +} + +impl PartialEq for ContentLength { + fn eq(&self, other: &u64) -> bool { self.0 == *other } } -impl PartialEq for usize { +impl PartialEq for u64 { fn eq(&self, other: &ContentLength) -> bool { *self == other.0 } } +impl PartialOrd for ContentLength { + fn partial_cmp(&self, other: &u64) -> Option { + self.0.partial_cmp(other) + } +} + +impl PartialOrd for u64 { + fn partial_cmp(&self, other: &ContentLength) -> Option { + self.partial_cmp(&other.0) + } +} + +impl PartialEq for ContentLength { + fn eq(&self, other: &usize) -> bool { + self.0 == *other as u64 + } +} + +impl PartialEq for usize { + fn eq(&self, other: &ContentLength) -> bool { + (*self as u64) == other.0 + } +} + impl PartialOrd for ContentLength { fn partial_cmp(&self, other: &usize) -> Option { - self.0.partial_cmp(other) + self.0.partial_cmp(&(*other as u64)) } } impl PartialOrd for usize { fn partial_cmp(&self, other: &ContentLength) -> Option { - self.partial_cmp(&other.0) + (*self as u64).partial_cmp(&other.0) } } @@ -215,10 +245,10 @@ mod tests { assert_parse_eq::(["0 "], ContentLength(0)); assert_parse_eq::([" 0 "], ContentLength(0)); - // large value (2^64 - 1) + // large value (2^64 - 1), now works on 32-bit platforms too assert_parse_eq::( ["18446744073709551615"], - ContentLength(18_446_744_073_709_551_615), + ContentLength(18_446_744_073_709_551_615_u64), ); } diff --git a/actix-web/src/types/json.rs b/actix-web/src/types/json.rs index 58464f360..779a55de2 100644 --- a/actix-web/src/types/json.rs +++ b/actix-web/src/types/json.rs @@ -347,7 +347,9 @@ impl JsonBody { return JsonBody::Error(Some(JsonPayloadError::ContentType)); } - let length = ContentLength::parse(req).ok().map(|x| x.0); + let length = ContentLength::parse(req) + .ok() + .and_then(|x| usize::try_from(x.0).ok()); // Notice the content-length is not checked against limit of json config here. // As the internal usage always call JsonBody::limit after JsonBody::new.