package api import ( "context" "net/http/httptest" "os" "path/filepath" "testing" "chfs-ng/internal/auth" "chfs-ng/internal/storage" "github.com/emersion/go-webdav" ) func TestWebDAVAuthFSCheckTree(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "test", "private"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "test", "public.txt"), []byte("public"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "test", "private", "secret.txt"), []byte("secret"), 0o644); err != nil { t.Fatal(err) } account := &auth.Account{Mask: auth.Read | auth.Write | auth.Delete, Rules: []auth.PathRule{ {Dir: "test", Mask: auth.Read | auth.Write | auth.Delete}, {Dir: "test/private", Mask: auth.None}, }} fs := &webdavAuthFS{inner: &webdavStorageFS{store: storage.Local{Root: root}}, account: account} if err := fs.checkTree(context.Background(), "/test", auth.Delete); err == nil { t.Fatal("expected recursive delete permission check to reject inaccessible descendant") } if err := fs.checkTree(context.Background(), "/test/public.txt", auth.Delete); err != nil { t.Fatalf("public file should be deletable: %v", err) } } func TestWebDAVAuthFSOverwriteRequiresDelete(t *testing.T) { root := t.TempDir() if err := os.WriteFile(filepath.Join(root, "src.txt"), []byte("src"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "dst.txt"), []byte("dst"), 0o644); err != nil { t.Fatal(err) } account := &auth.Account{Mask: auth.Write} fs := &webdavAuthFS{inner: &webdavStorageFS{store: storage.Local{Root: root}}, account: account} if err := fs.authorizeOverwrite(context.Background(), "/dst.txt"); err == nil { t.Fatal("expected overwrite authorization to require delete permission") } account.Mask = auth.Delete if err := fs.authorizeOverwrite(context.Background(), "/dst.txt"); err != nil { t.Fatalf("delete permission should authorize overwrite: %v", err) } } func TestSameWebDAVDestinationHost(t *testing.T) { req := httptest.NewRequest("MOVE", "/webdav/src.txt", nil) req.Host = "example.test" req.Header.Set("Destination", "http://other.test/webdav/dst.txt") if sameWebDAVDestinationHost(req) { t.Fatal("cross-host destination should be rejected") } req.Header.Set("Destination", "http://example.test/webdav/dst.txt") if !sameWebDAVDestinationHost(req) { t.Fatal("same-host absolute destination should be accepted") } req.Header.Set("Destination", "/webdav/dst.txt") if !sameWebDAVDestinationHost(req) { t.Fatal("relative destination should be accepted") } } func TestWebDAVStorageFSRejectsDestinationInsideSource(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "src", "child"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "src", "file.txt"), []byte("x"), 0o644); err != nil { t.Fatal(err) } fs := &webdavStorageFS{store: storage.Local{Root: root}} if _, err := fs.Copy(context.Background(), "/src", "/src/child/copy", &webdav.CopyOptions{}); err == nil { t.Fatal("copying a directory into its descendant should fail") } if _, err := fs.Move(context.Background(), "/src", "/src/child/moved", &webdav.MoveOptions{}); err == nil { t.Fatal("moving a directory into its descendant should fail") } if _, err := fs.Copy(context.Background(), "/src", "/src", &webdav.CopyOptions{}); err == nil { t.Fatal("copying a resource onto itself should fail") } } func TestWebDAVAuthFSMoveMissingSourcePrecedence(t *testing.T) { root := t.TempDir() if err := os.WriteFile(filepath.Join(root, "existing.txt"), []byte("existing"), 0o644); err != nil { t.Fatal(err) } account := &auth.Account{Mask: auth.Read | auth.Write | auth.Delete} fs := &webdavAuthFS{inner: &webdavStorageFS{store: storage.Local{Root: root}}, account: account} _, err := fs.Move(context.Background(), "/missing.txt", "/new.txt", &webdav.MoveOptions{}) if err == nil || err.Error() != "403 Forbidden: source not found" { t.Fatalf("missing source to free destination: got %v", err) } _, err = fs.Move(context.Background(), "/missing.txt", "/existing.txt", &webdav.MoveOptions{}) if err == nil || err.Error() != "412 Precondition Failed: destination exists" { t.Fatalf("missing source to occupied destination: got %v", err) } } func TestWebDAVStorageFSCopyAndMove(t *testing.T) { root1 := t.TempDir() root2 := t.TempDir() if err := os.WriteFile(filepath.Join(root1, "a.txt"), []byte("one"), 0o644); err != nil { t.Fatal(err) } m := storage.NewMulti([]string{root1, root2}) fs := &webdavStorageFS{store: m} if _, err := fs.Copy(context.Background(), "/"+m.Names[0]+"/a.txt", "/"+m.Names[0]+"/b.txt", &webdav.CopyOptions{}); err != nil { t.Fatalf("copy: %v", err) } if _, err := os.Stat(filepath.Join(root1, "b.txt")); err != nil { t.Fatal(err) } if _, err := fs.Move(context.Background(), "/"+m.Names[0]+"/b.txt", "/"+m.Names[1]+"/b.txt", &webdav.MoveOptions{}); err == nil { t.Fatal("expected cross-root move rejection") } }