98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
package platform
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type DirectoryEntry struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
var browseRoots = []string{"/mnt/user", "/mnt/disks", "/mnt/remotes", "/boot"}
|
|
|
|
func BrowseDirectories(path string) ([]DirectoryEntry, error) {
|
|
mounts, _ := mountedPaths()
|
|
return browseDirectories(path, storageBrowseRoots(mounts))
|
|
}
|
|
|
|
func browseDirectories(path string, roots []string) ([]DirectoryEntry, error) {
|
|
clean, err := validateBrowsePathWithRoots(path, roots)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
entries, err := os.ReadDir(clean)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result := make([]DirectoryEntry, 0, len(entries))
|
|
for _, entry := range entries {
|
|
child := filepath.Join(clean, entry.Name())
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
if _, err := validateBrowsePathWithRoots(child, roots); err != nil {
|
|
continue
|
|
}
|
|
info, err := os.Stat(child)
|
|
if err != nil || !info.IsDir() {
|
|
continue
|
|
}
|
|
} else if !entry.IsDir() {
|
|
continue
|
|
}
|
|
result = append(result, DirectoryEntry{Name: entry.Name(), Path: child})
|
|
}
|
|
sort.Slice(result, func(i, j int) bool { return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name) })
|
|
return result, nil
|
|
}
|
|
|
|
func BrowseRoots() []DirectoryEntry {
|
|
mounts, _ := mountedPaths()
|
|
roots := storageBrowseRoots(mounts)
|
|
result := make([]DirectoryEntry, 0, len(roots))
|
|
for _, root := range roots {
|
|
if info, err := os.Stat(root); err == nil && info.IsDir() {
|
|
result = append(result, DirectoryEntry{Name: root, Path: root})
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func validateBrowsePath(path string) (string, error) {
|
|
mounts, _ := mountedPaths()
|
|
return validateBrowsePathWithRoots(path, storageBrowseRoots(mounts))
|
|
}
|
|
|
|
func validateBrowsePathWithRoots(path string, roots []string) (string, error) {
|
|
if path == "" || path == "/" {
|
|
return "", errors.New("validation: select an allowed browse root")
|
|
}
|
|
clean := filepath.Clean(path)
|
|
if !filepath.IsAbs(clean) {
|
|
return "", errors.New("validation: browse path must be absolute")
|
|
}
|
|
allowed := false
|
|
for _, root := range roots {
|
|
if clean == root || strings.HasPrefix(clean, root+string(os.PathSeparator)) {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
if !allowed {
|
|
return "", errors.New("validation: browse path is outside allowed Unraid storage roots")
|
|
}
|
|
resolved, err := filepath.EvalSymlinks(clean)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for _, root := range roots {
|
|
if resolved == root || strings.HasPrefix(resolved, root+string(os.PathSeparator)) {
|
|
return clean, nil
|
|
}
|
|
}
|
|
return "", errors.New("validation: browse path resolves outside allowed Unraid storage roots")
|
|
}
|