From 68eab60564a936716aa4df0aefd0929bb5c2902b Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 2 Sep 2026 15:41:51 +0100 Subject: [PATCH] archive: fix corrupt listings when listing a zip directory more than once The zip archiver handed out its cached directory tree directly. Any caller which filters a listing in place (as the core listing code does) altered the cache, so later listings of the same directory could be corrupted. Return a copy of the cached listing instead. --- backend/archive/zip/zip.go | 4 +++- backend/archive/zip/zip_internal_test.go | 27 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/archive/zip/zip.go b/backend/archive/zip/zip.go index 58e285b8e..6349807b1 100644 --- a/backend/archive/zip/zip.go +++ b/backend/archive/zip/zip.go @@ -9,6 +9,7 @@ import ( "io" "os" "path" + "slices" "strings" "time" @@ -217,7 +218,8 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e return nil, fs.ErrorDirNotFound } fs.Debugf(f, "dir=%q, entries=%v", dir, entries) - return entries, nil + // Return a copy as callers may filter the listing in place + return slices.Clone(entries), nil } // NewObject finds the Object at remote. diff --git a/backend/archive/zip/zip_internal_test.go b/backend/archive/zip/zip_internal_test.go index 27ef80cd4..d13bb9140 100644 --- a/backend/archive/zip/zip_internal_test.go +++ b/backend/archive/zip/zip_internal_test.go @@ -128,3 +128,30 @@ func TestReadZipRootNamedEntry(t *testing.T) { remotes := allRemotes(t, f) assert.Equal(t, []string{"good.txt"}, remotes) } + +// Listings are served from a cache which must survive callers +// filtering the returned slice in place, as fs/list does. +func TestListCacheNotAliased(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + name := writeZip(t, dir, "test.zip", "a.txt", "b.txt", "c.txt") + + localFs, err := cache.Get(ctx, dir) + require.NoError(t, err) + f, err := New(ctx, localFs, name, "", "") + require.NoError(t, err) + + entries, err := f.List(ctx, "") + require.NoError(t, err) + require.Len(t, entries, 3) + // Compact in place, dropping the first entry + copy(entries, entries[1:]) + + entries, err = f.List(ctx, "") + require.NoError(t, err) + var remotes []string + for _, entry := range entries { + remotes = append(remotes, entry.Remote()) + } + assert.Equal(t, []string{"a.txt", "b.txt", "c.txt"}, remotes) +}