Files
WebDAV/internal/server/webdav.go
T
Yunling Zhang df5bff9b3c feat(server): 装配 WebDAV 与 admin/client API
- /<账号>/ chroot 隔离 + Basic 认证(401 带 WWW-Authenticate)+ 只读 403 + 路径逃逸 400
- admin API:登录/列表/新建/换口令/局部更新/删除(保留目录)/连接信息
- client API:登录/列表/上传/下载(Range)/新建文件夹/重命名/删除
- go:embed 内嵌前端,SPA 路由 / 与 /admin
- 修正 .gitignore 中误伤 cmd/davbox 的规则
2026-09-28 16:52:49 +08:00

84 lines
2.0 KiB
Go

package server
import (
"log"
"net/http"
"golang.org/x/net/webdav"
"github.com/YLing2024/davbox/internal/account"
)
// isWriteMethod 列出只读账号需要拒绝的动词。
func isWriteMethod(method string) bool {
switch method {
case http.MethodPut, http.MethodDelete, "MKCOL", "MOVE", "COPY", "PROPPATCH":
return true
}
return false
}
func writeAuthChallenge(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", `Basic realm="davbox"`)
writeError(w, http.StatusUnauthorized, "未认证")
}
// lockSystem 为每个账号复用同一个内存锁系统。
func (s *Server) lockSystem(user string) webdav.LockSystem {
s.davMu.Lock()
defer s.davMu.Unlock()
ls := s.locks[user]
if ls == nil {
ls = webdav.NewMemLS()
s.locks[user] = ls
}
return ls
}
// forgetLocks 在账号被删除后释放其锁系统。
func (s *Server) forgetLocks(user string) {
s.davMu.Lock()
defer s.davMu.Unlock()
delete(s.locks, user)
}
// handleDAV 完成认证、只读检查后交给官方 webdav.Handler。
func (s *Server) handleDAV(w http.ResponseWriter, r *http.Request, user string) {
authUser, pass, ok := r.BasicAuth()
if !ok || authUser != user {
writeAuthChallenge(w)
return
}
acct, ok := s.store.Authenticate(user, pass)
if !ok {
writeAuthChallenge(w)
return
}
if acct.Readonly && isWriteMethod(r.Method) {
writeError(w, http.StatusForbidden, "只读账号")
return
}
h := &webdav.Handler{
Prefix: "/" + user + "/",
FileSystem: webdav.Dir(acct.Root),
LockSystem: s.lockSystem(user),
Logger: func(req *http.Request, err error) {
if err != nil {
// 只记录方法、路径与错误,绝不触碰 Authorization 头。
log.Printf("webdav %s %s: %v", req.Method, req.URL.Path, err)
}
},
}
h.ServeHTTP(w, r)
}
// davHandlerFor 供测试直接拿到某账号的处理器。
func (s *Server) davHandlerFor(acct account.Account) http.Handler {
return &webdav.Handler{
Prefix: "/" + acct.User + "/",
FileSystem: webdav.Dir(acct.Root),
LockSystem: s.lockSystem(acct.User),
}
}