webdav: add COPY support to server

This commit is contained in:
Simon Ser 2020-01-22 13:00:42 +01:00
parent fda38c8f93
commit 6d229f4e8a
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
4 changed files with 131 additions and 10 deletions

View File

@ -277,6 +277,10 @@ func (b *backend) Mkcol(r *http.Request) error {
return internal.HTTPErrorf(http.StatusForbidden, "carddav: address book creation unsupported") 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) { func (b *backend) Move(r *http.Request, dest *internal.Href, overwrite bool) (created bool, err error) {
panic("TODO") panic("TODO")
} }

View File

@ -21,7 +21,7 @@ func (fs LocalFileSystem) localPath(name string) (string, error) {
} }
name = path.Clean(name) name = path.Clean(name)
if !path.IsAbs(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 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) 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) { func (fs LocalFileSystem) MoveAll(src, dst string, overwrite bool) (created bool, err error) {
srcPath, err := fs.localPath(src) srcPath, err := fs.localPath(src)
if err != nil { if err != nil {
@ -145,13 +226,12 @@ func (fs LocalFileSystem) MoveAll(src, dst string, overwrite bool) (created bool
} }
created = true created = true
} else { } else {
if overwrite { if !overwrite {
if err := os.RemoveAll(dstPath); err != nil {
return false, err
}
} else {
return false, os.ErrExist return false, os.ErrExist
} }
if err := os.RemoveAll(dstPath); err != nil {
return false, err
}
} }
if err := os.Rename(srcPath, dstPath); err != nil { if err := os.Rename(srcPath, dstPath); err != nil {

View File

@ -82,6 +82,7 @@ type Backend interface {
Put(r *http.Request) error Put(r *http.Request) error
Delete(r *http.Request) error Delete(r *http.Request) error
Mkcol(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) 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 { if err == nil {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
} }
case "MOVE": case "COPY", "MOVE":
err = h.handleMove(w, r) err = h.handleCopyMove(w, r)
default: default:
err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method") err = HTTPErrorf(http.StatusMethodNotAllowed, "webdav: unsupported method")
} }
@ -271,7 +272,7 @@ func parseDestination(h http.Header) (*Href, error) {
return (*Href)(dest), nil 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) dest, err := parseDestination(r.Header)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
if created { if created {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
} else { } else {

View File

@ -19,6 +19,7 @@ type FileSystem interface {
Create(name string) (io.WriteCloser, error) Create(name string) (io.WriteCloser, error)
RemoveAll(name string) error RemoveAll(name string) error
Mkdir(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) MoveAll(name, dest string, overwrite bool) (created bool, err error)
} }
@ -223,6 +224,14 @@ func (b *backend) Mkcol(r *http.Request) error {
return err 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) { 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) created, err = b.FileSystem.MoveAll(r.URL.Path, dest.Path, overwrite)
if os.IsExist(err) { if os.IsExist(err) {