package api import ( "encoding/json" "io" "mime/multipart" "net/http" "path" "strings" "time" "chfs-ng/internal/auth" "chfs-ng/internal/storage" ) type Server struct { Store storage.Storage Auth *auth.Manager Sessions map[string]*auth.Account } type fileInfo struct { Name string `json:"name"` Path string `json:"path"` Size int64 `json:"size"` Modified string `json:"modified"` Dir bool `json:"dir"` Mask string `json:"mask"` GuestMask string `json:"guestMask"` HasSubDir bool `json:"hasSubDir"` } func (s *Server) Handler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/chfs/files", s.files) mux.HandleFunc("/chfs/newdir", s.newdir) mux.HandleFunc("/chfs/rename", s.rename) mux.HandleFunc("/chfs/rmfiles", s.remove) mux.HandleFunc("/chfs/exist", s.exist) mux.HandleFunc("/chfs/session", s.session) mux.HandleFunc("/chfs/newtext", s.newtext) mux.HandleFunc("/chfs/updatetext", s.updatetext) mux.HandleFunc("/chfs/upload", s.upload) mux.HandleFunc("/chfs/upload2", s.upload) return mux } func account(r *http.Request, s *Server) *auth.Account { if c, e := r.Cookie("chfs_token"); e == nil { if a := s.Sessions[c.Value]; a != nil { return a } } return s.Auth.Guest } func writeJSON(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(v) } func qpath(r *http.Request) string { return strings.Trim(strings.ReplaceAll(r.URL.Query().Get("filepath"), "\\", "/"), "/") } func permString(p auth.PermMask) string { if p.HasDelete() { return "d" } if p.HasWrite() { return "w" } if p.HasRead() { return "r" } return "" } func (s *Server) files(w http.ResponseWriter, r *http.Request) { a := account(r, s) p := qpath(r) m := a.MaskFor(p) if !m.HasRead() { http.Error(w, "forbidden", 403) return } list, e := s.Store.List(p) if e != nil { http.Error(w, e.Error(), 404) return } out := make([]fileInfo, 0, len(list)) for _, fi := range list { fp := path.Join(p, fi.Name()) sub := false if fi.IsDir() { if xs, e := s.Store.List(fp); e == nil { sub = len(xs) > 0 } } out = append(out, fileInfo{Name: fi.Name(), Path: fp, Size: fi.Size(), Modified: fi.ModTime().Format(time.RFC3339), Dir: fi.IsDir(), Mask: permString(a.MaskFor(fp)), HasSubDir: sub}) } writeJSON(w, map[string]any{"files": out, "currentMask": permString(m), "virtualRoot": false}) } func (s *Server) newdir(w http.ResponseWriter, r *http.Request) { a := account(r, s) p := qpath(r) if !a.MaskFor(p).HasWrite() { http.Error(w, "forbidden", 403) return } name := r.FormValue("name") if name == "" { name = r.FormValue("new") } if name == "" { http.Error(w, "name required", 400) return } if e := s.Store.MkdirAll(path.Join(p, name)); e != nil { http.Error(w, e.Error(), 500) return } writeJSON(w, map[string]any{"ok": true}) } func (s *Server) rename(w http.ResponseWriter, r *http.Request) { a := account(r, s) old := strings.Trim(r.FormValue("old"), "/") neu := strings.Trim(r.FormValue("new"), "/") if !a.MaskFor(old).HasWrite() { http.Error(w, "forbidden", 403) return } if e := s.Store.Rename(old, neu); e != nil { http.Error(w, e.Error(), 500) return } writeJSON(w, map[string]any{"ok": true}) } func (s *Server) remove(w http.ResponseWriter, r *http.Request) { a := account(r, s) _ = r.ParseForm() items := r.Form["filepath"] if len(items) == 0 { items = r.Form["path"] } for _, p := range items { if !a.MaskFor(p).HasDelete() { http.Error(w, "forbidden", 403) return } if e := s.Store.Remove(p); e != nil { http.Error(w, e.Error(), 500) return } } writeJSON(w, map[string]any{"ok": true}) } func (s *Server) exist(w http.ResponseWriter, r *http.Request) { p := strings.Trim(strings.ReplaceAll(r.URL.Query().Get("file"), "\\", "/"), "/") _, e := s.Store.Stat(p) writeJSON(w, map[string]any{"exist": e == nil}) } func (s *Server) session(w http.ResponseWriter, r *http.Request) { if r.Method == "DELETE" { http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: "", Path: "/", MaxAge: -1, HttpOnly: true}) writeJSON(w, map[string]any{"ok": true}) return } if r.Method == "GET" { a := account(r, s) writeJSON(w, map[string]any{"login": a != nil, "user": a.Username}) return } user, pwd := r.FormValue("user"), r.FormValue("pwd") a := s.Auth.Authenticate(user, pwd) if a == nil { http.Error(w, "invalid account", 401) return } token := auth.NewToken() s.Sessions[token] = a http.SetCookie(w, &http.Cookie{Name: "chfs_token", Value: token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode}) writeJSON(w, map[string]any{"ok": true, "user": a.Username}) } func (s *Server) newtext(w http.ResponseWriter, r *http.Request) { a := account(r, s) p := strings.Trim(r.FormValue("filepath"), "/") if filename := strings.Trim(r.FormValue("filename"), "/"); filename != "" { p = path.Join(p, filename) } if !a.MaskFor(p).HasWrite() { http.Error(w, "forbidden", 403) return } f, e := s.Store.Create(p) if e != nil { http.Error(w, e.Error(), 500) return } defer f.Close() io.WriteString(f, r.FormValue("text")) writeJSON(w, map[string]any{"ok": true}) } func (s *Server) updatetext(w http.ResponseWriter, r *http.Request) { s.newtext(w, r) } func (s *Server) upload(w http.ResponseWriter, r *http.Request) { a := account(r, s) dir := strings.Trim(r.FormValue("folder"), "/") if !a.MaskFor(dir).HasWrite() { http.Error(w, "forbidden", 403) return } if e := r.ParseMultipartForm(64 << 20); e != nil { http.Error(w, e.Error(), 400) return } var saved []string for _, hs := range r.MultipartForm.File { for _, h := range hs { if e := savePart(s.Store, dir, h); e != nil { http.Error(w, e.Error(), 500) return } saved = append(saved, h.Filename) } } w.WriteHeader(http.StatusCreated) writeJSON(w, map[string]any{"ok": true, "files": saved}) } func savePart(st storage.Storage, dir string, h *multipart.FileHeader) error { in, e := h.Open() if e != nil { return e } defer in.Close() f, e := st.Create(path.Join(dir, h.Filename)) if e != nil { return e } defer f.Close() _, e = io.Copy(f, in) return e }