fix: perform CopyObject under more conditions (#9879)

- x-amz-storage-class specified CopyObject
  should proceed regardless, its not a precondition
- sourceVersionID is specified CopyObject should
  proceed regardless, its not a precondition
master
Harshavardhana 4 years ago committed by GitHub
parent 53f0cc1340
commit 67062840c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      cmd/object-handlers.go
  2. 51
      cmd/object-handlers_test.go

@ -768,9 +768,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// Note that url.Parse does the unescaping // Note that url.Parse does the unescaping
cpSrcPath = u.Path cpSrcPath = u.Path
} }
if vid == "" {
vid = strings.TrimSpace(r.Header.Get(xhttp.AmzCopySourceVersionID))
}
srcBucket, srcObject := path2BucketObject(cpSrcPath) srcBucket, srcObject := path2BucketObject(cpSrcPath)
// If source object is empty or bucket is empty, reply back invalid copy source. // If source object is empty or bucket is empty, reply back invalid copy source.
@ -884,12 +881,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
var chStorageClass bool var chStorageClass bool
if dstSc != "" { if dstSc != "" {
sc, ok := srcInfo.UserDefined[xhttp.AmzStorageClass]
if (ok && dstSc != sc) || (srcInfo.StorageClass != dstSc) {
chStorageClass = true chStorageClass = true
srcInfo.metadataOnly = false srcInfo.metadataOnly = false
} }
}
var reader io.Reader var reader io.Reader
var length = srcInfo.Size var length = srcInfo.Size
@ -1121,7 +1115,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
// if encryption is enabled we do not need explicit "REPLACE" metadata to // if encryption is enabled we do not need explicit "REPLACE" metadata to
// be enabled as well - this is to allow for key-rotation. // be enabled as well - this is to allow for key-rotation.
if !isDirectiveReplace(r.Header.Get(xhttp.AmzMetadataDirective)) && !isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) && if !isDirectiveReplace(r.Header.Get(xhttp.AmzMetadataDirective)) && !isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) &&
srcInfo.metadataOnly && !crypto.IsEncrypted(srcInfo.UserDefined) { srcInfo.metadataOnly && !crypto.IsEncrypted(srcInfo.UserDefined) && srcOpts.VersionID == "" {
// If x-amz-metadata-directive is not set to REPLACE then we need // If x-amz-metadata-directive is not set to REPLACE then we need
// to error out if source and destination are same. // to error out if source and destination are same.
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopyDest), r.URL, guessIsBrowserReq(r)) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidCopyDest), r.URL, guessIsBrowserReq(r))
@ -1181,6 +1175,12 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
encodedSuccessResponse := encodeResponse(response) encodedSuccessResponse := encodeResponse(response)
setPutObjHeaders(w, objInfo, false) setPutObjHeaders(w, objInfo, false)
// We must not use the http.Header().Set method here because some (broken)
// clients expect the x-amz-copy-source-version-id header key to be literally
// "x-amz-copy-source-version-id"- not in canonicalized form, preserve it.
if srcOpts.VersionID != "" {
w.Header()[strings.ToLower(xhttp.AmzCopySourceVersionID)] = []string{srcOpts.VersionID}
}
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodedSuccessResponse) writeSuccessResponseXML(w, encodedSuccessResponse)

@ -1580,7 +1580,6 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
testCases := []struct { testCases := []struct {
bucketName string bucketName string
copySourceHeader string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL. copySourceHeader string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL.
copySourceVersionID string // data for "X-Amz-Copy-Source-Version-Id" header.
copySourceRange string // data for "X-Amz-Copy-Source-Range" header, contains the byte range offsets of data to be copied. copySourceRange string // data for "X-Amz-Copy-Source-Range" header, contains the byte range offsets of data to be copied.
uploadID string // uploadID of the transaction. uploadID string // uploadID of the transaction.
invalidPartNumber bool // Sets an invalid multipart. invalidPartNumber bool // Sets an invalid multipart.
@ -1768,26 +1767,6 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest, expectedRespStatus: http.StatusBadRequest,
}, },
// Test case - 16, copy part 1 from from newObject1 with null X-Amz-Copy-Source-Version-Id
{
bucketName: bucketName,
uploadID: uploadID,
copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
copySourceVersionID: "null",
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
},
// Test case - 16, copy part 1 from from newObject1 with non null X-Amz-Copy-Source-Version-Id
{
bucketName: bucketName,
uploadID: uploadID,
copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
copySourceVersionID: "17", // invalid id
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
@ -1810,9 +1789,6 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
if testCase.copySourceHeader != "" { if testCase.copySourceHeader != "" {
req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader) req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
} }
if testCase.copySourceVersionID != "" {
req.Header.Set("X-Amz-Copy-Source-Version-Id", testCase.copySourceVersionID)
}
if testCase.copySourceRange != "" { if testCase.copySourceRange != "" {
req.Header.Set("X-Amz-Copy-Source-Range", testCase.copySourceRange) req.Header.Set("X-Amz-Copy-Source-Range", testCase.copySourceRange)
} }
@ -1933,7 +1909,6 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
bucketName string bucketName string
newObjectName string // name of the newly copied object. newObjectName string // name of the newly copied object.
copySourceHeader string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL. copySourceHeader string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL.
copySourceVersionID string // data for "X-Amz-Copy-Source-Version-Id" header.
copyModifiedHeader string // data for "X-Amz-Copy-Source-If-Modified-Since" header copyModifiedHeader string // data for "X-Amz-Copy-Source-If-Modified-Since" header
copyUnmodifiedHeader string // data for "X-Amz-Copy-Source-If-Unmodified-Since" header copyUnmodifiedHeader string // data for "X-Amz-Copy-Source-If-Unmodified-Since" header
metadataGarbage bool metadataGarbage bool
@ -2162,26 +2137,6 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest, expectedRespStatus: http.StatusBadRequest,
}, },
// Test case - 19, copy metadata from newObject1 with null X-Amz-Copy-Source-Version-Id
{
bucketName: bucketName,
newObjectName: "newObject1",
copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
copySourceVersionID: "null",
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusOK,
},
// Test case - 20, copy metadata from newObject1 with non null X-Amz-Copy-Source-Version-Id
{
bucketName: bucketName,
newObjectName: "newObject1",
copySourceHeader: url.QueryEscape(SlashSeparator + bucketName + SlashSeparator + objectName),
copySourceVersionID: "17",
accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey,
expectedRespStatus: http.StatusBadRequest,
},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
@ -2200,9 +2155,6 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
if testCase.copySourceHeader != "" { if testCase.copySourceHeader != "" {
req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader) req.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
} }
if testCase.copySourceVersionID != "" {
req.Header.Set("X-Amz-Copy-Source-Version-Id", testCase.copySourceVersionID)
}
if testCase.copyModifiedHeader != "" { if testCase.copyModifiedHeader != "" {
req.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader) req.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader)
} }
@ -2259,9 +2211,6 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
if testCase.copySourceHeader != "" { if testCase.copySourceHeader != "" {
reqV2.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader) reqV2.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
} }
if testCase.copySourceVersionID != "" {
reqV2.Header.Set("X-Amz-Copy-Source-Version-Id", testCase.copySourceVersionID)
}
if testCase.copyModifiedHeader != "" { if testCase.copyModifiedHeader != "" {
reqV2.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader) reqV2.Header.Set("X-Amz-Copy-Source-If-Modified-Since", testCase.copyModifiedHeader)
} }

Loading…
Cancel
Save