Skip to content

Commit e99238e

Browse files
committed
Fix "gitfs"'s "Mode()" function and transparently follow symlinks
1 parent 8eb28cd commit e99238e

File tree

1 file changed

+52
-3
lines changed

1 file changed

+52
-3
lines changed

pkg/gitfs/fs.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package gitfs
22

33
import (
4+
"fmt"
45
"io"
56
"io/fs"
67
"path"
8+
"strings"
79
"time"
810

911
goGit "github.com/go-git/go-git/v5"
@@ -31,17 +33,57 @@ type gitFS struct {
3133
commit *goGitPlumbingObject.Commit
3234
}
3335

36+
// apparently symlinks in "io/fs" are still a big TODO (https://github.com/golang/go/issues/49580, https://github.com/golang/go/issues/45470, etc related issues); all the existing interfaces assume symlinks don't exist
37+
//
38+
// if the File object passed to this function represents a symlink, this returns the (resolved) path that should be looked up instead; only relative symlinks are supported (and attempts to escape the repository with too many "../" *should* result in an error -- this is a convenience/sanity check, not a security boundary; subset of https://pkg.go.dev/io/fs#ValidPath)
39+
//
40+
// otherwise, it will return the empty string and nil
41+
func resolveSymlink(f *goGitPlumbingObject.File) (target string, err error) {
42+
if f.Mode != goGitPlumbingFileMode.Symlink {
43+
return "", nil
44+
}
45+
46+
target, err = f.Contents()
47+
if err != nil {
48+
return "", err
49+
}
50+
51+
if target == "" {
52+
return "", fmt.Errorf("unexpected: empty symlink %q", f.Name)
53+
}
54+
55+
if path.IsAbs(target) {
56+
return "", fmt.Errorf("unsupported: %q is an absolute symlink (%q)", f.Name, target)
57+
}
58+
59+
target = path.Join(path.Dir(f.Name), target)
60+
61+
if strings.HasPrefix(target, "../") {
62+
return "", fmt.Errorf("unsupported: %q is a relative symlink outside the tree (%q)", f.Name, target)
63+
}
64+
65+
return target, nil
66+
}
67+
3468
// https://pkg.go.dev/io/fs#FS
3569
func (fs gitFS) Open(name string) (fs.File, error) {
3670
f, err := fs.commit.File(name)
3771
if err != nil {
3872
// TODO if it's file-not-found, we need to check whether it's a directory
3973
return nil, err
4074
}
75+
76+
if target, err := resolveSymlink(f); err != nil {
77+
return nil, err
78+
} else if target != "" {
79+
return fs.Open(target)
80+
}
81+
4182
reader, err := f.Reader()
4283
if err != nil {
4384
return nil, err
4485
}
86+
4587
return gitFSFile{
4688
stat: gitFSFileInfo{
4789
file: f,
@@ -56,6 +98,13 @@ func (fs gitFS) Stat(name string) (fs.FileInfo, error) {
5698
if err != nil {
5799
return nil, err
58100
}
101+
102+
if target, err := resolveSymlink(f); err != nil {
103+
return nil, err
104+
} else if target != "" {
105+
return fs.Stat(target)
106+
}
107+
59108
return gitFSFileInfo{
60109
file: f,
61110
}, nil
@@ -98,13 +147,13 @@ func (fi gitFSFileInfo) Mode() fs.FileMode {
98147
case goGitPlumbingFileMode.Regular:
99148
return 0644
100149
case goGitPlumbingFileMode.Symlink:
101-
return 0644 & fs.ModeSymlink
150+
return 0644 | fs.ModeSymlink
102151
case goGitPlumbingFileMode.Executable:
103152
return 0755
104153
case goGitPlumbingFileMode.Dir:
105-
return 0755 & fs.ModeDir
154+
return 0755 | fs.ModeDir
106155
}
107-
return 0 & fs.ModeIrregular // TODO what to do for files whose types we don't support? 😬
156+
return 0 | fs.ModeIrregular // TODO what to do for files whose types we don't support? 😬
108157
}
109158

110159
// modification time

0 commit comments

Comments
 (0)