Preserve ETag case for S3 compatibility (#7498)

Most hadoop distributions hortonworks, cloudera all
depend on aws-sdk-java 1.7.x to 1.10.x - the releases
which have bugs related case sensitive check for
ETag header. Go changes the case of the headers set
to be canonical but only preserves them when set
through a direct map.

This fixes most compatibility issues we have had
in the past supporting older hadoop distributions.
master
Harshavardhana 6 years ago committed by kannappanr
parent 10a607154d
commit a2e344bf30
  1. 3
      cmd/api-headers.go
  2. 2
      cmd/bucket-handlers.go
  3. 4
      cmd/object-handlers-common.go
  4. 6
      cmd/object-handlers.go
  5. 2
      cmd/test-utils_test.go
  6. 2
      staticcheck.conf

@ -76,7 +76,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
// Set Etag if available. // Set Etag if available.
if objInfo.ETag != "" { if objInfo.ETag != "" {
w.Header().Set("ETag", "\""+objInfo.ETag+"\"") w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
} }
if objInfo.ContentType != "" { if objInfo.ContentType != "" {
@ -90,6 +90,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
if !objInfo.Expires.IsZero() { if !objInfo.Expires.IsZero() {
w.Header().Set("Expires", objInfo.Expires.UTC().Format(http.TimeFormat)) w.Header().Set("Expires", objInfo.Expires.UTC().Format(http.TimeFormat))
} }
// Set all other user defined metadata. // Set all other user defined metadata.
for k, v := range objInfo.UserDefined { for k, v := range objInfo.UserDefined {
if hasPrefix(k, ReservedMetadataPrefix) { if hasPrefix(k, ReservedMetadataPrefix) {

@ -669,7 +669,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
} }
location := getObjectLocation(r, globalDomainNames, bucket, object) location := getObjectLocation(r, globalDomainNames, bucket, object)
w.Header().Set("ETag", `"`+objInfo.ETag+`"`) w.Header()["ETag"] = []string{`"` + objInfo.ETag + `"`}
w.Header().Set("Location", location) w.Header().Set("Location", location)
// Notify object created event. // Notify object created event.

@ -71,7 +71,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat)) w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat))
if objInfo.ETag != "" { if objInfo.ETag != "" {
w.Header().Set("ETag", "\""+objInfo.ETag+"\"") w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
} }
} }
// x-amz-copy-source-if-modified-since: Return the object only if it has been modified // x-amz-copy-source-if-modified-since: Return the object only if it has been modified
@ -166,7 +166,7 @@ func checkPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Requ
w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat)) w.Header().Set("Last-Modified", objInfo.ModTime.UTC().Format(http.TimeFormat))
if objInfo.ETag != "" { if objInfo.ETag != "" {
w.Header().Set("ETag", "\""+objInfo.ETag+"\"") w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
} }
} }
// If-Modified-Since : Return the object only if it has been modified since the specified time, // If-Modified-Since : Return the object only if it has been modified since the specified time,

@ -1258,7 +1258,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
} else if hasServerSideEncryptionHeader(r.Header) { } else if hasServerSideEncryptionHeader(r.Header) {
etag = getDecryptedETag(r.Header, objInfo, false) etag = getDecryptedETag(r.Header, objInfo, false)
} }
w.Header().Set("ETag", "\""+etag+"\"") w.Header()["ETag"] = []string{"\"" + etag + "\""}
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
if crypto.IsEncrypted(objInfo.UserDefined) { if crypto.IsEncrypted(objInfo.UserDefined) {
@ -1941,7 +1941,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
} else if isEncrypted { } else if isEncrypted {
etag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header)) etag = tryDecryptETag(objectEncryptionKey, partInfo.ETag, crypto.SSEC.IsRequested(r.Header))
} }
w.Header().Set("ETag", "\""+etag+"\"") w.Header()["ETag"] = []string{"\"" + etag + "\""}
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
} }
@ -2304,7 +2304,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
} }
// Set etag. // Set etag.
w.Header().Set("ETag", "\""+objInfo.ETag+"\"") w.Header()["ETag"] = []string{"\"" + objInfo.ETag + "\""}
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodedSuccessResponse) writeSuccessResponseXML(w, encodedSuccessResponse)

@ -2413,7 +2413,7 @@ func uploadTestObject(t *testing.T, apiRouter http.Handler, creds auth.Credentia
rec = httptest.NewRecorder() rec = httptest.NewRecorder()
apiRouter.ServeHTTP(rec, req) apiRouter.ServeHTTP(rec, req)
checkRespErr(rec, http.StatusOK) checkRespErr(rec, http.StatusOK)
etag := rec.Header().Get("ETag") etag := rec.Header()["ETag"][0]
if etag == "" { if etag == "" {
t.Fatalf("Unexpected empty etag") t.Fatalf("Unexpected empty etag")
} }

@ -1 +1 @@
checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019"] checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019", "-SA1008"]

Loading…
Cancel
Save