aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_object_handlers_copy.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3api_object_handlers_copy.go')
-rw-r--r--weed/s3api/s3api_object_handlers_copy.go31
1 files changed, 27 insertions, 4 deletions
diff --git a/weed/s3api/s3api_object_handlers_copy.go b/weed/s3api/s3api_object_handlers_copy.go
index 91da98a0e..4dd31c8ce 100644
--- a/weed/s3api/s3api_object_handlers_copy.go
+++ b/weed/s3api/s3api_object_handlers_copy.go
@@ -230,10 +230,11 @@ func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request
}
}
- // Check if destination bucket has versioning configured
- dstVersioningConfigured, err := s3a.isVersioningConfigured(dstBucket)
+ // Check if destination bucket has versioning enabled
+ // Only create versions if versioning is explicitly "Enabled", not "Suspended" or unconfigured
+ dstVersioningState, err := s3a.getVersioningState(dstBucket)
if err != nil {
- glog.Errorf("Error checking versioning status for destination bucket %s: %v", dstBucket, err)
+ glog.Errorf("Error checking versioning state for destination bucket %s: %v", dstBucket, err)
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
}
@@ -241,7 +242,7 @@ func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request
var dstVersionId string
var etag string
- if dstVersioningConfigured {
+ if shouldCreateVersionForCopy(dstVersioningState) {
// For versioned destination, create a new version
dstVersionId = generateVersionId()
glog.V(2).Infof("CopyObjectHandler: creating version %s for destination %s/%s", dstVersionId, dstBucket, dstObject)
@@ -294,6 +295,9 @@ func (s3a *S3ApiServer) CopyObjectHandler(w http.ResponseWriter, r *http.Request
w.Header().Set("x-amz-version-id", dstVersionId)
} else {
// For non-versioned destination, use regular copy
+ // Remove any versioning-related metadata from source that shouldn't carry over
+ cleanupVersioningMetadata(dstEntry.Extended)
+
dstPath := util.FullPath(fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, dstBucket, dstObject))
dstDir, dstName := dstPath.DirAndName()
@@ -2327,6 +2331,25 @@ func (ctx *EncryptionHeaderContext) shouldSkipEncryptedToUnencryptedHeader() boo
return hasSourceEncryption && !hasDestinationEncryption && isAnyEncryptionHeader
}
+// cleanupVersioningMetadata removes versioning-related metadata from Extended attributes
+// when copying to non-versioned or suspended-versioning buckets.
+// This prevents objects in non-versioned buckets from carrying invalid versioning metadata.
+// It also removes the source ETag to prevent metadata inconsistency, as a new ETag will be
+// calculated for the destination object.
+func cleanupVersioningMetadata(metadata map[string][]byte) {
+ delete(metadata, s3_constants.ExtVersionIdKey)
+ delete(metadata, s3_constants.ExtDeleteMarkerKey)
+ delete(metadata, s3_constants.ExtIsLatestKey)
+ delete(metadata, s3_constants.ExtETagKey)
+}
+
+// shouldCreateVersionForCopy determines whether a version should be created during a copy operation
+// based on the destination bucket's versioning state.
+// Returns true only if versioning is explicitly "Enabled", not "Suspended" or unconfigured.
+func shouldCreateVersionForCopy(versioningState string) bool {
+ return versioningState == s3_constants.VersioningEnabled
+}
+
// shouldSkipEncryptionHeader determines if a header should be skipped when copying extended attributes
// based on the source and destination encryption types. This consolidates the repetitive logic for
// filtering encryption-related headers during copy operations.