feat: restore configurable root page and api docs

This commit is contained in:
OpenClaw Agent
2026-09-21 23:44:57 +08:00
parent 1913375251
commit fc918013ee
3 changed files with 65 additions and 2 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ func main() {
if len(cfg.Path) == 1 {
store = storage.Local{Root: root}
}
srv := &api.Server{Store: store, Auth: mgr, Sessions: map[string]*auth.Account{}, SessionExpiry: map[string]time.Time{}, SessionTimeout: cfg.SessionTimeout, WebDAVRoot: root, Allow: cfg.Allow, MultiRoot: len(cfg.Path) > 1, RemoveMode: cfg.FileRemove, ImagePreview: cfg.ImagePreview, FolderDownload: cfg.FolderDownload, Log: opslog.New(cfg.Log)}
srv := &api.Server{Store: store, Auth: mgr, Sessions: map[string]*auth.Account{}, SessionExpiry: map[string]time.Time{}, SessionTimeout: cfg.SessionTimeout, WebDAVRoot: root, Allow: cfg.Allow, MultiRoot: len(cfg.Path) > 1, RemoveMode: cfg.FileRemove, ImagePreview: cfg.ImagePreview, FolderDownload: cfg.FolderDownload, HTMLTitle: cfg.HTMLTitle, HTMLNotice: cfg.HTMLNotice, Log: opslog.New(cfg.Log)}
addr := ":" + itoa(cfg.Port)
log.Printf("chfs-ng listening on %s root=%s removeMode=%d", addr, filepath.Clean(root), cfg.FileRemove)
if cfg.SSLCert != "" && cfg.SSLKey != "" {
+35
View File
@@ -0,0 +1,35 @@
package api
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"chfs-ng/internal/auth"
"chfs-ng/internal/storage"
)
func TestRootAndAPIDoc(t *testing.T) {
s := &Server{Store: storage.Local{Root: t.TempDir()}, Auth: &auth.Manager{Guest: &auth.Account{Mask: auth.Read}}, HTMLTitle: "My Files", HTMLNotice: "`<strong>Hello</strong>`"}
h := s.Handler()
r := httptest.NewRecorder()
h.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/", nil))
if r.Code != 200 || !strings.Contains(r.Body.String(), "<title>My Files</title>") || !strings.Contains(r.Body.String(), "<strong>Hello</strong>") {
t.Fatalf("root: %d %s", r.Code, r.Body.String())
}
r = httptest.NewRecorder()
h.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/asset/api.html", nil))
if r.Code != 200 || !strings.Contains(r.Body.String(), "/chfs/files") {
t.Fatalf("api doc: %d %s", r.Code, r.Body.String())
}
}
func TestRootEscapesPlainNotice(t *testing.T) {
s := &Server{Store: storage.Local{Root: t.TempDir()}, Auth: &auth.Manager{Guest: &auth.Account{Mask: auth.Read}}, HTMLNotice: `<script>alert(1)</script>`}
r := httptest.NewRecorder()
s.Handler().ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/", nil))
if strings.Contains(r.Body.String(), `<script>alert(1)</script>`) {
t.Fatal("plain notice was not escaped")
}
}
+29 -1
View File
@@ -44,6 +44,8 @@ type Server struct {
RemoveMode int
ImagePreview bool
FolderDownload string
HTMLTitle string
HTMLNotice string
Log *opslog.Logger
}
type fileInfo struct {
@@ -76,6 +78,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("/chfs/shared", s.shared)
mux.HandleFunc("/chfs/shared/", s.shared)
mux.HandleFunc("/chfs/preview", s.preview)
mux.HandleFunc("/asset/api.html", s.apiDoc)
mux.HandleFunc("/", s.fileHandler)
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Go's ServeMux canonicalizes a subtree route before dispatching.
@@ -767,7 +770,32 @@ func (s *Server) preview(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) fileHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
title := s.HTMLTitle
if title == "" {
title = "CuteHttpFileServer"
}
notice := s.HTMLNotice
if strings.HasPrefix(notice, "`") && strings.HasSuffix(notice, "`") && len(notice) >= 2 {
notice = notice[1 : len(notice)-1]
} else {
notice = html.EscapeString(notice)
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = fmt.Fprintf(w, `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>%s</title></head><body><main><h1>%s</h1>%s<p><a href="/chfs/files?filepath=">Open file API</a> · <a href="/asset/api.html">API documentation</a></p></main></body></html>`, html.EscapeString(title), html.EscapeString(title), notice)
}
func (s *Server) apiDoc(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/asset/api.html" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
const doc = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CHFS API</title></head><body><h1>CHFS API</h1><p>Compatibility API implemented by chfs-ng.</p><ul><li>/chfs/files</li><li>/chfs/session</li><li>/chfs/upload</li><li>/chfs/newdir</li><li>/chfs/rename</li><li>/chfs/rmfiles</li><li>/chfs/search</li><li>/chfs/downloaddir/&lt;path&gt;</li><li>/chfs/shared/&lt;path&gt;</li><li>/chfs/preview</li><li>/webdav</li></ul></body></html>`
_, _ = w.Write([]byte(doc))
}
func (s *Server) serveFile(w http.ResponseWriter, r *http.Request, p string, attachment bool) {