From 3268102d5a6d450fe92b6565a616a04b453ba3c2 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 22 Jan 2020 11:43:36 +0100 Subject: [PATCH] webdav: add MOVE support to server --- carddav/server.go | 4 ++++ fs_local.go | 32 ++++++++++++++++++++++++++++++++ internal/server.go | 42 ++++++++++++++++++++++++++++++++++++++++++ server.go | 9 +++++++++ 4 files changed, 87 insertions(+) diff --git a/carddav/server.go b/carddav/server.go index 38adee5..d6e26f6 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -276,3 +276,7 @@ func (b *backend) Delete(r *http.Request) error { func (b *backend) Mkcol(r *http.Request) error { return internal.HTTPErrorf(http.StatusForbidden, "carddav: address book creation unsupported") } + +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 08f50f0..73c5dc8 100644 --- a/fs_local.go +++ b/fs_local.go @@ -122,4 +122,36 @@ func (fs LocalFileSystem) Mkdir(name string) error { return os.Mkdir(p, 0755) } +func (fs LocalFileSystem) MoveAll(src, dst string, 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 + } + + if _, err := os.Stat(dstPath); err != nil { + if !os.IsNotExist(err) { + return false, err + } + created = true + } else { + if overwrite { + if err := os.RemoveAll(dstPath); err != nil { + return false, err + } + } else { + return false, os.ErrExist + } + } + + if err := os.Rename(srcPath, dstPath); err != nil { + return false, err + } + + return created, nil +} + var _ FileSystem = LocalFileSystem("") diff --git a/internal/server.go b/internal/server.go index 039e1bf..6ed6502 100644 --- a/internal/server.go +++ b/internal/server.go @@ -5,6 +5,7 @@ import ( "fmt" "mime" "net/http" + "net/url" "strings" ) @@ -81,6 +82,7 @@ type Backend interface { Put(r *http.Request) error Delete(r *http.Request) error Mkcol(r *http.Request) error + Move(r *http.Request, dest *Href, overwrite bool) (created bool, err error) } type Handler struct { @@ -120,6 +122,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) default: err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method") } @@ -254,3 +258,41 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) error ms := NewMultistatus(*resp) return ServeMultistatus(w, ms) } + +func parseDestination(h http.Header) (*Href, error) { + destHref := h.Get("Destination") + if destHref == "" { + return nil, HTTPErrorf(http.StatusBadRequest, "webdav: missing Destination header in MOVE request") + } + dest, err := url.Parse(destHref) + if err != nil { + return nil, HTTPErrorf(http.StatusBadRequest, "webdav: marlformed Destination header in MOVE request: %v", err) + } + return (*Href)(dest), nil +} + +func (h *Handler) handleMove(w http.ResponseWriter, r *http.Request) error { + dest, err := parseDestination(r.Header) + if err != nil { + return err + } + + overwrite := true + if s := r.Header.Get("Overwrite"); s != "" { + overwrite, err = ParseOverwrite(s) + if err != nil { + return err + } + } + + created, err := h.Backend.Move(r, dest, overwrite) + if err != nil { + return err + } + if created { + w.WriteHeader(http.StatusCreated) + } else { + w.WriteHeader(http.StatusNoContent) + } + return nil +} diff --git a/server.go b/server.go index 1995b2f..693dda2 100644 --- a/server.go +++ b/server.go @@ -18,6 +18,7 @@ type FileSystem interface { Create(name string) (io.WriteCloser, error) RemoveAll(name string) error Mkdir(name string) error + MoveAll(name, dest string, overwrite bool) (created bool, err error) } // Handler handles WebDAV HTTP requests. It can be used to create a WebDAV @@ -213,3 +214,11 @@ func (b *backend) Mkcol(r *http.Request) error { } return 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) { + return false, &internal.HTTPError{http.StatusPreconditionFailed, err} + } + return created, err +}