Files
CHFS-NG/chfs-ng/internal/api/server.go
T

229 lines
5.7 KiB
Go

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["path"]
if len(items) == 0 {
items = r.Form["paths"]
}
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 := qpath(r)
_, 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 == "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("path"), "/")
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("path"), "/")
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)
}
}
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
}