diff --git a/chfs-ng/cmd/chfs/main.go b/chfs-ng/cmd/chfs/main.go index 65087e4..0a9cc48 100644 --- a/chfs-ng/cmd/chfs/main.go +++ b/chfs-ng/cmd/chfs/main.go @@ -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 != "" { diff --git a/chfs-ng/internal/api/root_handler_test.go b/chfs-ng/internal/api/root_handler_test.go new file mode 100644 index 0000000..ddf4b6d --- /dev/null +++ b/chfs-ng/internal/api/root_handler_test.go @@ -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: "`Hello`"} + h := s.Handler() + r := httptest.NewRecorder() + h.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/", nil)) + if r.Code != 200 || !strings.Contains(r.Body.String(), "My Files") || !strings.Contains(r.Body.String(), "Hello") { + 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: ``} + r := httptest.NewRecorder() + s.Handler().ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/", nil)) + if strings.Contains(r.Body.String(), ``) { + t.Fatal("plain notice was not escaped") + } +} diff --git a/chfs-ng/internal/api/server.go b/chfs-ng/internal/api/server.go index c224e91..a6f262c 100644 --- a/chfs-ng/internal/api/server.go +++ b/chfs-ng/internal/api/server.go @@ -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, `%s

%s

%s

Open file API ยท API documentation

`, 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 = `CHFS API

CHFS API

Compatibility API implemented by chfs-ng.

` + _, _ = w.Write([]byte(doc)) } func (s *Server) serveFile(w http.ResponseWriter, r *http.Request, p string, attachment bool) {