Compare commits

..

No commits in common. "b92f1face5d19fa1f6800a3aa6c2f8f80e3f2278" and "85409ba803344a9fdad0a4519f9f99f1c04a542a" have entirely different histories.

8 changed files with 48 additions and 175 deletions

View file

@ -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 {

View file

@ -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: {},
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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,7 +194,7 @@ 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 }
@ -209,21 +205,9 @@ type memFile struct {
fs *MemFS
offset int64
fullPath string
contentLength int64
}
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()
if f.offset+int64(len(p)) > int64(len(f.node.data)) {
newSize := f.offset + int64(len(p))
if newSize > int64(cap(f.node.data)) {
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
}

View file

@ -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

View file

@ -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
}

View file

@ -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()