diff --git a/src/api/s3/get.rs b/src/api/s3/get.rs index 888a040a..a1e4ce10 100644 --- a/src/api/s3/get.rs +++ b/src/api/s3/get.rs @@ -845,7 +845,9 @@ impl PreconditionHeaders { } fn check(&self, v: &ObjectVersion, etag: &str) -> Result, Error> { - let v_date = UNIX_EPOCH + Duration::from_millis(v.timestamp); + // we store date with ms precision, but headers are precise to the second: truncate + // the timestamp to handle the same-second edge case + let v_date = UNIX_EPOCH + Duration::from_secs(v.timestamp / 1000); // Implemented from https://datatracker.ietf.org/doc/html/rfc7232#section-6 diff --git a/src/garage/tests/s3/objects.rs b/src/garage/tests/s3/objects.rs index d63ac000..53e8231d 100644 --- a/src/garage/tests/s3/objects.rs +++ b/src/garage/tests/s3/objects.rs @@ -198,6 +198,7 @@ async fn test_precondition() { ); } let older_date = DateTime::from_secs_f64(last_modified.as_secs_f64() - 10.0); + let same_date = DateTime::from_secs_f64(last_modified.as_secs_f64()); let newer_date = DateTime::from_secs_f64(last_modified.as_secs_f64() + 10.0); { let err = ctx @@ -212,6 +213,18 @@ async fn test_precondition() { matches!(err, Err(SdkError::ServiceError(se)) if se.raw().status().as_u16() == 304) ); + let err = ctx + .client + .get_object() + .bucket(&bucket) + .key(STD_KEY) + .if_modified_since(same_date) + .send() + .await; + assert!( + matches!(err, Err(SdkError::ServiceError(se)) if se.raw().status().as_u16() == 304) + ); + let o = ctx .client .get_object() @@ -236,6 +249,17 @@ async fn test_precondition() { matches!(err, Err(SdkError::ServiceError(se)) if se.raw().status().as_u16() == 412) ); + let o = ctx + .client + .get_object() + .bucket(&bucket) + .key(STD_KEY) + .if_unmodified_since(same_date) + .send() + .await + .unwrap(); + assert_eq!(o.e_tag.as_ref().unwrap().as_str(), etag); + let o = ctx .client .get_object()