@@ -11,6 +11,7 @@ import (
11
11
"errors"
12
12
"io"
13
13
"net/http"
14
+ "net/url"
14
15
"os"
15
16
"time"
16
17
)
@@ -37,8 +38,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
37
38
} else if h .LockSystem == nil {
38
39
status , err = http .StatusInternalServerError , errNoLockSystem
39
40
} else {
40
- // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods.
41
- // MOVE needs to enforce its Depth constraint. See the parseDepth comment.
41
+ // TODO: PROPFIND, PROPPATCH methods.
42
42
switch r .Method {
43
43
case "OPTIONS" :
44
44
status , err = h .handleOptions (w , r )
@@ -50,6 +50,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
50
50
status , err = h .handlePut (w , r )
51
51
case "MKCOL" :
52
52
status , err = h .handleMkcol (w , r )
53
+ case "COPY" , "MOVE" :
54
+ status , err = h .handleCopyMove (w , r )
53
55
case "LOCK" :
54
56
status , err = h .handleLock (w , r )
55
57
case "UNLOCK" :
@@ -193,6 +195,91 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
193
195
return http .StatusCreated , nil
194
196
}
195
197
198
+ func (h * Handler ) handleCopyMove (w http.ResponseWriter , r * http.Request ) (status int , err error ) {
199
+ // TODO: COPY/MOVE for Properties, as per sections 9.8.2 and 9.9.1.
200
+
201
+ hdr := r .Header .Get ("Destination" )
202
+ if hdr == "" {
203
+ return http .StatusBadRequest , errInvalidDestination
204
+ }
205
+ u , err := url .Parse (hdr )
206
+ if err != nil {
207
+ return http .StatusBadRequest , errInvalidDestination
208
+ }
209
+ if u .Host != r .Host {
210
+ return http .StatusBadGateway , errInvalidDestination
211
+ }
212
+ // TODO: do we need a webdav.StripPrefix HTTP handler that's like the
213
+ // standard library's http.StripPrefix handler, but also strips the
214
+ // prefix in the Destination header?
215
+
216
+ dst , src := u .Path , r .URL .Path
217
+ if dst == src {
218
+ return http .StatusForbidden , errDestinationEqualsSource
219
+ }
220
+
221
+ // TODO: confirmLocks should also check dst.
222
+ releaser , status , err := h .confirmLocks (r )
223
+ if err != nil {
224
+ return status , err
225
+ }
226
+ defer releaser .Release ()
227
+
228
+ if r .Method == "COPY" {
229
+ // Section 9.8.3 says that "The COPY method on a collection without a Depth
230
+ // header must act as if a Depth header with value "infinity" was included".
231
+ depth := infiniteDepth
232
+ if hdr := r .Header .Get ("Depth" ); hdr != "" {
233
+ depth = parseDepth (hdr )
234
+ if depth != 0 && depth != infiniteDepth {
235
+ // Section 9.8.3 says that "A client may submit a Depth header on a
236
+ // COPY on a collection with a value of "0" or "infinity"."
237
+ return http .StatusBadRequest , errInvalidDepth
238
+ }
239
+ }
240
+ return copyFiles (h .FileSystem , src , dst , r .Header .Get ("Overwrite" ) != "F" , depth , 0 )
241
+ }
242
+
243
+ // Section 9.9.2 says that "The MOVE method on a collection must act as if
244
+ // a "Depth: infinity" header was used on it. A client must not submit a
245
+ // Depth header on a MOVE on a collection with any value but "infinity"."
246
+ if hdr := r .Header .Get ("Depth" ); hdr != "" {
247
+ if parseDepth (hdr ) != infiniteDepth {
248
+ return http .StatusBadRequest , errInvalidDepth
249
+ }
250
+ }
251
+
252
+ created := false
253
+ if _ , err := h .FileSystem .Stat (dst ); err != nil {
254
+ if ! os .IsNotExist (err ) {
255
+ return http .StatusForbidden , err
256
+ }
257
+ created = true
258
+ } else {
259
+ switch r .Header .Get ("Overwrite" ) {
260
+ case "T" :
261
+ // Section 9.9.3 says that "If a resource exists at the destination
262
+ // and the Overwrite header is "T", then prior to performing the move,
263
+ // the server must perform a DELETE with "Depth: infinity" on the
264
+ // destination resource.
265
+ if err := h .FileSystem .RemoveAll (dst ); err != nil {
266
+ return http .StatusForbidden , err
267
+ }
268
+ case "F" :
269
+ return http .StatusPreconditionFailed , os .ErrExist
270
+ default :
271
+ return http .StatusBadRequest , errInvalidOverwrite
272
+ }
273
+ }
274
+ if err := h .FileSystem .Rename (src , dst ); err != nil {
275
+ return http .StatusForbidden , err
276
+ }
277
+ if created {
278
+ return http .StatusCreated , nil
279
+ }
280
+ return http .StatusNoContent , nil
281
+ }
282
+
196
283
func (h * Handler ) handleLock (w http.ResponseWriter , r * http.Request ) (retStatus int , retErr error ) {
197
284
duration , err := parseTimeout (r .Header .Get ("Timeout" ))
198
285
if err != nil {
@@ -308,7 +395,8 @@ const (
308
395
//
309
396
// Different WebDAV methods have further constraints on valid depths:
310
397
// - PROPFIND has no further restrictions, as per section 9.1.
311
- // - MOVE accepts only "infinity", as per section 9.2.2.
398
+ // - COPY accepts only "0" or "infinity", as per section 9.8.3.
399
+ // - MOVE accepts only "infinity", as per section 9.9.2.
312
400
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
313
401
// These constraints are enforced by the handleXxx methods.
314
402
func parseDepth (s string ) int {
@@ -349,16 +437,20 @@ func StatusText(code int) string {
349
437
}
350
438
351
439
var (
352
- errDirectoryNotEmpty = errors .New ("webdav: directory not empty" )
353
- errInvalidDepth = errors .New ("webdav: invalid depth" )
354
- errInvalidIfHeader = errors .New ("webdav: invalid If header" )
355
- errInvalidLockInfo = errors .New ("webdav: invalid lock info" )
356
- errInvalidLockToken = errors .New ("webdav: invalid lock token" )
357
- errInvalidPropfind = errors .New ("webdav: invalid propfind" )
358
- errInvalidResponse = errors .New ("webdav: invalid response" )
359
- errInvalidTimeout = errors .New ("webdav: invalid timeout" )
360
- errNoFileSystem = errors .New ("webdav: no file system" )
361
- errNoLockSystem = errors .New ("webdav: no lock system" )
362
- errNotADirectory = errors .New ("webdav: not a directory" )
363
- errUnsupportedLockInfo = errors .New ("webdav: unsupported lock info" )
440
+ errDestinationEqualsSource = errors .New ("webdav: destination equals source" )
441
+ errDirectoryNotEmpty = errors .New ("webdav: directory not empty" )
442
+ errInvalidDepth = errors .New ("webdav: invalid depth" )
443
+ errInvalidDestination = errors .New ("webdav: invalid destination" )
444
+ errInvalidIfHeader = errors .New ("webdav: invalid If header" )
445
+ errInvalidLockInfo = errors .New ("webdav: invalid lock info" )
446
+ errInvalidLockToken = errors .New ("webdav: invalid lock token" )
447
+ errInvalidOverwrite = errors .New ("webdav: invalid overwrite" )
448
+ errInvalidPropfind = errors .New ("webdav: invalid propfind" )
449
+ errInvalidResponse = errors .New ("webdav: invalid response" )
450
+ errInvalidTimeout = errors .New ("webdav: invalid timeout" )
451
+ errNoFileSystem = errors .New ("webdav: no file system" )
452
+ errNoLockSystem = errors .New ("webdav: no lock system" )
453
+ errNotADirectory = errors .New ("webdav: not a directory" )
454
+ errRecursionTooDeep = errors .New ("webdav: recursion too deep" )
455
+ errUnsupportedLockInfo = errors .New ("webdav: unsupported lock info" )
364
456
)
0 commit comments