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) { clean, err := validateBrowsePath(path) 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 { if entry.IsDir() && entry.Type()&os.ModeSymlink == 0 { result = append(result, DirectoryEntry{Name: entry.Name(), Path: filepath.Join(clean, entry.Name())}) } } 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 { result := make([]DirectoryEntry, 0, len(browseRoots)) for _, root := range browseRoots { 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) { 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 browseRoots { 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 browseRoots { if resolved == root || strings.HasPrefix(resolved, root+string(os.PathSeparator)) { return resolved, nil } } return "", errors.New("validation: browse path resolves outside allowed Unraid storage roots") }