From c350f8ebbfbdb0cc8b82857977d12fbc2d96aada Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Sat, 19 Sep 2026 18:22:09 +0100 Subject: [PATCH] serve s3: forget the metadata of deleted objects The metadata of every object uploaded was kept in memory for as long as the server ran, even after the object was deleted, so it grew without limit and the metadata of a deleted object reappeared on any object later created at the same key other than through serve s3. Deleting an object now forgets its metadata. --- cmd/serve/s3/backend.go | 1 + cmd/serve/s3/put_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/cmd/serve/s3/backend.go b/cmd/serve/s3/backend.go index 6e5eb92bf..3bcb10a01 100644 --- a/cmd/serve/s3/backend.go +++ b/cmd/serve/s3/backend.go @@ -491,6 +491,7 @@ func (b *s3Backend) deleteObject(ctx context.Context, bucketName, objectName str if err := _vfs.Remove(fp); err != nil && !os.IsNotExist(err) { return err } + b.meta.Delete(fp) // FIXME: unsafe operation rmdirRecursive(fp, _vfs) diff --git a/cmd/serve/s3/put_test.go b/cmd/serve/s3/put_test.go index 242d028b5..6dbddfa57 100644 --- a/cmd/serve/s3/put_test.go +++ b/cmd/serve/s3/put_test.go @@ -19,6 +19,7 @@ import ( "github.com/rclone/gofakes3" "github.com/rclone/rclone/cmd/serve/proxy" "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/fstest" "github.com/rclone/rclone/lib/random" "github.com/rclone/rclone/vfs" @@ -222,3 +223,34 @@ func TestPutObjectFailureCached(t *testing.T) { } } } + +// TestDeleteObjectForgetsMetadata checks that deleting an object forgets +// its metadata, so it isn't kept in memory and doesn't reappear on a new +// object at the same key. +func TestDeleteObjectForgetsMetadata(t *testing.T) { + b, f, bucket := newPutTestBackend(t, "", nil) + ctx := context.Background() + const key = "meta.txt" + + meta := map[string]string{"X-Amz-Meta-Colour": "blue"} + _, err := b.PutObject(ctx, bucket, key, meta, bytes.NewReader([]byte("one")), 3) + require.NoError(t, err) + obj, err := b.HeadObject(ctx, bucket, key) + require.NoError(t, err) + assert.Equal(t, "blue", obj.Metadata["X-Amz-Meta-Colour"]) + + _, err = b.DeleteObject(ctx, bucket, key) + require.NoError(t, err) + + // Recreate the object without going through serve s3. + src := object.NewStaticObjectInfo(path.Join(bucket, key), time.Now(), 3, true, nil, nil) + _, err = f.Put(ctx, bytes.NewReader([]byte("two")), src) + require.NoError(t, err) + _vfs, err := b.s.getVFS(ctx) + require.NoError(t, err) + b.forgetPath(_vfs, path.Join(bucket, key)) + + obj, err = b.HeadObject(ctx, bucket, key) + require.NoError(t, err) + assert.NotContains(t, obj.Metadata, "X-Amz-Meta-Colour", "metadata of the deleted object reappeared") +}