Files
seaweedfs/weed/s3api/s3api_object_handlers_copy_collection_test.go
Chris Lu 83b7ea5e7b fix(s3): keep server-side copy data in the bucket collection (#9607)
* fix(s3): keep server-side copy data in the bucket collection

UploadPartCopy and SSE-C CopyObject assigned destination volumes against
r.URL.Path, the S3 request URI. The filer derives a bucket's collection
only when the assign path sits under its buckets folder, so an S3 URI
routed copied bytes to the default collection instead of the destination
bucket's. Assign against the destination's real filer path.

* refactor(s3): centralize copy-part path and thread dstPath into SSE-C copy

Extract copyPartLocation so the fast path and writeEmptyCopyPart share one
definition of the .uploads/<id>/<n>_copy.part location. Pass the destination
filer path into copyChunksWithSSEC instead of re-deriving it from the request,
and thread it through key rotation so re-encrypt copies also assign in the
destination bucket's collection.
2026-05-21 09:35:42 -07:00

46 lines
2.0 KiB
Go

package s3api
import (
"fmt"
"testing"
"github.com/seaweedfs/seaweedfs/weed/filer"
"github.com/seaweedfs/seaweedfs/weed/util"
)
// TestCopyDestinationPathResolvesBucketCollection guards against server-side
// copies routing data into the default collection.
//
// The filer maps a write to a bucket's collection only when the AssignVolume
// Path sits under its buckets folder (/buckets/<bucket>/...). UploadPartCopy
// and SSE-C CopyObject used to assign destination volumes against r.URL.Path,
// the S3 request URI (e.g. /bucket/key), which never has that prefix; the
// copied bytes therefore landed in the default collection instead of the
// destination bucket's. The handlers must assign against the real filer path
// of the destination.
func TestCopyDestinationPathResolvesBucketCollection(t *testing.T) {
const bucket = "docker-registry"
s3a := &S3ApiServer{option: &S3ApiServerOption{BucketsPath: "/buckets"}}
f := &filer.Filer{DirBucketsPath: "/buckets"}
// The S3 request URI is not a filer path: the filer cannot derive the
// destination bucket from it. This is the shape that caused the leak.
if got := f.DetectBucket(util.FullPath("/" + bucket + "/blobs/data")); got != "" {
t.Fatalf("S3 request URI unexpectedly mapped to collection %q; the test no longer reproduces the bug", got)
}
// UploadPartCopy assigns against the destination part path under .uploads.
uploadDir, partName := s3a.copyPartLocation(bucket, "uploadid", 1)
partPath := uploadDir + "/" + partName
if got := f.DetectBucket(util.FullPath(partPath)); got != bucket {
t.Fatalf("UploadPartCopy dst path %q resolved to collection %q, want %q", partPath, got, bucket)
}
// CopyObject (including the SSE-C paths) assigns against the destination
// object path.
objPath := fmt.Sprintf("%s/%s", s3a.bucketDir(bucket), "blobs/data")
if got := f.DetectBucket(util.FullPath(objPath)); got != bucket {
t.Fatalf("CopyObject dst path %q resolved to collection %q, want %q", objPath, got, bucket)
}
}