mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-11-13 08:06:06 +00:00
211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
package files
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
)
|
|
|
|
type fileDeletePolicy struct {
|
|
dataDir, trashRoot string
|
|
fixedRoots []string
|
|
}
|
|
type deletePathViews struct {
|
|
lexical, physical string
|
|
info os.FileInfo
|
|
}
|
|
|
|
func CheckFileDeletePaths(paths ...string) error {
|
|
return (fileDeletePolicy{dataDir: global.Dir.DataDir, trashRoot: global.Dir.RecycleBinDir, fixedRoots: protectedPaths}).check(paths...)
|
|
}
|
|
|
|
func (policy fileDeletePolicy) check(paths ...string) error {
|
|
var fixed []deletePathViews
|
|
for _, root := range policy.fixedRoots {
|
|
view, err := newDeletePathViews(root)
|
|
if err != nil {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
fixed = append(fixed, view)
|
|
}
|
|
var data, trash *deletePathViews
|
|
for _, setting := range []struct {
|
|
path string
|
|
destination **deletePathViews
|
|
}{{policy.dataDir, &data}, {policy.trashRoot, &trash}} {
|
|
if strings.TrimSpace(setting.path) == "" {
|
|
continue
|
|
}
|
|
view, err := newDeletePathViews(setting.path)
|
|
if err != nil {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
*setting.destination = &view
|
|
}
|
|
for _, path := range paths {
|
|
target, err := newDeletePathViews(path)
|
|
if err != nil {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
for _, root := range fixed {
|
|
if sameDeleteRoot(target, root) {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
}
|
|
if data != nil && deleteTargetContainsRoot(target, *data) {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
if strings.EqualFold(filepath.Base(target.lexical), ".1panel_clash") || strings.EqualFold(filepath.Base(target.physical), ".1panel_clash") || trash != nil && sameDeleteRoot(target, *trash) {
|
|
return buserr.New("ErrPathNotDelete")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newDeletePathViews(value string) (deletePathViews, error) {
|
|
if strings.TrimSpace(value) == "" || strings.IndexByte(value, 0) >= 0 {
|
|
return deletePathViews{}, syscall.EINVAL
|
|
}
|
|
absolute, err := filepath.Abs(value)
|
|
if err != nil {
|
|
return deletePathViews{}, err
|
|
}
|
|
physicalInput := value
|
|
if !filepath.IsAbs(physicalInput) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return deletePathViews{}, err
|
|
}
|
|
physicalInput = cwd + string(filepath.Separator) + physicalInput
|
|
}
|
|
physical, err := filepath.EvalSymlinks(physicalInput)
|
|
if os.IsNotExist(err) {
|
|
physical, err = resolveMissingDeletePath(value)
|
|
}
|
|
if err != nil {
|
|
return deletePathViews{}, err
|
|
}
|
|
physical, err = filepath.Abs(physical)
|
|
if err != nil {
|
|
return deletePathViews{}, err
|
|
}
|
|
info, err := os.Stat(physical)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return deletePathViews{}, err
|
|
}
|
|
return deletePathViews{lexical: absolute, physical: physical, info: info}, nil
|
|
}
|
|
|
|
func sameDeleteRoot(target, root deletePathViews) bool {
|
|
for _, candidate := range []string{target.lexical, target.physical} {
|
|
for _, protected := range []string{root.lexical, root.physical} {
|
|
if candidate == protected {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return sameDeleteDirectory(target.info, root.info)
|
|
}
|
|
func sameDeleteDirectory(a, b os.FileInfo) bool {
|
|
return a != nil && b != nil && a.IsDir() && b.IsDir() && os.SameFile(a, b)
|
|
}
|
|
|
|
func deleteTargetContainsRoot(target, root deletePathViews) bool {
|
|
if sameDeleteRoot(target, root) {
|
|
return true
|
|
}
|
|
for _, candidate := range []string{target.lexical, target.physical} {
|
|
for _, protected := range []string{root.lexical, root.physical} {
|
|
relative, err := filepath.Rel(candidate, protected)
|
|
if err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if target.info != nil && target.info.IsDir() {
|
|
for ancestor := root.physical; ; ancestor = filepath.Dir(ancestor) {
|
|
info, err := os.Stat(ancestor)
|
|
if err == nil && sameDeleteDirectory(target.info, info) {
|
|
return true
|
|
}
|
|
if filepath.Dir(ancestor) == ancestor {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func resolveMissingDeletePath(value string) (string, error) {
|
|
if !filepath.IsAbs(value) {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
value = cwd + string(filepath.Separator) + value
|
|
}
|
|
pending := strings.Split(filepath.ToSlash(value), "/")
|
|
current := string(filepath.Separator)
|
|
var currentInfo os.FileInfo
|
|
missing, links := false, 0
|
|
for len(pending) > 0 {
|
|
component := pending[0]
|
|
pending = pending[1:]
|
|
if component == "" {
|
|
continue
|
|
}
|
|
if currentInfo != nil && !currentInfo.IsDir() {
|
|
return "", syscall.ENOTDIR
|
|
}
|
|
if component == "." {
|
|
continue
|
|
}
|
|
if component == ".." {
|
|
if missing {
|
|
return "", os.ErrNotExist
|
|
}
|
|
current = filepath.Dir(current)
|
|
currentInfo = nil
|
|
continue
|
|
}
|
|
next := filepath.Join(current, component)
|
|
if missing {
|
|
current = next
|
|
continue
|
|
}
|
|
info, err := os.Lstat(next)
|
|
if os.IsNotExist(err) {
|
|
missing = true
|
|
current = next
|
|
currentInfo = nil
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
links++
|
|
if links > 40 {
|
|
return "", syscall.ELOOP
|
|
}
|
|
link, err := os.Readlink(next)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if filepath.IsAbs(link) {
|
|
current = string(filepath.Separator)
|
|
}
|
|
currentInfo = nil
|
|
pending = append(strings.Split(filepath.ToSlash(link), "/"), pending...)
|
|
continue
|
|
}
|
|
current = next
|
|
currentInfo = info
|
|
}
|
|
return current, nil
|
|
}
|