83 lines
2.9 KiB
Go
83 lines
2.9 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func validateScopedTarget(root, target, policy string, requireDir bool) (scopedDir, error) {
|
|
cleanRoot := strings.TrimSpace(root)
|
|
cleanTarget := strings.TrimSpace(target)
|
|
if cleanRoot == "" {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: root path is required", policy)
|
|
}
|
|
if cleanTarget == "" {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: target path is required", policy)
|
|
}
|
|
|
|
rootAbs, err := filepath.Abs(cleanRoot)
|
|
if err != nil {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: resolve root %q: %w", policy, cleanRoot, err)
|
|
}
|
|
targetAbs, err := filepath.Abs(cleanTarget)
|
|
if err != nil {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: resolve target %q: %w", policy, cleanTarget, err)
|
|
}
|
|
|
|
rel, err := filepath.Rel(rootAbs, targetAbs)
|
|
if err != nil {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: relative path from %q to %q: %w", policy, rootAbs, targetAbs, err)
|
|
}
|
|
if rel == "." {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: refusing to delete root directory %q", policy, rootAbs)
|
|
}
|
|
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: refusing to delete path outside root: root=%q target=%q", policy, rootAbs, targetAbs)
|
|
}
|
|
|
|
info, err := os.Lstat(targetAbs)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return scopedDir{RootAbs: rootAbs, TargetAbs: targetAbs, Exists: false}, nil
|
|
}
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: stat target %q: %w", policy, targetAbs, err)
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: refusing to delete symlink path %q", policy, targetAbs)
|
|
}
|
|
if requireDir && !info.IsDir() {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: target %q is not a directory", policy, targetAbs)
|
|
}
|
|
if !requireDir && info.IsDir() {
|
|
return scopedDir{}, fmt.Errorf("cleanup policy %s: target %q is a directory", policy, targetAbs)
|
|
}
|
|
return scopedDir{RootAbs: rootAbs, TargetAbs: targetAbs, Exists: true}, nil
|
|
}
|
|
|
|
func validateCleanRoot(root, policy string) (string, bool, error) {
|
|
cleanRoot := strings.TrimSpace(root)
|
|
if cleanRoot == "" {
|
|
return "", false, fmt.Errorf("cleanup policy %s: root path is required", policy)
|
|
}
|
|
rootAbs, err := filepath.Abs(cleanRoot)
|
|
if err != nil {
|
|
return "", false, fmt.Errorf("cleanup policy %s: resolve root %q: %w", policy, cleanRoot, err)
|
|
}
|
|
info, err := os.Lstat(rootAbs)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return rootAbs, false, nil
|
|
}
|
|
return "", false, fmt.Errorf("cleanup policy %s: stat root %q: %w", policy, rootAbs, err)
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
return "", false, fmt.Errorf("cleanup policy %s: refusing to clean symlink root %q", policy, rootAbs)
|
|
}
|
|
if !info.IsDir() {
|
|
return "", false, fmt.Errorf("cleanup policy %s: root %q is not a directory", policy, rootAbs)
|
|
}
|
|
return rootAbs, true, nil
|
|
}
|