aboutsummaryrefslogtreecommitdiff
path: root/webdav/webdav.go
diff options
context:
space:
mode:
Diffstat (limited to 'webdav/webdav.go')
-rw-r--r--webdav/webdav.go122
1 files changed, 107 insertions, 15 deletions
diff --git a/webdav/webdav.go b/webdav/webdav.go
index 93d971f..501b6aa 100644
--- a/webdav/webdav.go
+++ b/webdav/webdav.go
@@ -11,6 +11,7 @@ import (
"errors"
"io"
"net/http"
+ "net/url"
"os"
"time"
)
@@ -37,8 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem
} else {
- // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods.
- // MOVE needs to enforce its Depth constraint. See the parseDepth comment.
+ // TODO: PROPFIND, PROPPATCH methods.
switch r.Method {
case "OPTIONS":
status, err = h.handleOptions(w, r)
@@ -50,6 +50,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
status, err = h.handlePut(w, r)
case "MKCOL":
status, err = h.handleMkcol(w, r)
+ case "COPY", "MOVE":
+ status, err = h.handleCopyMove(w, r)
case "LOCK":
status, err = h.handleLock(w, r)
case "UNLOCK":
@@ -193,6 +195,91 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
return http.StatusCreated, nil
}
+func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ // TODO: COPY/MOVE for Properties, as per sections 9.8.2 and 9.9.1.
+
+ hdr := r.Header.Get("Destination")
+ if hdr == "" {
+ return http.StatusBadRequest, errInvalidDestination
+ }
+ u, err := url.Parse(hdr)
+ if err != nil {
+ return http.StatusBadRequest, errInvalidDestination
+ }
+ if u.Host != r.Host {
+ return http.StatusBadGateway, errInvalidDestination
+ }
+ // TODO: do we need a webdav.StripPrefix HTTP handler that's like the
+ // standard library's http.StripPrefix handler, but also strips the
+ // prefix in the Destination header?
+
+ dst, src := u.Path, r.URL.Path
+ if dst == src {
+ return http.StatusForbidden, errDestinationEqualsSource
+ }
+
+ // TODO: confirmLocks should also check dst.
+ releaser, status, err := h.confirmLocks(r)
+ if err != nil {
+ return status, err
+ }
+ defer releaser.Release()
+
+ if r.Method == "COPY" {
+ // Section 9.8.3 says that "The COPY method on a collection without a Depth
+ // header must act as if a Depth header with value "infinity" was included".
+ depth := infiniteDepth
+ if hdr := r.Header.Get("Depth"); hdr != "" {
+ depth = parseDepth(hdr)
+ if depth != 0 && depth != infiniteDepth {
+ // Section 9.8.3 says that "A client may submit a Depth header on a
+ // COPY on a collection with a value of "0" or "infinity"."
+ return http.StatusBadRequest, errInvalidDepth
+ }
+ }
+ return copyFiles(h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
+ }
+
+ // Section 9.9.2 says that "The MOVE method on a collection must act as if
+ // a "Depth: infinity" header was used on it. A client must not submit a
+ // Depth header on a MOVE on a collection with any value but "infinity"."
+ if hdr := r.Header.Get("Depth"); hdr != "" {
+ if parseDepth(hdr) != infiniteDepth {
+ return http.StatusBadRequest, errInvalidDepth
+ }
+ }
+
+ created := false
+ if _, err := h.FileSystem.Stat(dst); err != nil {
+ if !os.IsNotExist(err) {
+ return http.StatusForbidden, err
+ }
+ created = true
+ } else {
+ switch r.Header.Get("Overwrite") {
+ case "T":
+ // Section 9.9.3 says that "If a resource exists at the destination
+ // and the Overwrite header is "T", then prior to performing the move,
+ // the server must perform a DELETE with "Depth: infinity" on the
+ // destination resource.
+ if err := h.FileSystem.RemoveAll(dst); err != nil {
+ return http.StatusForbidden, err
+ }
+ case "F":
+ return http.StatusPreconditionFailed, os.ErrExist
+ default:
+ return http.StatusBadRequest, errInvalidOverwrite
+ }
+ }
+ if err := h.FileSystem.Rename(src, dst); err != nil {
+ return http.StatusForbidden, err
+ }
+ if created {
+ return http.StatusCreated, nil
+ }
+ return http.StatusNoContent, nil
+}
+
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
duration, err := parseTimeout(r.Header.Get("Timeout"))
if err != nil {
@@ -308,7 +395,8 @@ const (
//
// Different WebDAV methods have further constraints on valid depths:
// - PROPFIND has no further restrictions, as per section 9.1.
-// - MOVE accepts only "infinity", as per section 9.2.2.
+// - COPY accepts only "0" or "infinity", as per section 9.8.3.
+// - MOVE accepts only "infinity", as per section 9.9.2.
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
// These constraints are enforced by the handleXxx methods.
func parseDepth(s string) int {
@@ -349,16 +437,20 @@ func StatusText(code int) string {
}
var (
- errDirectoryNotEmpty = errors.New("webdav: directory not empty")
- errInvalidDepth = errors.New("webdav: invalid depth")
- errInvalidIfHeader = errors.New("webdav: invalid If header")
- errInvalidLockInfo = errors.New("webdav: invalid lock info")
- errInvalidLockToken = errors.New("webdav: invalid lock token")
- errInvalidPropfind = errors.New("webdav: invalid propfind")
- errInvalidResponse = errors.New("webdav: invalid response")
- errInvalidTimeout = errors.New("webdav: invalid timeout")
- errNoFileSystem = errors.New("webdav: no file system")
- errNoLockSystem = errors.New("webdav: no lock system")
- errNotADirectory = errors.New("webdav: not a directory")
- errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
+ errDestinationEqualsSource = errors.New("webdav: destination equals source")
+ errDirectoryNotEmpty = errors.New("webdav: directory not empty")
+ errInvalidDepth = errors.New("webdav: invalid depth")
+ errInvalidDestination = errors.New("webdav: invalid destination")
+ errInvalidIfHeader = errors.New("webdav: invalid If header")
+ errInvalidLockInfo = errors.New("webdav: invalid lock info")
+ errInvalidLockToken = errors.New("webdav: invalid lock token")
+ errInvalidOverwrite = errors.New("webdav: invalid overwrite")
+ errInvalidPropfind = errors.New("webdav: invalid propfind")
+ errInvalidResponse = errors.New("webdav: invalid response")
+ errInvalidTimeout = errors.New("webdav: invalid timeout")
+ errNoFileSystem = errors.New("webdav: no file system")
+ errNoLockSystem = errors.New("webdav: no lock system")
+ errNotADirectory = errors.New("webdav: not a directory")
+ errRecursionTooDeep = errors.New("webdav: recursion too deep")
+ errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
)