package webdav import ( "context" "errors" "io" "io/fs" "os" "path" "sync" "time" "server/log" "server/torrfs" "github.com/gin-gonic/gin" "golang.org/x/net/webdav" ) var missingMethods = []string{ "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", } func MountWebDAV(r *gin.Engine) { log.TLogln("Starting WebDAV") tfs := torrfs.AsFS(torrfs.New()) h := &webdav.Handler{ Prefix: "/dav", FileSystem: &ReadOnlyFS{FS: tfs}, LockSystem: webdav.NewMemLS(), } grp := r.Group("/dav") handler := func(c *gin.Context) { h.ServeHTTP(c.Writer, c.Request) } grp.Any("/*webdav", handler) for _, m := range missingMethods { grp.Handle(m, "/*webdav", handler) } grp.Any("", handler) for _, m := range missingMethods { grp.Handle(m, "", handler) } } type ReadOnlyFS struct { FS fs.FS } var _ webdav.FileSystem = (*ReadOnlyFS)(nil) func (ro *ReadOnlyFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { return os.ErrPermission } func (ro *ReadOnlyFS) RemoveAll(ctx context.Context, name string) error { return os.ErrPermission } func (ro *ReadOnlyFS) Rename(ctx context.Context, oldName, newName string) error { return os.ErrPermission } func (ro *ReadOnlyFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { name = cleanWebDAVPath(name) return fs.Stat(ro.FS, name) } func (ro *ReadOnlyFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { if flag&(os.O_WRONLY|os.O_RDWR|os.O_APPEND|os.O_CREATE|os.O_TRUNC) != 0 { return nil, os.ErrPermission } name = cleanWebDAVPath(name) f, err := ro.FS.Open(name) if err != nil { return nil, err } return newROFile(ro.FS, name, f), nil } // --- file wrapper --- type roFile struct { fsys fs.FS name string mu sync.Mutex f fs.File dirPos int dirList []fs.DirEntry } func newROFile(fsys fs.FS, name string, f fs.File) *roFile { return &roFile{fsys: fsys, name: name, f: f} } var _ webdav.File = (*roFile)(nil) func (f *roFile) Write(p []byte) (n int, err error) { return 0, fs.ErrPermission } func (f *roFile) Close() error { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return nil } err := f.f.Close() f.f = nil f.dirList = nil f.dirPos = 0 return err } func (f *roFile) Read(p []byte) (int, error) { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return 0, fs.ErrClosed } r, ok := f.f.(io.Reader) if !ok { return 0, fs.ErrInvalid } return r.Read(p) } func (f *roFile) Seek(offset int64, whence int) (int64, error) { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return 0, fs.ErrClosed } rs, ok := f.f.(io.Seeker) if !ok { return 0, errors.New("seek not supported") } return rs.Seek(offset, whence) } func (f *roFile) Stat() (os.FileInfo, error) { return fs.Stat(f.fsys, f.name) } func (f *roFile) Readdir(count int) ([]os.FileInfo, error) { f.mu.Lock() defer f.mu.Unlock() if f.f == nil { return nil, fs.ErrClosed } fi, err := fs.Stat(f.fsys, f.name) if err != nil { return nil, err } if !fi.IsDir() { return nil, fs.ErrInvalid } if f.dirList == nil { des, err := fs.ReadDir(f.fsys, f.name) if err != nil { return nil, err } f.dirList = des f.dirPos = 0 } if count <= 0 { out := make([]os.FileInfo, 0, len(f.dirList)-f.dirPos) for f.dirPos < len(f.dirList) { de := f.dirList[f.dirPos] f.dirPos++ info, err := de.Info() if err != nil { continue } out = append(out, info) } return out, nil } out := make([]os.FileInfo, 0, count) for f.dirPos < len(f.dirList) && len(out) < count { de := f.dirList[f.dirPos] f.dirPos++ info, err := de.Info() if err != nil { continue } out = append(out, info) } if len(out) == 0 { return nil, io.EOF } return out, nil } // --- path helpers --- func cleanWebDAVPath(name string) string { if name == "" || name == "/" { return "." } name = path.Clean("/" + name) name = name[1:] if name == "" { return "." } return name } func nonZeroTime(t time.Time) time.Time { if t.IsZero() { return time.Unix(0, 0) } return t }