webdav: factor out a moveFiles function, and make the tests call that
instead of FileSystem.Rename directly.

Dir.Rename's behavior wrt overwriting existing files and directories is
OS-dependent.

Fixes golang/go#9786

Change-Id: If42728caa6f0f38f8e3d6b1fcdda8c2d272080d6
Reviewed-on: https://go-review.googlesource.com/4341
Reviewed-by: Nick Cooper <nmvc@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
diff --git a/webdav/file.go b/webdav/file.go
index 0f67853..aa4ea6a 100644
--- a/webdav/file.go
+++ b/webdav/file.go
@@ -30,6 +30,10 @@
 //
 // Each method has the same semantics as the os package's function of the same
 // name.
+//
+// Note that the os.Rename documentation says that "OS-specific restrictions
+// might apply". In particular, whether or not renaming a file or directory
+// overwriting another existing file or directory is an error is OS-dependent.
 type FileSystem interface {
 	Mkdir(name string, perm os.FileMode) error
 	OpenFile(name string, flag int, perm os.FileMode) (File, error)
@@ -548,6 +552,36 @@
 	return lenp, nil
 }
 
+// moveFiles moves files and/or directories from src to dst.
+//
+// See section 9.9.4 for when various HTTP status codes apply.
+func moveFiles(fs FileSystem, src, dst string, overwrite bool) (status int, err error) {
+	created := false
+	if _, err := fs.Stat(dst); err != nil {
+		if !os.IsNotExist(err) {
+			return http.StatusForbidden, err
+		}
+		created = true
+	} else if overwrite {
+		// 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 := fs.RemoveAll(dst); err != nil {
+			return http.StatusForbidden, err
+		}
+	} else {
+		return http.StatusPreconditionFailed, os.ErrExist
+	}
+	if err := fs.Rename(src, dst); err != nil {
+		return http.StatusForbidden, err
+	}
+	if created {
+		return http.StatusCreated, nil
+	}
+	return http.StatusNoContent, nil
+}
+
 // copyFiles copies files and/or directories from src to dst.
 //
 // See section 9.8.5 for when various HTTP status codes apply.