diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 48c6728f43..2041f28bb1 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -56,14 +56,7 @@ func Local(name, base string, sub ...string) *Layer { panic(fmt.Sprintf("Unable to get absolute path for %q: %v", base, err)) } root := util.FilePathJoinAbs(base, sub...) - fsRoot, err := os.OpenRoot(root) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return nil - } - panic(fmt.Sprintf("Unable to open layer %q", err)) - } - return &Layer{name: name, fs: fsRoot.FS(), localPath: root} + return &Layer{name: name, fs: os.DirFS(root), localPath: root} } // Bindata returns a new Layer with the given name, it serves files from the given bindata asset. @@ -80,7 +73,7 @@ type LayeredFS struct { // Layered returns a new LayeredFS with the given layers. The first layer is the top layer. func Layered(layers ...*Layer) *LayeredFS { - return &LayeredFS{layers: slices.DeleteFunc(layers, func(layer *Layer) bool { return layer == nil })} + return &LayeredFS{layers: layers} } // Open opens the named file. The caller is responsible for closing the file. diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go index 87d1f92b00..76eeb61d83 100644 --- a/modules/assetfs/layered_test.go +++ b/modules/assetfs/layered_test.go @@ -1,4 +1,5 @@ // Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package assetfs @@ -108,3 +109,30 @@ func TestLayered(t *testing.T) { assert.Equal(t, "l1", assets.GetFileLayerName("f1")) assert.Equal(t, "l2", assets.GetFileLayerName("f2")) } + +// Allow layers to read symlink outside the layer root. +func TestLayeredSymlink(t *testing.T) { + dir := t.TempDir() + dirl1 := filepath.Join(dir, "l1") + require.NoError(t, os.MkdirAll(dirl1, 0o755)) + + // Open layer in dir/l1 + layer := Local("l1", dirl1) + + // Create a file in dir/outside + fileContents := []byte("I am outside the layer") + require.NoError(t, os.WriteFile(filepath.Join(dir, "outside"), fileContents, 0o600)) + // Symlink dir/l1/outside to dir/outside + require.NoError(t, os.Symlink(filepath.Join(dir, "outside"), filepath.Join(dirl1, "outside"))) + + // Open dir/l1/outside. + f, err := layer.Open("outside") + require.NoError(t, err) + defer f.Close() + + // Confirm it contains the output of dir/outside + contents, err := io.ReadAll(f) + require.NoError(t, err) + + assert.Equal(t, fileContents, contents) +}