From 6d229f4e8a9c968bf09b23d8cb2844b8d0de4813 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 22 Jan 2020 13:00:42 +0100 Subject: [PATCH] webdav: add COPY support to server --- carddav/server.go | 4 ++ fs_local.go | 92 +++++++++++++++++++++++++++++++++++++++++++--- internal/server.go | 36 ++++++++++++++++-- server.go | 9 +++++ 4 files changed, 131 insertions(+), 10 deletions(-) diff --git a/carddav/server.go b/carddav/server.go index d6e26f6..c53f654 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -277,6 +277,10 @@ func (b *backend) Mkcol(r *http.Request) error { return internal.HTTPErrorf(http.StatusForbidden, "carddav: address book creation unsupported") } +func (b *backend) Copy(r *http.Request, dest *internal.Href, recursive, overwrite bool) (created bool, err error) { + panic("TODO") +} + func (b *backend) Move(r *http.Request, dest *internal.Href, overwrite bool) (created bool, err error) { panic("TODO") } diff --git a/fs_local.go b/fs_local.go index 0cc692b..4caf6d2 100644 --- a/fs_local.go +++ b/fs_local.go @@ -21,7 +21,7 @@ func (fs LocalFileSystem) localPath(name string) (string, error) { } name = path.Clean(name) if !path.IsAbs(name) { - return "", internal.HTTPErrorf(http.StatusBadRequest, "webdav: expected absolute path") + return "", internal.HTTPErrorf(http.StatusBadRequest, "webdav: expected absolute path, got %q", name) } return filepath.Join(string(fs), filepath.FromSlash(name)), nil } @@ -129,6 +129,87 @@ func (fs LocalFileSystem) Mkdir(name string) error { return os.Mkdir(p, 0755) } +func copyRegularFile(src, dst string, perm os.FileMode) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + // TODO: send http.StatusConflict on os.IsNotExist + return err + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return err + } + + return dstFile.Close() +} + +func (fs LocalFileSystem) Copy(src, dst string, recursive, overwrite bool) (created bool, err error) { + srcPath, err := fs.localPath(src) + if err != nil { + return false, err + } + dstPath, err := fs.localPath(dst) + if err != nil { + return false, err + } + + // TODO: "Note that an infinite-depth COPY of /A/ into /A/B/ could lead to + // infinite recursion if not handled correctly" + + srcInfo, err := os.Stat(srcPath) + if err != nil { + return false, err + } + srcPerm := srcInfo.Mode() & os.ModePerm + + if _, err := os.Stat(dstPath); err != nil { + if !os.IsNotExist(err) { + return false, err + } + created = true + } else { + if !overwrite { + return false, os.ErrExist + } + if err := os.RemoveAll(dstPath); err != nil { + return false, err + } + } + + err = filepath.Walk(srcPath, func(p string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + if fi.IsDir() { + if err := os.Mkdir(dstPath, srcPerm); err != nil { + return err + } + } else { + if err := copyRegularFile(srcPath, dstPath, srcPerm); err != nil { + return err + } + } + + if fi.IsDir() && !recursive { + return filepath.SkipDir + } + return nil + }) + if err != nil { + return false, err + } + + return created, nil +} + func (fs LocalFileSystem) MoveAll(src, dst string, overwrite bool) (created bool, err error) { srcPath, err := fs.localPath(src) if err != nil { @@ -145,13 +226,12 @@ func (fs LocalFileSystem) MoveAll(src, dst string, overwrite bool) (created bool } created = true } else { - if overwrite { - if err := os.RemoveAll(dstPath); err != nil { - return false, err - } - } else { + if !overwrite { return false, os.ErrExist } + if err := os.RemoveAll(dstPath); err != nil { + return false, err + } } if err := os.Rename(srcPath, dstPath); err != nil { diff --git a/internal/server.go b/internal/server.go index 6ed6502..abd6d6c 100644 --- a/internal/server.go +++ b/internal/server.go @@ -82,6 +82,7 @@ type Backend interface { Put(r *http.Request) error Delete(r *http.Request) error Mkcol(r *http.Request) error + Copy(r *http.Request, dest *Href, recursive, overwrite bool) (created bool, err error) Move(r *http.Request, dest *Href, overwrite bool) (created bool, err error) } @@ -122,8 +123,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err == nil { w.WriteHeader(http.StatusCreated) } - case "MOVE": - err = h.handleMove(w, r) + case "COPY", "MOVE": + err = h.handleCopyMove(w, r) default: err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method") } @@ -271,7 +272,7 @@ func parseDestination(h http.Header) (*Href, error) { return (*Href)(dest), nil } -func (h *Handler) handleMove(w http.ResponseWriter, r *http.Request) error { +func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) error { dest, err := parseDestination(r.Header) if err != nil { return err @@ -285,10 +286,37 @@ func (h *Handler) handleMove(w http.ResponseWriter, r *http.Request) error { } } - created, err := h.Backend.Move(r, dest, overwrite) + depth := DepthInfinity + if s := r.Header.Get("Depth"); s != "" { + depth, err = ParseDepth(s) + if err != nil { + return err + } + } + + var created bool + if r.Method == "COPY" { + var recursive bool + switch depth { + case DepthZero: + recursive = false + case DepthOne: + return HTTPErrorf(http.StatusBadRequest, `webdav: "Depth: 1" is not supported in COPY request`) + case DepthInfinity: + recursive = true + } + + created, err = h.Backend.Copy(r, dest, recursive, overwrite) + } else { + if depth != DepthInfinity { + return HTTPErrorf(http.StatusBadRequest, `webdav: only "Depth: infinity" is accepted in MOVE request`) + } + created, err = h.Backend.Move(r, dest, overwrite) + } if err != nil { return err } + if created { w.WriteHeader(http.StatusCreated) } else { diff --git a/server.go b/server.go index 10d80ac..09aca28 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ type FileSystem interface { Create(name string) (io.WriteCloser, error) RemoveAll(name string) error Mkdir(name string) error + Copy(name, dest string, recursive, overwrite bool) (created bool, err error) MoveAll(name, dest string, overwrite bool) (created bool, err error) } @@ -223,6 +224,14 @@ func (b *backend) Mkcol(r *http.Request) error { return err } +func (b *backend) Copy(r *http.Request, dest *internal.Href, recursive, overwrite bool) (created bool, err error) { + created, err = b.FileSystem.Copy(r.URL.Path, dest.Path, recursive, overwrite) + if os.IsExist(err) { + return false, &internal.HTTPError{http.StatusPreconditionFailed, err} + } + return created, err +} + func (b *backend) Move(r *http.Request, dest *internal.Href, overwrite bool) (created bool, err error) { created, err = b.FileSystem.MoveAll(r.URL.Path, dest.Path, overwrite) if os.IsExist(err) {