diff --git a/examples/webdav/main.go b/examples/webdav/main.go index 87bb00e..ecbfe15 100644 --- a/examples/webdav/main.go +++ b/examples/webdav/main.go @@ -17,10 +17,18 @@ func main() { log.Fatal(err) } - // Serve the "public" directory on the "/webdav/" route. - if err := webdav.Serve(r, "/webdav", "public"); err != nil { + // Create a new WebDAV handler with the OS file system. + fs, err := webdav.NewOSFS("public") + if err != nil { log.Fatal(err) } + handler := webdav.NewHandler("/webdav", fs, webdav.NewMemLock(), log.New(os.Stdout, "", 0)) + + // Mount the WebDAV handler on the "/webdav/" route. + webdavMethods := []string{ + "OPTIONS", "GET", "HEAD", "DELETE", "PUT", "MKCOL", "COPY", "MOVE", "PROPFIND", "PROPPATCH", "LOCK", "UNLOCK", + } + r.HandleFunc(webdavMethods, "/webdav/*path", handler.ServeTouka) log.Println("Touka WebDAV Server starting on :8080...") if err := r.RunShutdown(":8080", 10*time.Second); err != nil { diff --git a/touka.go b/touka.go index 441212c..898a8b3 100644 --- a/touka.go +++ b/touka.go @@ -67,8 +67,6 @@ var ( MethodMkcol = "MKCOL" MethodCopy = "COPY" MethodMove = "MOVE" - MethodLock = "LOCK" - MethodUnlock = "UNLOCK" ) var MethodsSet = map[string]struct{}{ @@ -86,6 +84,4 @@ var MethodsSet = map[string]struct{}{ MethodMkcol: {}, MethodCopy: {}, MethodMove: {}, - MethodLock: {}, - MethodUnlock: {}, } diff --git a/webdav/easy.go b/webdav/easy.go deleted file mode 100644 index 56d1eb0..0000000 --- a/webdav/easy.go +++ /dev/null @@ -1,48 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 WJQSERVER. All rights reserved. -// All rights reserved by WJQSERVER, related rights can be exercised by the infinite-iroha organization. -package webdav - -import ( - "log" - "os" - - "github.com/infinite-iroha/touka" -) - -// Config is a configuration for the WebDAV handler. -type Config struct { - FileSystem FileSystem - LockSystem LockSystem - Logger Logger -} - -// Register registers a WebDAV handler on the given router. -func Register(engine *touka.Engine, prefix string, cfg *Config) { - if cfg.LockSystem == nil { - cfg.LockSystem = NewMemLock() - } - - handler := NewHandler(prefix, cfg.FileSystem, cfg.LockSystem, cfg.Logger) - - webdavMethods := []string{ - "OPTIONS", "GET", "HEAD", "DELETE", "PUT", "MKCOL", "COPY", "MOVE", "PROPFIND", "PROPPATCH", "LOCK", "UNLOCK", - } - engine.HandleFunc(webdavMethods, prefix+"/*path", handler.ServeTouka) -} - -// Serve serves a local directory via WebDAV. -func Serve(engine *touka.Engine, prefix string, rootDir string) error { - fs, err := NewOSFS(rootDir) - if err != nil { - return err - } - - cfg := &Config{ - FileSystem: fs, - Logger: log.New(os.Stdout, "", 0), - } - Register(engine, prefix, cfg) - return nil -} diff --git a/webdav/easy_test.go b/webdav/easy_test.go deleted file mode 100644 index bf44441..0000000 --- a/webdav/easy_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// Copyright 2024 WJQSERVER. All rights reserved. -// All rights reserved by WJQSERVER, related rights can be exercised by the infinite-iroha organization. -package webdav - -import ( - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/infinite-iroha/touka" -) - -func TestRegister(t *testing.T) { - r := touka.New() - cfg := &Config{ - FileSystem: NewMemFS(), - } - Register(r, "/dav", cfg) - - // Check if a WebDAV method is registered - req, _ := http.NewRequest("PROPFIND", "/dav/", nil) - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - - if w.Code == http.StatusNotFound { - t.Errorf("Expected PROPFIND to be registered, but got 404") - } -} - -func TestServe(t *testing.T) { - r := touka.New() - dir, _ := os.MkdirTemp("", "webdav") - defer os.RemoveAll(dir) - - if err := Serve(r, "/serve", dir); err != nil { - t.Fatalf("Serve failed: %v", err) - } - - // Check if a WebDAV method is registered - req, _ := http.NewRequest("OPTIONS", "/serve/", nil) - w := httptest.NewRecorder() - r.ServeHTTP(w, req) - - if w.Code != http.StatusOK { - t.Errorf("Expected OPTIONS to return 200, but got %d", w.Code) - } -} diff --git a/webdav/memfs.go b/webdav/memfs.go index 6333eb1..837fd9d 100644 --- a/webdav/memfs.go +++ b/webdav/memfs.go @@ -10,10 +10,7 @@ import ( "path" "strings" "sync" - "sync/atomic" "time" - - "github.com/infinite-iroha/touka" ) // MemFS is an in-memory file system for WebDAV using a tree structure. @@ -88,7 +85,7 @@ func (fs *MemFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error } // OpenFile opens a file in the in-memory file system. -func (fs *MemFS) OpenFile(c *touka.Context, name string, flag int, perm os.FileMode) (File, error) { +func (fs *MemFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { fs.mu.Lock() defer fs.mu.Unlock() @@ -114,16 +111,15 @@ func (fs *MemFS) OpenFile(c *touka.Context, name string, flag int, perm os.FileM if flag&os.O_TRUNC != 0 { node.data = nil - atomic.StoreInt64(&node.size, 0) + node.size = 0 } - mf := memFilePool.Get().(*memFile) - mf.node = node - mf.fs = fs - mf.offset = 0 - mf.fullPath = name - mf.contentLength = c.Request.ContentLength - return mf, nil + return &memFile{ + node: node, + fs: fs, + offset: 0, + fullPath: name, + }, nil } // RemoveAll removes a file or directory from the in-memory file system. @@ -198,32 +194,20 @@ type memNode struct { } func (n *memNode) Name() string { return n.name } -func (n *memNode) Size() int64 { return atomic.LoadInt64(&n.size) } +func (n *memNode) Size() int64 { return n.size } func (n *memNode) Mode() os.FileMode { return n.mode } func (n *memNode) ModTime() time.Time { return n.modTime } func (n *memNode) IsDir() bool { return n.isDir } func (n *memNode) Sys() interface{} { return nil } type memFile struct { - node *memNode - fs *MemFS - offset int64 - fullPath string - contentLength int64 + node *memNode + fs *MemFS + offset int64 + fullPath string } -var memFilePool = sync.Pool{ - New: func() interface{} { - return &memFile{} - }, -} - -func (f *memFile) Close() error { - f.node = nil - f.fs = nil - memFilePool.Put(f) - return nil -} +func (f *memFile) Close() error { return nil } func (f *memFile) Stat() (ObjectInfo, error) { return f.node, nil } func (f *memFile) Read(p []byte) (n int, err error) { @@ -240,17 +224,17 @@ func (f *memFile) Read(p []byte) (n int, err error) { func (f *memFile) Write(p []byte) (n int, err error) { f.fs.mu.Lock() defer f.fs.mu.Unlock() - newSize := f.offset + int64(len(p)) - if newSize > int64(cap(f.node.data)) { + if f.offset+int64(len(p)) > int64(len(f.node.data)) { + newSize := f.offset + int64(len(p)) newData := make([]byte, newSize) copy(newData, f.node.data) f.node.data = newData - } else { - f.node.data = f.node.data[:newSize] } n = copy(f.node.data[f.offset:], p) f.offset += int64(n) - atomic.StoreInt64(&f.node.size, newSize) + if f.offset > f.node.size { + f.node.size = f.offset + } return n, nil } diff --git a/webdav/osfs.go b/webdav/osfs.go index 3d54a30..cf4c62e 100644 --- a/webdav/osfs.go +++ b/webdav/osfs.go @@ -9,8 +9,6 @@ import ( "os" "path/filepath" "strings" - - "github.com/infinite-iroha/touka" ) // OSFS is a WebDAV FileSystem that uses the local OS file system. @@ -55,11 +53,7 @@ func (fs *OSFS) resolve(name string) (string, error) { } } - rel, err := filepath.Rel(fs.RootDir, path) - if err != nil { - return "", err - } - if strings.HasPrefix(rel, "..") { + if !strings.HasPrefix(path, fs.RootDir) { return "", os.ErrPermission } @@ -104,7 +98,7 @@ func (f *osFile) Readdir(count int) ([]ObjectInfo, error) { } // OpenFile opens a file. -func (fs *OSFS) OpenFile(c *touka.Context, name string, flag int, perm os.FileMode) (File, error) { +func (fs *OSFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { path, err := fs.resolve(name) if err != nil { return nil, err diff --git a/webdav/webdav.go b/webdav/webdav.go index b438c24..2bad373 100644 --- a/webdav/webdav.go +++ b/webdav/webdav.go @@ -14,7 +14,6 @@ import ( "os" "path" "strings" - "sync" "time" "github.com/infinite-iroha/touka" @@ -25,7 +24,7 @@ import ( // abstracting the underlying storage from the WebDAV protocol logic. type FileSystem interface { Mkdir(ctx context.Context, name string, perm os.FileMode) error - OpenFile(c *touka.Context, name string, flag int, perm os.FileMode) (File, error) + OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) RemoveAll(ctx context.Context, name string) error Rename(ctx context.Context, oldName, newName string) error Stat(ctx context.Context, name string) (ObjectInfo, error) @@ -150,14 +149,6 @@ type Response struct { Propstats []Propstat `xml:"DAV: propstat"` } -var multistatusPool = sync.Pool{ - New: func() interface{} { - return &Multistatus{ - Responses: make([]*Response, 0), - } - }, -} - // Propstat groups properties with their corresponding HTTP status in a // single response, indicating success or failure for those properties. type Propstat struct { @@ -266,7 +257,7 @@ func (h *Handler) handleOptions(c *touka.Context) { func (h *Handler) handleGetHead(c *touka.Context) { path, _ := c.Get("webdav_path") - file, err := h.FileSystem.OpenFile(c, path.(string), os.O_RDONLY, 0) + file, err := h.FileSystem.OpenFile(c.Context(), path.(string), os.O_RDONLY, 0) if err != nil { if os.IsNotExist(err) { c.Status(http.StatusNotFound) @@ -306,7 +297,7 @@ func (h *Handler) handleDelete(c *touka.Context) { } if info.IsDir() { - file, err := h.FileSystem.OpenFile(c, pathStr, os.O_RDONLY, 0) + file, err := h.FileSystem.OpenFile(c.Context(), pathStr, os.O_RDONLY, 0) if err != nil { c.Status(http.StatusInternalServerError) return @@ -338,7 +329,7 @@ func (h *Handler) handleDelete(c *touka.Context) { func (h *Handler) handlePut(c *touka.Context) { path, _ := c.Get("webdav_path") - file, err := h.FileSystem.OpenFile(c, path.(string), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + file, err := h.FileSystem.OpenFile(c.Context(), path.(string), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { c.Status(http.StatusInternalServerError) return @@ -397,7 +388,7 @@ func (h *Handler) handleCopy(c *touka.Context) { return } - if err := h.copy(c, srcPath.(string), destPath); err != nil { + if err := h.copy(c.Context(), srcPath.(string), destPath); err != nil { c.Status(http.StatusInternalServerError) return } @@ -450,18 +441,18 @@ func (h *Handler) handleMove(c *touka.Context) { } } -func (h *Handler) copy(c *touka.Context, src, dest string) error { - info, err := h.FileSystem.Stat(c.Context(), src) +func (h *Handler) copy(ctx context.Context, src, dest string) error { + info, err := h.FileSystem.Stat(ctx, src) if err != nil { return err } if info.IsDir() { - if err := h.FileSystem.Mkdir(c.Context(), dest, info.Mode()); err != nil { + if err := h.FileSystem.Mkdir(ctx, dest, info.Mode()); err != nil { return err } - srcFile, err := h.FileSystem.OpenFile(c, src, os.O_RDONLY, 0) + srcFile, err := h.FileSystem.OpenFile(ctx, src, os.O_RDONLY, 0) if err != nil { return err } @@ -473,20 +464,20 @@ func (h *Handler) copy(c *touka.Context, src, dest string) error { } for _, child := range children { - if err := h.copy(c, path.Join(src, child.Name()), path.Join(dest, child.Name())); err != nil { + if err := h.copy(ctx, path.Join(src, child.Name()), path.Join(dest, child.Name())); err != nil { return err } } return nil } - srcFile, err := h.FileSystem.OpenFile(c, src, os.O_RDONLY, 0) + srcFile, err := h.FileSystem.OpenFile(ctx, src, os.O_RDONLY, 0) if err != nil { return err } defer srcFile.Close() - destFile, err := h.FileSystem.OpenFile(c, dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) + destFile, err := h.FileSystem.OpenFile(ctx, dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) if err != nil { return err } @@ -516,11 +507,9 @@ func (h *Handler) handlePropfind(c *touka.Context) { } } - ms := multistatusPool.Get().(*Multistatus) - defer func() { - ms.Responses = ms.Responses[:0] - multistatusPool.Put(ms) - }() + ms := &Multistatus{ + Responses: make([]*Response, 0), + } depth := c.GetReqHeader("Depth") if depth == "" { @@ -536,7 +525,7 @@ func (h *Handler) handlePropfind(c *touka.Context) { return nil } - file, err := h.FileSystem.OpenFile(c, p, os.O_RDONLY, 0) + file, err := h.FileSystem.OpenFile(c.Context(), p, os.O_RDONLY, 0) if err != nil { return err } diff --git a/webdav/webdav_test.go b/webdav/webdav_test.go index 5939523..db2e17e 100644 --- a/webdav/webdav_test.go +++ b/webdav/webdav_test.go @@ -75,7 +75,7 @@ func TestHandlePropfind(t *testing.T) { // Create a test directory and a test file fs.Mkdir(nil, "/testdir", 0755) - file, _ := fs.OpenFile(&touka.Context{Request: &http.Request{}}, "/testdir/testfile", os.O_CREATE|os.O_WRONLY, 0644) + file, _ := fs.OpenFile(nil, "/testdir/testfile", os.O_CREATE|os.O_WRONLY, 0644) file.Write([]byte("test content")) file.Close()