From d73c5f40f315584b0ee74b9e1e5fceead4a7ed6e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 4 Mar 2026 11:04:14 -0800 Subject: [PATCH] s3api: fix combined conditional header evaluation per RFC 7232 Per RFC 7232: - Section 3.4: If-Unmodified-Since MUST be ignored when If-Match is present (If-Match is the more accurate replacement) - Section 3.3: If-Modified-Since MUST be ignored when If-None-Match is present (If-None-Match is the more accurate replacement) Previously, all four conditional headers were evaluated independently. This caused incorrect 412 responses when If-Match succeeded but If-Unmodified-Since failed (should return 200 per AWS S3 behavior). Fix applied to both validateConditionalHeadersForReads (GET/HEAD) and validateConditionalHeaders (PUT) paths. Co-Authored-By: Claude Opus 4.6 --- weed/s3api/s3api_object_handlers_put.go | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index 7ac8b04e2..4b4772e2e 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -1658,7 +1658,11 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond objectExists := entry != nil // For PUT requests, all specified conditions must be met. - // The evaluation order follows AWS S3 behavior for consistency. + // The evaluation order follows RFC 7232 Section 6 and AWS S3 behavior: + // 1. If-Match + // 2. If-Unmodified-Since (ignored when If-Match is present, per RFC 7232 Section 3.4) + // 3. If-None-Match + // 4. If-Modified-Since (ignored when If-None-Match is present, per RFC 7232 Section 3.3) // 1. Check If-Match if headers.ifMatch != "" { @@ -1679,7 +1683,9 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond } // 2. Check If-Unmodified-Since - if !headers.ifUnmodifiedSince.IsZero() { + // Per RFC 7232 Section 3.4: "A recipient MUST ignore If-Unmodified-Since + // if the request contains an If-Match header field" + if !headers.ifUnmodifiedSince.IsZero() && headers.ifMatch == "" { if objectExists { if entry.Attributes != nil { objectModTime := time.Unix(entry.Attributes.Mtime, 0) @@ -1713,7 +1719,9 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond } // 4. Check If-Modified-Since - if !headers.ifModifiedSince.IsZero() { + // Per RFC 7232 Section 3.3: "A recipient MUST ignore If-Modified-Since + // if the request contains an If-None-Match header field" + if !headers.ifModifiedSince.IsZero() && headers.ifNoneMatch == "" { if objectExists { if entry.Attributes != nil { objectModTime := time.Unix(entry.Attributes.Mtime, 0) @@ -1802,7 +1810,11 @@ func (s3a *S3ApiServer) validateConditionalHeadersForReads(r *http.Request, head } // Object exists - check all conditions - // The evaluation order follows AWS S3 behavior for consistency. + // The evaluation order follows RFC 7232 Section 6 and AWS S3 behavior: + // 1. If-Match + // 2. If-Unmodified-Since (ignored when If-Match is present, per RFC 7232 Section 3.4) + // 3. If-None-Match + // 4. If-Modified-Since (ignored when If-None-Match is present, per RFC 7232 Section 3.3) // 1. Check If-Match (412 Precondition Failed if fails) if headers.ifMatch != "" { @@ -1821,7 +1833,9 @@ func (s3a *S3ApiServer) validateConditionalHeadersForReads(r *http.Request, head } // 2. Check If-Unmodified-Since (412 Precondition Failed if fails) - if !headers.ifUnmodifiedSince.IsZero() { + // Per RFC 7232 Section 3.4: "A recipient MUST ignore If-Unmodified-Since + // if the request contains an If-Match header field" + if !headers.ifUnmodifiedSince.IsZero() && headers.ifMatch == "" { objectModTime := time.Unix(entry.Attributes.Mtime, 0) if objectModTime.After(headers.ifUnmodifiedSince) { glog.V(3).Infof("validateConditionalHeadersForReads: If-Unmodified-Since failed - object modified after %s", r.Header.Get(s3_constants.IfUnmodifiedSince)) @@ -1848,7 +1862,9 @@ func (s3a *S3ApiServer) validateConditionalHeadersForReads(r *http.Request, head } // 4. Check If-Modified-Since (304 Not Modified if fails) - if !headers.ifModifiedSince.IsZero() { + // Per RFC 7232 Section 3.3: "A recipient MUST ignore If-Modified-Since + // if the request contains an If-None-Match header field" + if !headers.ifModifiedSince.IsZero() && headers.ifNoneMatch == "" { objectModTime := time.Unix(entry.Attributes.Mtime, 0) if !objectModTime.After(headers.ifModifiedSince) { // Use production getObjectETag method