diff --git a/backend/archive/zip/zip.go b/backend/archive/zip/zip.go index 6af2d21d7..58e285b8e 100644 --- a/backend/archive/zip/zip.go +++ b/backend/archive/zip/zip.go @@ -163,6 +163,14 @@ func (f *Fs) readZip() (singleObject bool, err error) { dt.AddDir(dir) } else { if remote == "" { + // A file at the root itself can only be the + // archive member f.root points at - with no root + // it is a crafted name for the archive's own + // directory, which can't be a file + if f.root == "" { + skipped++ + continue + } remote = path.Base(f.root) singleObject = true dt = dirtree.New() diff --git a/backend/archive/zip/zip_internal_test.go b/backend/archive/zip/zip_internal_test.go index 5b7a0252d..27ef80cd4 100644 --- a/backend/archive/zip/zip_internal_test.go +++ b/backend/archive/zip/zip_internal_test.go @@ -104,3 +104,27 @@ func TestReadZipRootBoundary(t *testing.T) { remotes := allRemotes(t, f) assert.Equal(t, []string{"a.txt"}, remotes) } + +// A file entry whose name refers to the archive's own root (".", "/", +// "./" or "") must be skipped, not turn the whole archive into a single +// file which hides every other entry. +func TestReadZipRootNamedEntry(t *testing.T) { + ctx := context.Background() + dir := t.TempDir() + name := writeZip(t, dir, "dot.zip", + ".", + "/", + "./", + "", + "good.txt", + ) + + localFs, err := cache.Get(ctx, dir) + require.NoError(t, err) + + f, err := New(ctx, localFs, name, "", "") + require.NoError(t, err) + + remotes := allRemotes(t, f) + assert.Equal(t, []string{"good.txt"}, remotes) +}