mirror of
https://github.com/filebrowser/filebrowser.git
synced 2026-07-18 00:45:47 +00:00
fix: skip inaccessible children when listing directories (#5958)
This commit is contained in:
parent
1086903c14
commit
7b7ff8ae8f
2 changed files with 144 additions and 2 deletions
|
|
@ -18,6 +18,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -452,8 +453,7 @@ func (i *FileInfo) addSubtitle(fPath string) {
|
|||
}
|
||||
|
||||
func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRes bool) error {
|
||||
afs := &afero.Afero{Fs: i.Fs}
|
||||
dir, err := afs.ReadDir(i.Path)
|
||||
dir, err := readDir(i.Fs, i.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -535,3 +535,56 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool, calcImgRe
|
|||
i.Listing = listing
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDir(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
|
||||
dir, err := afero.ReadDir(afs, dirname)
|
||||
if err == nil {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
dir, fallbackErr := readDirNames(afs, dirname)
|
||||
if fallbackErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func readDirNames(afs afero.Fs, dirname string) ([]os.FileInfo, error) {
|
||||
file, err := afs.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names, err := file.Readdirnames(-1)
|
||||
if closeErr := file.Close(); err == nil && closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
dir := make([]os.FileInfo, 0, len(names))
|
||||
for _, name := range names {
|
||||
fPath := path.Join(dirname, name)
|
||||
info, err := lstatIfPossible(afs, fPath)
|
||||
if err != nil {
|
||||
log.Printf("Skipping inaccessible file %s: %v", fPath, err)
|
||||
continue
|
||||
}
|
||||
|
||||
dir = append(dir, info)
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func lstatIfPossible(afs afero.Fs, name string) (os.FileInfo, error) {
|
||||
if lstaterFs, ok := afs.(afero.Lstater); ok {
|
||||
info, _, err := lstaterFs.LstatIfPossible(name)
|
||||
return info, err
|
||||
}
|
||||
|
||||
return afs.Stat(name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package files
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
|
|
@ -131,3 +132,91 @@ func TestStatRejectsLinkedAncestorEscape(t *testing.T) {
|
|||
t.Fatalf("expected in-scope file to be served, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type allowAllChecker struct{}
|
||||
|
||||
func (allowAllChecker) Check(string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type inaccessibleChildFs struct {
|
||||
afero.Fs
|
||||
child string
|
||||
}
|
||||
|
||||
func (fs inaccessibleChildFs) Open(name string) (afero.File, error) {
|
||||
file, err := fs.Fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if path.Clean(name) == "/" {
|
||||
return inaccessibleChildDir{File: file}, nil
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (fs inaccessibleChildFs) Stat(name string) (os.FileInfo, error) {
|
||||
if path.Clean(name) == fs.child {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
return fs.Fs.Stat(name)
|
||||
}
|
||||
|
||||
func (fs inaccessibleChildFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
|
||||
if path.Clean(name) == fs.child {
|
||||
return nil, false, os.ErrPermission
|
||||
}
|
||||
|
||||
if lstater, ok := fs.Fs.(afero.Lstater); ok {
|
||||
return lstater.LstatIfPossible(name)
|
||||
}
|
||||
|
||||
info, err := fs.Fs.Stat(name)
|
||||
return info, false, err
|
||||
}
|
||||
|
||||
type inaccessibleChildDir struct {
|
||||
afero.File
|
||||
}
|
||||
|
||||
func (dir inaccessibleChildDir) Readdir(int) ([]os.FileInfo, error) {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
func TestReadListingSkipsInaccessibleChildren(t *testing.T) {
|
||||
memFs := afero.NewMemMapFs()
|
||||
for _, dir := range []string{"/media", "/proton-mount"} {
|
||||
if err := memFs.Mkdir(dir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
file, err := NewFileInfo(&FileOptions{
|
||||
Fs: inaccessibleChildFs{Fs: memFs, child: "/proton-mount"},
|
||||
Path: "/",
|
||||
Expand: true,
|
||||
Checker: allowAllChecker{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if file.Listing == nil {
|
||||
t.Fatal("expected root listing")
|
||||
}
|
||||
|
||||
if got := len(file.Items); got != 1 {
|
||||
t.Fatalf("expected one accessible child, got %d", got)
|
||||
}
|
||||
|
||||
if got := file.Items[0].Name; got != "media" {
|
||||
t.Fatalf("expected accessible child to be listed, got %q", got)
|
||||
}
|
||||
|
||||
if got := file.NumDirs; got != 1 {
|
||||
t.Fatalf("expected one listed directory, got %d", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue