You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sonic/service/theme/file_scanner.go

100 lines
2.4 KiB
Go

2 years ago
//go:build linux || darwin || freebsd || netbsd || openbsd || dragonfly
package theme
import (
"context"
"io/fs"
"os"
"path/filepath"
"syscall"
"github.com/go-sonic/sonic/model/dto"
"github.com/go-sonic/sonic/util/xerr"
)
type FileScanner interface {
ListThemeFiles(ctx context.Context, themePath string) ([]*dto.ThemeFile, error)
}
func NewFileScanner() FileScanner {
return &fileScannerImpl{}
}
type fileScannerImpl struct{}
func (f *fileScannerImpl) ListThemeFiles(ctx context.Context, themePath string) ([]*dto.ThemeFile, error) {
fileMap := make(map[string]*dto.ThemeFile)
root := &dto.ThemeFile{}
fileMap[themePath] = root
euid, egid := os.Geteuid(), os.Getegid()
err := filepath.Walk(themePath, func(path string, info fs.FileInfo, err error) error {
if os.IsNotExist(err) {
return err
} else if !os.IsPermission(err) && err != nil {
return err
}
themeFile := &dto.ThemeFile{
Name: info.Name(),
IsFile: !info.IsDir(),
Path: path,
2 years ago
Editable: f.isFileEditable(info.Name()) && f.isFileWritable(info.Sys().(*syscall.Stat_t), info.Mode(), euid, egid) && f.isFileReadable(info.Sys().(*syscall.Stat_t), info.Mode(), euid, egid),
2 years ago
}
parentDir, ok := fileMap[filepath.Dir(path)]
if !ok {
return nil
}
parentDir.Node = append(parentDir.Node, themeFile)
fileMap[path] = themeFile
return nil
})
if err != nil {
return nil, xerr.NoType.Wrap(err)
}
return root.Node, nil
}
2 years ago
var catEditSuffix = map[string]struct{}{
".tmpl": {},
".css": {},
".js": {},
".yaml": {},
".yml": {},
".properties": {},
}
func (f *fileScannerImpl) isFileEditable(filePath string) bool {
_, ok := catEditSuffix[filepath.Ext(filePath)]
return ok
}
2 years ago
func (f *fileScannerImpl) isFileWritable(t *syscall.Stat_t, fileMode fs.FileMode, euid, egid int) bool {
if t == nil {
return true
}
if t.Uid == uint32(euid) {
return (uint32(fileMode) & uint32(0o200)) != 0
}
if t.Gid == uint32(egid) {
return (uint32(fileMode) & uint32(0o020)) != 0
} else {
return (uint32(fileMode) & uint32(0o002)) != 0
}
}
func (f *fileScannerImpl) isFileReadable(t *syscall.Stat_t, fileMode fs.FileMode, euid, egid int) bool {
if t == nil {
return true
}
if t.Uid == uint32(euid) {
return (uint32(fileMode) & uint32(0o400)) != 0
}
if t.Gid == uint32(egid) {
return (uint32(fileMode) & uint32(0o040)) != 0
} else {
return (uint32(fileMode) & uint32(0o004)) != 0
}
}