diff --git a/cmd/object-handlers-common.go b/cmd/object-handlers-common.go index fba7ddbda..9325d8ecf 100644 --- a/cmd/object-handlers-common.go +++ b/cmd/object-handlers-common.go @@ -268,6 +268,14 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ ifNoneMatchETagHeader := r.Header.Get(xhttp.IfNoneMatch) if ifNoneMatchETagHeader != "" { if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) { + // Do not care If-Modified-Since, Because: + // 1. If If-Modified-Since condition evaluates to true. + // If both of the If-None-Match and If-Modified-Since headers are present in the request as follows: + // If-None-Match condition evaluates to false , and; + // If-Modified-Since condition evaluates to true ; + // Then Amazon S3 returns the 304 Not Modified response code. + // 2. If If-Modified-Since condition evaluates to false, The following `ifModifiedSinceHeader` judgment will also return 304 + // If the object ETag matches with the specified ETag. writeHeadersPrecondition(w, objInfo) w.WriteHeader(http.StatusNotModified) @@ -304,7 +312,7 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ // If-Unmodified-Since : Return the object only if it has not been modified since the specified // time, otherwise return a 412 (precondition failed). ifUnmodifiedSinceHeader := r.Header.Get(xhttp.IfUnmodifiedSince) - if ifUnmodifiedSinceHeader != "" { + if ifUnmodifiedSinceHeader != "" && ifMatchETagHeader == "" { if givenTime, err := amztime.ParseHeader(ifUnmodifiedSinceHeader); err == nil { if ifModifiedSince(objInfo.ModTime, givenTime) { // If the object is modified since the specified time. diff --git a/cmd/object-handlers-common_test.go b/cmd/object-handlers-common_test.go index 6d190a95e..08a787f73 100644 --- a/cmd/object-handlers-common_test.go +++ b/cmd/object-handlers-common_test.go @@ -18,7 +18,14 @@ package cmd import ( + "bytes" + "context" + "net/http" + "net/http/httptest" "testing" + "time" + + xhttp "github.com/minio/minio/internal/http" ) // Tests - canonicalizeETag() @@ -51,3 +58,125 @@ func TestCanonicalizeETag(t *testing.T) { } } } + +// Tests - CheckPreconditions() +func TestCheckPreconditions(t *testing.T) { + objModTime := time.Date(2024, time.August, 26, 0o2, 0o1, 0o1, 0, time.UTC) + objInfo := ObjectInfo{ETag: "aa", ModTime: objModTime} + testCases := []struct { + name string + ifMatch string + ifNoneMatch string + ifModifiedSince string + ifUnmodifiedSince string + objInfo ObjectInfo + expectedFlag bool + expectedCode int + }{ + // If-None-Match(false) and If-Modified-Since(true) + { + name: "If-None-Match1", + ifNoneMatch: "aa", + ifModifiedSince: "Sun, 26 Aug 2024 02:01:00 GMT", + objInfo: objInfo, + expectedFlag: true, + expectedCode: 304, + }, + // If-Modified-Since(false) + { + name: "If-None-Match2", + ifNoneMatch: "aaa", + ifModifiedSince: "Sun, 26 Aug 2024 02:01:01 GMT", + objInfo: objInfo, + expectedFlag: true, + expectedCode: 304, + }, + { + name: "If-None-Match3", + ifNoneMatch: "aaa", + ifModifiedSince: "Sun, 26 Aug 2024 02:01:02 GMT", + objInfo: objInfo, + expectedFlag: true, + expectedCode: 304, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodHead, "/bucket/a", bytes.NewReader([]byte{})) + request.Header.Set(xhttp.IfNoneMatch, tc.ifNoneMatch) + request.Header.Set(xhttp.IfModifiedSince, tc.ifModifiedSince) + request.Header.Set(xhttp.IfMatch, tc.ifMatch) + request.Header.Set(xhttp.IfUnmodifiedSince, tc.ifUnmodifiedSince) + actualFlag := checkPreconditions(context.Background(), recorder, request, tc.objInfo, ObjectOptions{}) + if tc.expectedFlag != actualFlag { + t.Errorf("test: %s, got flag: %v, want: %v", tc.name, actualFlag, tc.expectedFlag) + } + if tc.expectedCode != recorder.Code { + t.Errorf("test: %s, got code: %d, want: %d", tc.name, recorder.Code, tc.expectedCode) + } + }) + } + testCases = []struct { + name string + ifMatch string + ifNoneMatch string + ifModifiedSince string + ifUnmodifiedSince string + objInfo ObjectInfo + expectedFlag bool + expectedCode int + }{ + // If-Match(true) and If-Unmodified-Since(false) + { + name: "If-Match1", + ifMatch: "aa", + ifUnmodifiedSince: "Sun, 26 Aug 2024 02:01:00 GMT", + objInfo: objInfo, + expectedFlag: false, + expectedCode: 200, + }, + // If-Unmodified-Since(true) + { + name: "If-Match2", + ifMatch: "aa", + ifUnmodifiedSince: "Sun, 26 Aug 2024 02:01:01 GMT", + objInfo: objInfo, + expectedFlag: false, + expectedCode: 200, + }, + { + name: "If-Match3", + ifMatch: "aa", + ifUnmodifiedSince: "Sun, 26 Aug 2024 02:01:02 GMT", + objInfo: objInfo, + expectedFlag: false, + expectedCode: 200, + }, + // If-Match(true) + { + name: "If-Match4", + ifMatch: "aa", + objInfo: objInfo, + expectedFlag: false, + expectedCode: 200, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodHead, "/bucket/a", bytes.NewReader([]byte{})) + request.Header.Set(xhttp.IfNoneMatch, tc.ifNoneMatch) + request.Header.Set(xhttp.IfModifiedSince, tc.ifModifiedSince) + request.Header.Set(xhttp.IfMatch, tc.ifMatch) + request.Header.Set(xhttp.IfUnmodifiedSince, tc.ifUnmodifiedSince) + actualFlag := checkPreconditions(context.Background(), recorder, request, tc.objInfo, ObjectOptions{}) + if tc.expectedFlag != actualFlag { + t.Errorf("test: %s, got flag: %v, want: %v", tc.name, actualFlag, tc.expectedFlag) + } + if tc.expectedCode != recorder.Code { + t.Errorf("test: %s, got code: %d, want: %d", tc.name, recorder.Code, tc.expectedCode) + } + }) + } +}