diff --git a/rustfs/src/storage/ecfs_extend.rs b/rustfs/src/storage/ecfs_extend.rs index f521e32cb..38bd59749 100644 --- a/rustfs/src/storage/ecfs_extend.rs +++ b/rustfs/src/storage/ecfs_extend.rs @@ -47,10 +47,14 @@ use std::collections::HashMap; use std::ops::Add; use std::sync::Arc; use time::OffsetDateTime; -use time::format_description::well_known::{Rfc2822, Rfc3339}; +use time::format_description::well_known::Rfc3339; +use time::{format_description::FormatItem, macros::format_description}; use tokio::io::AsyncRead; use tracing::{debug, warn}; +const RFC1123: &[FormatItem<'_>] = + format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"); + /// ======================= /// Presigned POST helpers /// ======================= @@ -614,7 +618,7 @@ pub(crate) async fn validate_bucket_object_lock_enabled(bucket: &str) -> S3Resul } /// Validates HTTP conditional request headers for a single object according to -/// RFC 7232 and S3 API semantics. +/// RFC 7232 (HTTP/1.1 conditional requests) and S3 API semantics. /// /// This function evaluates the following headers, if present, in the standard /// conditional request order: @@ -654,7 +658,7 @@ pub(crate) fn check_preconditions(headers: &HeaderMap, info: &ObjectInfo) -> S3R if headers.get("if-match").is_none() && let Some(t) = mod_time && let Some(if_unmodified_since) = headers.get("if-unmodified-since").and_then(|v| v.to_str().ok()) - && let Ok(given_time) = OffsetDateTime::parse(if_unmodified_since, &Rfc2822) + && let Ok(given_time) = time::PrimitiveDateTime::parse(if_unmodified_since, &RFC1123).map(|dt| dt.assume_utc()) && t > given_time.add(time::Duration::seconds(1)) { return Err(S3Error::new(S3ErrorCode::PreconditionFailed)); @@ -670,7 +674,7 @@ pub(crate) fn check_preconditions(headers: &HeaderMap, info: &ObjectInfo) -> S3R error_headers.insert("etag", etag_header); } if let Some(t) = mod_time - && let Ok(last_modified_str) = t.format(&Rfc2822) + && let Ok(last_modified_str) = t.format(&RFC1123) && let Ok(last_modified_header) = HeaderValue::from_str(&last_modified_str) { error_headers.insert("last-modified", last_modified_header); @@ -683,11 +687,11 @@ pub(crate) fn check_preconditions(headers: &HeaderMap, info: &ObjectInfo) -> S3R return Err(s3_error); } - // If-Modified-Since (only when If-None-Match is absent - RFC 7232) + // If-Modified-Since (only when If-None-Match is absent — semantics per RFC 7232; dates use RFC 1123 format) if headers.get("if-none-match").is_none() && let Some(t) = mod_time && let Some(if_modified_since) = headers.get("if-modified-since").and_then(|v| v.to_str().ok()) - && let Ok(given_time) = OffsetDateTime::parse(if_modified_since, &Rfc2822) + && let Ok(given_time) = time::PrimitiveDateTime::parse(if_modified_since, &RFC1123).map(|dt| dt.assume_utc()) && t < given_time.add(time::Duration::seconds(1)) { let mut error_headers = HeaderMap::new(); @@ -696,7 +700,7 @@ pub(crate) fn check_preconditions(headers: &HeaderMap, info: &ObjectInfo) -> S3R { error_headers.insert("etag", etag_header); } - if let Ok(last_modified_str) = t.format(&Rfc2822) + if let Ok(last_modified_str) = t.format(&RFC1123) && let Ok(last_modified_header) = HeaderValue::from_str(&last_modified_str) { error_headers.insert("last-modified", last_modified_header); diff --git a/rustfs/src/storage/ecfs_test.rs b/rustfs/src/storage/ecfs_test.rs index 305619694..d282a398e 100644 --- a/rustfs/src/storage/ecfs_test.rs +++ b/rustfs/src/storage/ecfs_test.rs @@ -35,7 +35,6 @@ mod tests { }; use s3s::{S3Error, S3ErrorCode, s3_error}; use time::OffsetDateTime; - use time::format_description::well_known::Rfc2822; #[test] fn test_fs_creation() { @@ -558,6 +557,9 @@ mod tests { #[test] fn test_check_preconditions() { + use time::{format_description::FormatItem, macros::format_description}; + const RFC1123: &[FormatItem<'_>] = + format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"); let valid_mod_time = OffsetDateTime::from_unix_timestamp(1700000000).unwrap(); let valid_etag = "\"d41d8cd98f00b204e9800998ecf8427e\""; let wrong_etag = "\"wrong-etag-123456\""; @@ -605,7 +607,7 @@ mod tests { let mut headers5 = HeaderMap::new(); headers5.insert( "if-modified-since", - HeaderValue::from_str(&valid_mod_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&valid_mod_time.format(&RFC1123).unwrap()).unwrap(), ); let info5 = info3.clone(); let result5 = check_preconditions(&headers5, &info5); @@ -618,7 +620,7 @@ mod tests { let mut headers6 = HeaderMap::new(); headers6.insert( "if-modified-since", - HeaderValue::from_str(&earlier_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&earlier_time.format(&RFC1123).unwrap()).unwrap(), ); let info6 = info3.clone(); assert!(check_preconditions(&headers6, &info6).is_ok()); @@ -641,7 +643,7 @@ mod tests { let mut headers9 = HeaderMap::new(); headers9.insert( "if-unmodified-since", - HeaderValue::from_str(&earlier_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&earlier_time.format(&RFC1123).unwrap()).unwrap(), ); let info9 = info3.clone(); let result9 = check_preconditions(&headers9, &info9); @@ -652,7 +654,7 @@ mod tests { let mut headers10 = HeaderMap::new(); headers10.insert( "if-unmodified-since", - HeaderValue::from_str(&valid_mod_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&valid_mod_time.format(&RFC1123).unwrap()).unwrap(), ); let info10 = info3.clone(); assert!(check_preconditions(&headers10, &info10).is_ok()); @@ -684,7 +686,7 @@ mod tests { headers13.insert("if-none-match", HeaderValue::from_str(valid_etag).unwrap()); headers13.insert( "if-modified-since", - HeaderValue::from_str(&earlier_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&earlier_time.format(&RFC1123).unwrap()).unwrap(), ); let info13 = info3.clone(); let result13 = check_preconditions(&headers13, &info13); @@ -696,7 +698,7 @@ mod tests { headers14.insert("if-none-match", HeaderValue::from_str("\"wrong-etag\"").unwrap()); headers14.insert( "if-modified-since", - HeaderValue::from_str(&valid_mod_time.format(&Rfc2822).unwrap()).unwrap(), + HeaderValue::from_str(&valid_mod_time.format(&RFC1123).unwrap()).unwrap(), ); let info14 = info3.clone(); assert!(check_preconditions(&headers14, &info14).is_ok());