package api import ( "errors" "net/http" "net/url" "os" "path/filepath" "sort" "strings" ) func (s *Server) handleSources(w http.ResponseWriter, r *http.Request) { relPath, err := normalizeSourcePath(r.URL.Query().Get("path")) if err != nil { jsonErr(w, http.StatusBadRequest, err.Error()) return } absPath := s.deps.MediaPath if relPath != "" { absPath = filepath.Join(absPath, relPath) } entries, err := os.ReadDir(absPath) if err != nil { jsonOK(w, map[string]any{"path": relPath, "items": []map[string]string{}}) return } type item struct { Name string `json:"name"` Path string `json:"path"` } var items []item for _, e := range entries { if !e.IsDir() || strings.HasPrefix(e.Name(), ".") { continue } childPath := e.Name() if relPath != "" { childPath = filepath.Join(relPath, childPath) } items = append(items, item{ Name: e.Name(), Path: filepath.ToSlash(childPath), }) } sort.Slice(items, func(i, j int) bool { return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name) }) jsonOK(w, map[string]any{ "path": relPath, "items": items, }) } func normalizeSourcePath(raw string) (string, error) { raw, _ = url.QueryUnescape(raw) raw = strings.TrimSpace(raw) raw = filepath.ToSlash(raw) raw = strings.TrimPrefix(raw, "/") if raw == "" || raw == "." { return "", nil } clean := filepath.Clean(raw) clean = filepath.ToSlash(clean) if clean == "." { return "", nil } if clean == ".." || strings.HasPrefix(clean, "../") { return "", errors.New("invalid source path") } return clean, nil }