diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index b5c9b56da..059030c9c 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -1283,7 +1283,7 @@ func (s3a *S3ApiServer) setResponseHeaders(w http.ResponseWriter, entry *filer_p // Support backward compatibility: migrate old non-canonical format to canonical format // OLD: "x-amz-meta-foo" → NEW: "X-Amz-Meta-foo" (preserving suffix case) headerKey := k - if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") && !strings.HasPrefix(k, s3_constants.AmzUserMetaPrefix) { + if len(k) >= 11 && strings.EqualFold(k[:11], "x-amz-meta-") && !strings.HasPrefix(k, s3_constants.AmzUserMetaPrefix) { // Old format detected - normalize to canonical prefix, preserve suffix case suffix := k[len("x-amz-meta-"):] headerKey = s3_constants.AmzUserMetaPrefix + suffix diff --git a/weed/s3api/s3api_object_handlers_copy.go b/weed/s3api/s3api_object_handlers_copy.go index e52fbc71d..4127588ca 100644 --- a/weed/s3api/s3api_object_handlers_copy.go +++ b/weed/s3api/s3api_object_handlers_copy.go @@ -709,12 +709,22 @@ func processMetadataBytes(reqHeader http.Header, existing map[string][]byte, rep } } else { // Copy existing metadata as-is + // Note: Metadata should already be normalized during storage (X-Amz-Meta-*), + // but we handle legacy non-canonical formats for backward compatibility for k, v := range existing { if strings.HasPrefix(k, s3_constants.AmzUserMetaPrefix) { + // Already in canonical format metadata[k] = v - } else if strings.HasPrefix(strings.ToLower(k), "x-amz-meta-") { - // Backward compatibility: old format (x-amz-meta-foo) - // Preserve the original key from storage for backward compatibility + } else if len(k) >= 11 && strings.EqualFold(k[:11], "x-amz-meta-") { + // Backward compatibility: old non-canonical format (e.g., x-amz-meta-foo) + // This should be rare; log if we see it to track migration progress + if glog.V(4) { + glog.Infof("Found legacy user metadata key format: %q", k) + } + // Preserve original key to avoid breaking existing data + if _, exists := metadata[k]; exists { + glog.Warningf("User metadata key collision during copy: %q already exists", k) + } metadata[k] = v } }