mirror of https://github.com/go-sonic/sonic.git
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.
100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
//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,
|
|
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),
|
|
}
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|