feat: expand legacy file API and webdav support
This commit is contained in:
+52
-12
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"chfs-ng/internal/api"
|
"chfs-ng/internal/api"
|
||||||
"chfs-ng/internal/auth"
|
"chfs-ng/internal/auth"
|
||||||
@@ -17,37 +18,65 @@ func main() {
|
|||||||
file := flag.String("file", "", "configuration file")
|
file := flag.String("file", "", "configuration file")
|
||||||
pathFlag := flag.String("path", "/data", "shared path")
|
pathFlag := flag.String("path", "/data", "shared path")
|
||||||
port := flag.Int("port", 80, "listen port")
|
port := flag.Int("port", 80, "listen port")
|
||||||
|
allow := flag.String("allow", "", "allowed IPv4 addresses")
|
||||||
|
var rules multiFlag
|
||||||
|
flag.Var(&rules, "rule", "access rule (repeatable)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
cfg := config.Default()
|
cfg := config.Default()
|
||||||
if *file != "" {
|
if *file != "" {
|
||||||
var err error
|
loaded, err := config.Load(*file)
|
||||||
cfg, err = config.Load(*file)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
cfg = loaded
|
||||||
}
|
}
|
||||||
if *pathFlag != "/data" || *file == "" {
|
}
|
||||||
if *file == "" {
|
flag.CommandLine.Visit(func(f *flag.Flag) {
|
||||||
cfg.Path = []string{*pathFlag}
|
switch f.Name {
|
||||||
|
case "path":
|
||||||
|
cfg.Path = splitPath(*pathFlag)
|
||||||
|
case "port":
|
||||||
cfg.Port = *port
|
cfg.Port = *port
|
||||||
|
case "allow":
|
||||||
|
cfg.Allow = *allow
|
||||||
|
case "rule":
|
||||||
|
cfg.Rules = append([]string(nil), rules...)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
if len(cfg.Path) == 0 {
|
||||||
|
cfg.Path = []string{"/data"}
|
||||||
}
|
}
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
root := cfg.Path[0]
|
root := cfg.Path[0]
|
||||||
if e := os.MkdirAll(root, 0755); e != nil {
|
if len(cfg.Path) > 1 {
|
||||||
log.Fatal(e)
|
log.Printf("warning: %d shared roots configured; v1 compatibility currently serves the first root %q", len(cfg.Path), root)
|
||||||
}
|
}
|
||||||
mgr, _ := auth.ParseRules(cfg.Rules)
|
if err := os.MkdirAll(root, 0755); err != nil {
|
||||||
srv := &api.Server{Store: storage.Local{Root: root}, Auth: mgr, Sessions: map[string]*auth.Account{}}
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
mgr, err := auth.ParseRules(cfg.Rules)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
srv := &api.Server{Store: storage.Local{Root: root}, Auth: mgr, Sessions: map[string]*auth.Account{}, WebDAVRoot: root}
|
||||||
addr := ":" + itoa(cfg.Port)
|
addr := ":" + itoa(cfg.Port)
|
||||||
if cfg.Port == 0 {
|
|
||||||
addr = ":" + itoa(*port)
|
|
||||||
}
|
|
||||||
log.Printf("chfs-ng listening on %s root=%s", addr, filepath.Clean(root))
|
log.Printf("chfs-ng listening on %s root=%s", addr, filepath.Clean(root))
|
||||||
if cfg.SSLCert != "" && cfg.SSLKey != "" {
|
if cfg.SSLCert != "" && cfg.SSLKey != "" {
|
||||||
log.Fatal(http.ListenAndServeTLS(addr, cfg.SSLCert, cfg.SSLKey, srv.Handler()))
|
log.Fatal(http.ListenAndServeTLS(addr, cfg.SSLCert, cfg.SSLKey, srv.Handler()))
|
||||||
}
|
}
|
||||||
log.Fatal(http.ListenAndServe(addr, srv.Handler()))
|
log.Fatal(http.ListenAndServe(addr, srv.Handler()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type multiFlag []string
|
||||||
|
|
||||||
|
func (m *multiFlag) String() string { return strings.Join(*m, "|") }
|
||||||
|
func (m *multiFlag) Set(v string) error { *m = append(*m, v); return nil }
|
||||||
|
|
||||||
func itoa(n int) string {
|
func itoa(n int) string {
|
||||||
const d = "0123456789"
|
const d = "0123456789"
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
@@ -60,3 +89,14 @@ func itoa(n int) string {
|
|||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitPath(v string) []string {
|
||||||
|
var out []string
|
||||||
|
for _, p := range strings.Split(v, "|") {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p != "" {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
module chfs-ng
|
module chfs-ng
|
||||||
|
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
|
require github.com/emersion/go-webdav v0.7.0
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw=
|
||||||
|
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
|
github.com/emersion/go-webdav v0.7.0 h1:cp6aBWXBf8Sjzguka9VJarr4XTkGc2IHxXI1Gq3TKpA=
|
||||||
|
github.com/emersion/go-webdav v0.7.0/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
|
||||||
|
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
||||||
+294
-20
@@ -1,22 +1,27 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"chfs-ng/internal/auth"
|
"chfs-ng/internal/auth"
|
||||||
"chfs-ng/internal/storage"
|
"chfs-ng/internal/storage"
|
||||||
|
"github.com/emersion/go-webdav"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Store storage.Storage
|
Store storage.Storage
|
||||||
Auth *auth.Manager
|
Auth *auth.Manager
|
||||||
Sessions map[string]*auth.Account
|
Sessions map[string]*auth.Account
|
||||||
|
WebDAVRoot string
|
||||||
}
|
}
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -31,6 +36,8 @@ type fileInfo struct {
|
|||||||
|
|
||||||
func (s *Server) Handler() http.Handler {
|
func (s *Server) Handler() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/webdav/", s.webdav)
|
||||||
|
mux.HandleFunc("/webdav", s.webdav)
|
||||||
mux.HandleFunc("/chfs/files", s.files)
|
mux.HandleFunc("/chfs/files", s.files)
|
||||||
mux.HandleFunc("/chfs/newdir", s.newdir)
|
mux.HandleFunc("/chfs/newdir", s.newdir)
|
||||||
mux.HandleFunc("/chfs/rename", s.rename)
|
mux.HandleFunc("/chfs/rename", s.rename)
|
||||||
@@ -40,7 +47,12 @@ func (s *Server) Handler() http.Handler {
|
|||||||
mux.HandleFunc("/chfs/newtext", s.newtext)
|
mux.HandleFunc("/chfs/newtext", s.newtext)
|
||||||
mux.HandleFunc("/chfs/updatetext", s.updatetext)
|
mux.HandleFunc("/chfs/updatetext", s.updatetext)
|
||||||
mux.HandleFunc("/chfs/upload", s.upload)
|
mux.HandleFunc("/chfs/upload", s.upload)
|
||||||
mux.HandleFunc("/chfs/upload2", s.upload)
|
mux.HandleFunc("/chfs/upload2", s.upload2)
|
||||||
|
mux.HandleFunc("/chfs/search", s.search)
|
||||||
|
mux.HandleFunc("/chfs/downloaddir/", s.downloadDir)
|
||||||
|
mux.HandleFunc("/chfs/shared", s.shared)
|
||||||
|
mux.HandleFunc("/chfs/preview", s.preview)
|
||||||
|
mux.HandleFunc("/", s.fileHandler)
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
func account(r *http.Request, s *Server) *auth.Account {
|
func account(r *http.Request, s *Server) *auth.Account {
|
||||||
@@ -97,21 +109,28 @@ func (s *Server) files(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeJSON(w, map[string]any{"files": out, "currentMask": permString(m), "virtualRoot": false})
|
writeJSON(w, map[string]any{"files": out, "currentMask": permString(m), "virtualRoot": false})
|
||||||
}
|
}
|
||||||
func (s *Server) newdir(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) newdir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_ = r.ParseMultipartForm(16 << 20)
|
||||||
|
_ = r.ParseForm()
|
||||||
a := account(r, s)
|
a := account(r, s)
|
||||||
p := qpath(r)
|
p := qpath(r)
|
||||||
if !a.MaskFor(p).HasWrite() {
|
if !a.MaskFor(p).HasWrite() {
|
||||||
http.Error(w, "forbidden", 403)
|
http.Error(w, "forbidden", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := r.FormValue("name")
|
if p == "" {
|
||||||
if name == "" {
|
p = strings.Trim(strings.ReplaceAll(r.FormValue("filepath"), "\\", "/"), "/")
|
||||||
name = r.FormValue("new")
|
|
||||||
}
|
}
|
||||||
if name == "" {
|
if p == "" {
|
||||||
http.Error(w, "name required", 400)
|
p = strings.Trim(r.FormValue("name"), "/")
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = strings.Trim(r.FormValue("new"), "/")
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
http.Error(w, "filepath required", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if e := s.Store.MkdirAll(path.Join(p, name)); e != nil {
|
if e := s.Store.MkdirAll(p); e != nil {
|
||||||
http.Error(w, e.Error(), 500)
|
http.Error(w, e.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -162,8 +181,13 @@ func (s *Server) session(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
a := account(r, s)
|
user := ""
|
||||||
writeJSON(w, map[string]any{"login": a != nil, "user": a.Username})
|
if c, e := r.Cookie("chfs_token"); e == nil {
|
||||||
|
if x := s.Sessions[c.Value]; x != nil {
|
||||||
|
user = x.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{"login": user != "", "user": user})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, pwd := r.FormValue("user"), r.FormValue("pwd")
|
user, pwd := r.FormValue("user"), r.FormValue("pwd")
|
||||||
@@ -179,10 +203,16 @@ func (s *Server) session(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
func (s *Server) newtext(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) newtext(w http.ResponseWriter, r *http.Request) {
|
||||||
a := account(r, s)
|
a := account(r, s)
|
||||||
p := strings.Trim(r.FormValue("filepath"), "/")
|
dir := strings.Trim(strings.ReplaceAll(r.FormValue("filepath"), "\\", "/"), "/")
|
||||||
if filename := strings.Trim(r.FormValue("filename"), "/"); filename != "" {
|
filename := strings.Trim(r.FormValue("title"), "/")
|
||||||
p = path.Join(p, filename)
|
if filename == "" {
|
||||||
|
filename = strings.Trim(r.FormValue("filename"), "/")
|
||||||
}
|
}
|
||||||
|
if filename == "" {
|
||||||
|
http.Error(w, "title required", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := path.Join(dir, filename)
|
||||||
if !a.MaskFor(p).HasWrite() {
|
if !a.MaskFor(p).HasWrite() {
|
||||||
http.Error(w, "forbidden", 403)
|
http.Error(w, "forbidden", 403)
|
||||||
return
|
return
|
||||||
@@ -196,22 +226,47 @@ func (s *Server) newtext(w http.ResponseWriter, r *http.Request) {
|
|||||||
io.WriteString(f, r.FormValue("text"))
|
io.WriteString(f, r.FormValue("text"))
|
||||||
writeJSON(w, map[string]any{"ok": true})
|
writeJSON(w, map[string]any{"ok": true})
|
||||||
}
|
}
|
||||||
func (s *Server) updatetext(w http.ResponseWriter, r *http.Request) { s.newtext(w, r) }
|
func (s *Server) updatetext(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := account(r, s)
|
||||||
|
dir := strings.Trim(r.FormValue("filepath"), "/")
|
||||||
|
name := strings.Trim(r.FormValue("filename"), "/")
|
||||||
|
p := path.Join(dir, name)
|
||||||
|
if name == "" || !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()
|
||||||
|
if _, e = io.WriteString(f, r.FormValue("text")); e != nil {
|
||||||
|
http.Error(w, e.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{"ok": true})
|
||||||
|
}
|
||||||
func (s *Server) upload(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) upload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if e := r.ParseMultipartForm(64 << 20); e != nil {
|
||||||
|
http.Error(w, e.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
a := account(r, s)
|
a := account(r, s)
|
||||||
dir := strings.Trim(r.FormValue("folder"), "/")
|
dir := strings.Trim(r.FormValue("folder"), "/")
|
||||||
if !a.MaskFor(dir).HasWrite() {
|
if !a.MaskFor(dir).HasWrite() {
|
||||||
http.Error(w, "forbidden", 403)
|
http.Error(w, "forbidden", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if e := r.ParseMultipartForm(64 << 20); e != nil {
|
|
||||||
http.Error(w, e.Error(), 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var saved []string
|
var saved []string
|
||||||
for _, hs := range r.MultipartForm.File {
|
for _, hs := range r.MultipartForm.File {
|
||||||
for _, h := range hs {
|
for _, h := range hs {
|
||||||
if e := savePart(s.Store, dir, h); e != nil {
|
name := filepath.ToSlash(h.Filename)
|
||||||
|
if name == "" || strings.HasPrefix(name, "/") || strings.Contains(name, "../") || name == ".." {
|
||||||
|
http.Error(w, "invalid filename", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if e := savePart(s.Store, path.Join(dir, name), h); e != nil {
|
||||||
http.Error(w, e.Error(), 500)
|
http.Error(w, e.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -221,13 +276,13 @@ func (s *Server) upload(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
writeJSON(w, map[string]any{"ok": true, "files": saved})
|
writeJSON(w, map[string]any{"ok": true, "files": saved})
|
||||||
}
|
}
|
||||||
func savePart(st storage.Storage, dir string, h *multipart.FileHeader) error {
|
func savePart(st storage.Storage, target string, h *multipart.FileHeader) error {
|
||||||
in, e := h.Open()
|
in, e := h.Open()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
defer in.Close()
|
defer in.Close()
|
||||||
f, e := st.Create(path.Join(dir, h.Filename))
|
f, e := st.Create(target)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@@ -235,3 +290,222 @@ func savePart(st storage.Storage, dir string, h *multipart.FileHeader) error {
|
|||||||
_, e = io.Copy(f, in)
|
_, e = io.Copy(f, in)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) upload2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := account(r, s)
|
||||||
|
dir := strings.Trim(r.URL.Query().Get("folder"), "/")
|
||||||
|
if !a.MaskFor(dir).HasWrite() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := r.ParseMultipartForm(128 << 20); err != nil {
|
||||||
|
http.Error(w, err.Error(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var saved []string
|
||||||
|
for _, hs := range r.MultipartForm.File {
|
||||||
|
for _, h := range hs {
|
||||||
|
name := filepath.ToSlash(h.Filename)
|
||||||
|
name = strings.TrimPrefix(name, "/")
|
||||||
|
if name == "" || strings.Contains(name, "../") || name == ".." {
|
||||||
|
http.Error(w, "invalid filename", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := savePart(s.Store, path.Join(dir, name), h); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
saved = append(saved, path.Join(dir, name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
writeJSON(w, map[string]any{"ok": true, "files": saved})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) search(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := account(r, s)
|
||||||
|
if !a.MaskFor("").HasRead() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
needle := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("str")))
|
||||||
|
if needle == "" {
|
||||||
|
writeJSON(w, map[string]any{"files": []fileInfo{}})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var out []fileInfo
|
||||||
|
var walk func(string) error
|
||||||
|
walk = func(dir string) error {
|
||||||
|
items, err := s.Store.List(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, fi := range items {
|
||||||
|
p := path.Join(dir, fi.Name())
|
||||||
|
if !a.MaskFor(p).HasRead() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(strings.ToLower(fi.Name()), needle) {
|
||||||
|
out = append(out, fileInfo{Name: fi.Name(), Path: p, Size: fi.Size(), Modified: fi.ModTime().Format(time.RFC3339), Dir: fi.IsDir(), Mask: permString(a.MaskFor(p)), GuestMask: permString(s.Auth.Guest.MaskFor(p))})
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
if err := walk(p); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := walk(""); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, map[string]any{"files": out})
|
||||||
|
}
|
||||||
|
func (s *Server) shared(w http.ResponseWriter, r *http.Request) {
|
||||||
|
file := strings.Trim(r.URL.Query().Get("file"), "/")
|
||||||
|
if file == "" {
|
||||||
|
file = strings.TrimPrefix(r.URL.Path, "/chfs/shared/")
|
||||||
|
}
|
||||||
|
s.serveFile(w, r, file, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) preview(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !account(r, s).MaskFor(strings.Trim(r.URL.Query().Get("file"), "/")).HasRead() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file := strings.Trim(r.URL.Query().Get("file"), "/")
|
||||||
|
if file == "" {
|
||||||
|
http.Error(w, "file required", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.serveFile(w, r, file, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) fileHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/" || strings.HasPrefix(r.URL.Path, "/asset/") {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/chfs/") || r.URL.Path == "/webdav" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := strings.Trim(strings.ReplaceAll(r.URL.Path, "\\", "/"), "/")
|
||||||
|
if p == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.serveFile(w, r, p, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) serveFile(w http.ResponseWriter, r *http.Request, p string, attachment bool) {
|
||||||
|
a := account(r, s)
|
||||||
|
p = strings.Trim(strings.ReplaceAll(p, "\\", "/"), "/")
|
||||||
|
if !a.MaskFor(p).HasRead() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi, err := s.Store.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
http.Error(w, "directory", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := s.Store.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if attachment {
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", fi.Name()))
|
||||||
|
}
|
||||||
|
http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) downloadDir(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := account(r, s)
|
||||||
|
p := strings.TrimPrefix(r.URL.Path, "/chfs/downloaddir/")
|
||||||
|
p = strings.Trim(p, "/")
|
||||||
|
if !a.MaskFor(p).HasRead() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fi, err := s.Store.Stat(p)
|
||||||
|
if err != nil || !fi.IsDir() {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := fi.Name()
|
||||||
|
if name == "." || name == "" {
|
||||||
|
name = "download"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q.zip", name))
|
||||||
|
zw := zip.NewWriter(w)
|
||||||
|
defer zw.Close()
|
||||||
|
var walk func(string, string) error
|
||||||
|
walk = func(dir, prefix string) error {
|
||||||
|
items, err := s.Store.List(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
p2 := path.Join(dir, item.Name())
|
||||||
|
rel := path.Join(prefix, item.Name())
|
||||||
|
if !a.MaskFor(p2).HasRead() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if item.IsDir() {
|
||||||
|
if err := walk(p2, rel); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h, err := zw.Create(rel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := s.Store.Open(p2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(h, f)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := walk(p, ""); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) webdav(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.WebDAVRoot == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vp := strings.TrimPrefix(r.URL.Path, "/webdav")
|
||||||
|
vp = strings.Trim(vp, "/")
|
||||||
|
if !account(r, s).MaskFor(vp).HasRead() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead && !account(r, s).MaskFor(vp).HasWrite() {
|
||||||
|
http.Error(w, "forbidden", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
old := r.URL.Path
|
||||||
|
r.URL.Path = "/" + vp
|
||||||
|
defer func() { r.URL.Path = old }()
|
||||||
|
h := &webdav.Handler{FileSystem: webdav.LocalFileSystem(s.WebDAVRoot)}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user