Files
URBM/internal/platform/paths.go
T

62 lines
1.7 KiB
Go

package platform
import (
"errors"
"os"
"path/filepath"
"strings"
)
func ValidateRestoreTarget(target, restoreRoot string, inPlace, confirmed bool) (string, error) {
if !filepath.IsAbs(target) {
return "", errors.New("validation: restore target must be absolute")
}
clean := filepath.Clean(target)
if clean == "/" || clean == "/boot" || clean == "/etc" || clean == "/usr" || clean == "/var" {
return "", errors.New("validation: protected system restore target")
}
if !inPlace {
root := filepath.Clean(restoreRoot)
if clean != root && !strings.HasPrefix(clean, root+string(os.PathSeparator)) {
return "", errors.New("validation: staging restore must remain below restoreRoot")
}
if err := rejectSymlinks(root, clean); err != nil {
return "", err
}
} else if !confirmed {
return "", errors.New("validation: in-place restore requires explicit confirmation")
}
if inPlace {
anchor := string(os.PathSeparator) + strings.Split(strings.TrimPrefix(clean, string(os.PathSeparator)), string(os.PathSeparator))[0]
if err := rejectSymlinks(anchor, clean); err != nil {
return "", err
}
}
return clean, nil
}
func rejectSymlinks(root, target string) error {
relative, err := filepath.Rel(root, target)
if err != nil {
return err
}
current := root
for _, component := range strings.Split(relative, string(os.PathSeparator)) {
if component == "." || component == "" {
continue
}
current = filepath.Join(current, component)
info, err := os.Lstat(current)
if errors.Is(err, os.ErrNotExist) {
return nil
}
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
return errors.New("validation: restore target traverses a symlink")
}
}
return nil
}