238 lines
6.1 KiB
Go
238 lines
6.1 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["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
|
|
}
|