Browse Source

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 <noreply@anthropic.com>
pull/8504/head
Chris Lu 3 days ago
parent
commit
d73c5f40f3
  1. 28
      weed/s3api/s3api_object_handlers_put.go

28
weed/s3api/s3api_object_handlers_put.go

@ -1658,7 +1658,11 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond
objectExists := entry != nil objectExists := entry != nil
// For PUT requests, all specified conditions must be met. // 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 // 1. Check If-Match
if headers.ifMatch != "" { if headers.ifMatch != "" {
@ -1679,7 +1683,9 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond
} }
// 2. Check If-Unmodified-Since // 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 objectExists {
if entry.Attributes != nil { if entry.Attributes != nil {
objectModTime := time.Unix(entry.Attributes.Mtime, 0) 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 // 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 objectExists {
if entry.Attributes != nil { if entry.Attributes != nil {
objectModTime := time.Unix(entry.Attributes.Mtime, 0) objectModTime := time.Unix(entry.Attributes.Mtime, 0)
@ -1802,7 +1810,11 @@ func (s3a *S3ApiServer) validateConditionalHeadersForReads(r *http.Request, head
} }
// Object exists - check all conditions // 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) // 1. Check If-Match (412 Precondition Failed if fails)
if headers.ifMatch != "" { 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) // 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) objectModTime := time.Unix(entry.Attributes.Mtime, 0)
if objectModTime.After(headers.ifUnmodifiedSince) { if objectModTime.After(headers.ifUnmodifiedSince) {
glog.V(3).Infof("validateConditionalHeadersForReads: If-Unmodified-Since failed - object modified after %s", r.Header.Get(s3_constants.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) // 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) objectModTime := time.Unix(entry.Attributes.Mtime, 0)
if !objectModTime.After(headers.ifModifiedSince) { if !objectModTime.After(headers.ifModifiedSince) {
// Use production getObjectETag method // Use production getObjectETag method

Loading…
Cancel
Save