OSDN Git Service

new repo
[bytom/vapor.git] / vendor / golang.org / x / net / webdav / webdav.go
1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package webdav provides a WebDAV server implementation.
6 package webdav // import "golang.org/x/net/webdav"
7
8 import (
9         "errors"
10         "fmt"
11         "io"
12         "net/http"
13         "net/url"
14         "os"
15         "path"
16         "strings"
17         "time"
18 )
19
20 type Handler struct {
21         // Prefix is the URL path prefix to strip from WebDAV resource paths.
22         Prefix string
23         // FileSystem is the virtual file system.
24         FileSystem FileSystem
25         // LockSystem is the lock management system.
26         LockSystem LockSystem
27         // Logger is an optional error logger. If non-nil, it will be called
28         // for all HTTP requests.
29         Logger func(*http.Request, error)
30 }
31
32 func (h *Handler) stripPrefix(p string) (string, int, error) {
33         if h.Prefix == "" {
34                 return p, http.StatusOK, nil
35         }
36         if r := strings.TrimPrefix(p, h.Prefix); len(r) < len(p) {
37                 return r, http.StatusOK, nil
38         }
39         return p, http.StatusNotFound, errPrefixMismatch
40 }
41
42 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
43         status, err := http.StatusBadRequest, errUnsupportedMethod
44         if h.FileSystem == nil {
45                 status, err = http.StatusInternalServerError, errNoFileSystem
46         } else if h.LockSystem == nil {
47                 status, err = http.StatusInternalServerError, errNoLockSystem
48         } else {
49                 switch r.Method {
50                 case "OPTIONS":
51                         status, err = h.handleOptions(w, r)
52                 case "GET", "HEAD", "POST":
53                         status, err = h.handleGetHeadPost(w, r)
54                 case "DELETE":
55                         status, err = h.handleDelete(w, r)
56                 case "PUT":
57                         status, err = h.handlePut(w, r)
58                 case "MKCOL":
59                         status, err = h.handleMkcol(w, r)
60                 case "COPY", "MOVE":
61                         status, err = h.handleCopyMove(w, r)
62                 case "LOCK":
63                         status, err = h.handleLock(w, r)
64                 case "UNLOCK":
65                         status, err = h.handleUnlock(w, r)
66                 case "PROPFIND":
67                         status, err = h.handlePropfind(w, r)
68                 case "PROPPATCH":
69                         status, err = h.handleProppatch(w, r)
70                 }
71         }
72
73         if status != 0 {
74                 w.WriteHeader(status)
75                 if status != http.StatusNoContent {
76                         w.Write([]byte(StatusText(status)))
77                 }
78         }
79         if h.Logger != nil {
80                 h.Logger(r, err)
81         }
82 }
83
84 func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) {
85         token, err = h.LockSystem.Create(now, LockDetails{
86                 Root:      root,
87                 Duration:  infiniteTimeout,
88                 ZeroDepth: true,
89         })
90         if err != nil {
91                 if err == ErrLocked {
92                         return "", StatusLocked, err
93                 }
94                 return "", http.StatusInternalServerError, err
95         }
96         return token, 0, nil
97 }
98
99 func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func(), status int, err error) {
100         hdr := r.Header.Get("If")
101         if hdr == "" {
102                 // An empty If header means that the client hasn't previously created locks.
103                 // Even if this client doesn't care about locks, we still need to check that
104                 // the resources aren't locked by another client, so we create temporary
105                 // locks that would conflict with another client's locks. These temporary
106                 // locks are unlocked at the end of the HTTP request.
107                 now, srcToken, dstToken := time.Now(), "", ""
108                 if src != "" {
109                         srcToken, status, err = h.lock(now, src)
110                         if err != nil {
111                                 return nil, status, err
112                         }
113                 }
114                 if dst != "" {
115                         dstToken, status, err = h.lock(now, dst)
116                         if err != nil {
117                                 if srcToken != "" {
118                                         h.LockSystem.Unlock(now, srcToken)
119                                 }
120                                 return nil, status, err
121                         }
122                 }
123
124                 return func() {
125                         if dstToken != "" {
126                                 h.LockSystem.Unlock(now, dstToken)
127                         }
128                         if srcToken != "" {
129                                 h.LockSystem.Unlock(now, srcToken)
130                         }
131                 }, 0, nil
132         }
133
134         ih, ok := parseIfHeader(hdr)
135         if !ok {
136                 return nil, http.StatusBadRequest, errInvalidIfHeader
137         }
138         // ih is a disjunction (OR) of ifLists, so any ifList will do.
139         for _, l := range ih.lists {
140                 lsrc := l.resourceTag
141                 if lsrc == "" {
142                         lsrc = src
143                 } else {
144                         u, err := url.Parse(lsrc)
145                         if err != nil {
146                                 continue
147                         }
148                         if u.Host != r.Host {
149                                 continue
150                         }
151                         lsrc, status, err = h.stripPrefix(u.Path)
152                         if err != nil {
153                                 return nil, status, err
154                         }
155                 }
156                 release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...)
157                 if err == ErrConfirmationFailed {
158                         continue
159                 }
160                 if err != nil {
161                         return nil, http.StatusInternalServerError, err
162                 }
163                 return release, 0, nil
164         }
165         // Section 10.4.1 says that "If this header is evaluated and all state lists
166         // fail, then the request must fail with a 412 (Precondition Failed) status."
167         // We follow the spec even though the cond_put_corrupt_token test case from
168         // the litmus test warns on seeing a 412 instead of a 423 (Locked).
169         return nil, http.StatusPreconditionFailed, ErrLocked
170 }
171
172 func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) {
173         reqPath, status, err := h.stripPrefix(r.URL.Path)
174         if err != nil {
175                 return status, err
176         }
177         ctx := getContext(r)
178         allow := "OPTIONS, LOCK, PUT, MKCOL"
179         if fi, err := h.FileSystem.Stat(ctx, reqPath); err == nil {
180                 if fi.IsDir() {
181                         allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
182                 } else {
183                         allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
184                 }
185         }
186         w.Header().Set("Allow", allow)
187         // http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
188         w.Header().Set("DAV", "1, 2")
189         // http://msdn.microsoft.com/en-au/library/cc250217.aspx
190         w.Header().Set("MS-Author-Via", "DAV")
191         return 0, nil
192 }
193
194 func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
195         reqPath, status, err := h.stripPrefix(r.URL.Path)
196         if err != nil {
197                 return status, err
198         }
199         // TODO: check locks for read-only access??
200         ctx := getContext(r)
201         f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDONLY, 0)
202         if err != nil {
203                 return http.StatusNotFound, err
204         }
205         defer f.Close()
206         fi, err := f.Stat()
207         if err != nil {
208                 return http.StatusNotFound, err
209         }
210         if fi.IsDir() {
211                 return http.StatusMethodNotAllowed, nil
212         }
213         etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
214         if err != nil {
215                 return http.StatusInternalServerError, err
216         }
217         w.Header().Set("ETag", etag)
218         // Let ServeContent determine the Content-Type header.
219         http.ServeContent(w, r, reqPath, fi.ModTime(), f)
220         return 0, nil
221 }
222
223 func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
224         reqPath, status, err := h.stripPrefix(r.URL.Path)
225         if err != nil {
226                 return status, err
227         }
228         release, status, err := h.confirmLocks(r, reqPath, "")
229         if err != nil {
230                 return status, err
231         }
232         defer release()
233
234         ctx := getContext(r)
235
236         // TODO: return MultiStatus where appropriate.
237
238         // "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
239         // returns nil (no error)." WebDAV semantics are that it should return a
240         // "404 Not Found". We therefore have to Stat before we RemoveAll.
241         if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
242                 if os.IsNotExist(err) {
243                         return http.StatusNotFound, err
244                 }
245                 return http.StatusMethodNotAllowed, err
246         }
247         if err := h.FileSystem.RemoveAll(ctx, reqPath); err != nil {
248                 return http.StatusMethodNotAllowed, err
249         }
250         return http.StatusNoContent, nil
251 }
252
253 func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
254         reqPath, status, err := h.stripPrefix(r.URL.Path)
255         if err != nil {
256                 return status, err
257         }
258         release, status, err := h.confirmLocks(r, reqPath, "")
259         if err != nil {
260                 return status, err
261         }
262         defer release()
263         // TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
264         // comments in http.checkEtag.
265         ctx := getContext(r)
266
267         f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
268         if err != nil {
269                 return http.StatusNotFound, err
270         }
271         _, copyErr := io.Copy(f, r.Body)
272         fi, statErr := f.Stat()
273         closeErr := f.Close()
274         // TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
275         if copyErr != nil {
276                 return http.StatusMethodNotAllowed, copyErr
277         }
278         if statErr != nil {
279                 return http.StatusMethodNotAllowed, statErr
280         }
281         if closeErr != nil {
282                 return http.StatusMethodNotAllowed, closeErr
283         }
284         etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
285         if err != nil {
286                 return http.StatusInternalServerError, err
287         }
288         w.Header().Set("ETag", etag)
289         return http.StatusCreated, nil
290 }
291
292 func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
293         reqPath, status, err := h.stripPrefix(r.URL.Path)
294         if err != nil {
295                 return status, err
296         }
297         release, status, err := h.confirmLocks(r, reqPath, "")
298         if err != nil {
299                 return status, err
300         }
301         defer release()
302
303         ctx := getContext(r)
304
305         if r.ContentLength > 0 {
306                 return http.StatusUnsupportedMediaType, nil
307         }
308         if err := h.FileSystem.Mkdir(ctx, reqPath, 0777); err != nil {
309                 if os.IsNotExist(err) {
310                         return http.StatusConflict, err
311                 }
312                 return http.StatusMethodNotAllowed, err
313         }
314         return http.StatusCreated, nil
315 }
316
317 func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status int, err error) {
318         hdr := r.Header.Get("Destination")
319         if hdr == "" {
320                 return http.StatusBadRequest, errInvalidDestination
321         }
322         u, err := url.Parse(hdr)
323         if err != nil {
324                 return http.StatusBadRequest, errInvalidDestination
325         }
326         if u.Host != r.Host {
327                 return http.StatusBadGateway, errInvalidDestination
328         }
329
330         src, status, err := h.stripPrefix(r.URL.Path)
331         if err != nil {
332                 return status, err
333         }
334
335         dst, status, err := h.stripPrefix(u.Path)
336         if err != nil {
337                 return status, err
338         }
339
340         if dst == "" {
341                 return http.StatusBadGateway, errInvalidDestination
342         }
343         if dst == src {
344                 return http.StatusForbidden, errDestinationEqualsSource
345         }
346
347         ctx := getContext(r)
348
349         if r.Method == "COPY" {
350                 // Section 7.5.1 says that a COPY only needs to lock the destination,
351                 // not both destination and source. Strictly speaking, this is racy,
352                 // even though a COPY doesn't modify the source, if a concurrent
353                 // operation modifies the source. However, the litmus test explicitly
354                 // checks that COPYing a locked-by-another source is OK.
355                 release, status, err := h.confirmLocks(r, "", dst)
356                 if err != nil {
357                         return status, err
358                 }
359                 defer release()
360
361                 // Section 9.8.3 says that "The COPY method on a collection without a Depth
362                 // header must act as if a Depth header with value "infinity" was included".
363                 depth := infiniteDepth
364                 if hdr := r.Header.Get("Depth"); hdr != "" {
365                         depth = parseDepth(hdr)
366                         if depth != 0 && depth != infiniteDepth {
367                                 // Section 9.8.3 says that "A client may submit a Depth header on a
368                                 // COPY on a collection with a value of "0" or "infinity"."
369                                 return http.StatusBadRequest, errInvalidDepth
370                         }
371                 }
372                 return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
373         }
374
375         release, status, err := h.confirmLocks(r, src, dst)
376         if err != nil {
377                 return status, err
378         }
379         defer release()
380
381         // Section 9.9.2 says that "The MOVE method on a collection must act as if
382         // a "Depth: infinity" header was used on it. A client must not submit a
383         // Depth header on a MOVE on a collection with any value but "infinity"."
384         if hdr := r.Header.Get("Depth"); hdr != "" {
385                 if parseDepth(hdr) != infiniteDepth {
386                         return http.StatusBadRequest, errInvalidDepth
387                 }
388         }
389         return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
390 }
391
392 func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
393         duration, err := parseTimeout(r.Header.Get("Timeout"))
394         if err != nil {
395                 return http.StatusBadRequest, err
396         }
397         li, status, err := readLockInfo(r.Body)
398         if err != nil {
399                 return status, err
400         }
401
402         ctx := getContext(r)
403         token, ld, now, created := "", LockDetails{}, time.Now(), false
404         if li == (lockInfo{}) {
405                 // An empty lockInfo means to refresh the lock.
406                 ih, ok := parseIfHeader(r.Header.Get("If"))
407                 if !ok {
408                         return http.StatusBadRequest, errInvalidIfHeader
409                 }
410                 if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
411                         token = ih.lists[0].conditions[0].Token
412                 }
413                 if token == "" {
414                         return http.StatusBadRequest, errInvalidLockToken
415                 }
416                 ld, err = h.LockSystem.Refresh(now, token, duration)
417                 if err != nil {
418                         if err == ErrNoSuchLock {
419                                 return http.StatusPreconditionFailed, err
420                         }
421                         return http.StatusInternalServerError, err
422                 }
423
424         } else {
425                 // Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
426                 // then the request MUST act as if a "Depth:infinity" had been submitted."
427                 depth := infiniteDepth
428                 if hdr := r.Header.Get("Depth"); hdr != "" {
429                         depth = parseDepth(hdr)
430                         if depth != 0 && depth != infiniteDepth {
431                                 // Section 9.10.3 says that "Values other than 0 or infinity must not be
432                                 // used with the Depth header on a LOCK method".
433                                 return http.StatusBadRequest, errInvalidDepth
434                         }
435                 }
436                 reqPath, status, err := h.stripPrefix(r.URL.Path)
437                 if err != nil {
438                         return status, err
439                 }
440                 ld = LockDetails{
441                         Root:      reqPath,
442                         Duration:  duration,
443                         OwnerXML:  li.Owner.InnerXML,
444                         ZeroDepth: depth == 0,
445                 }
446                 token, err = h.LockSystem.Create(now, ld)
447                 if err != nil {
448                         if err == ErrLocked {
449                                 return StatusLocked, err
450                         }
451                         return http.StatusInternalServerError, err
452                 }
453                 defer func() {
454                         if retErr != nil {
455                                 h.LockSystem.Unlock(now, token)
456                         }
457                 }()
458
459                 // Create the resource if it didn't previously exist.
460                 if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
461                         f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
462                         if err != nil {
463                                 // TODO: detect missing intermediate dirs and return http.StatusConflict?
464                                 return http.StatusInternalServerError, err
465                         }
466                         f.Close()
467                         created = true
468                 }
469
470                 // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
471                 // Lock-Token value is a Coded-URL. We add angle brackets.
472                 w.Header().Set("Lock-Token", "<"+token+">")
473         }
474
475         w.Header().Set("Content-Type", "application/xml; charset=utf-8")
476         if created {
477                 // This is "w.WriteHeader(http.StatusCreated)" and not "return
478                 // http.StatusCreated, nil" because we write our own (XML) response to w
479                 // and Handler.ServeHTTP would otherwise write "Created".
480                 w.WriteHeader(http.StatusCreated)
481         }
482         writeLockInfo(w, token, ld)
483         return 0, nil
484 }
485
486 func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) {
487         // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
488         // Lock-Token value is a Coded-URL. We strip its angle brackets.
489         t := r.Header.Get("Lock-Token")
490         if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
491                 return http.StatusBadRequest, errInvalidLockToken
492         }
493         t = t[1 : len(t)-1]
494
495         switch err = h.LockSystem.Unlock(time.Now(), t); err {
496         case nil:
497                 return http.StatusNoContent, err
498         case ErrForbidden:
499                 return http.StatusForbidden, err
500         case ErrLocked:
501                 return StatusLocked, err
502         case ErrNoSuchLock:
503                 return http.StatusConflict, err
504         default:
505                 return http.StatusInternalServerError, err
506         }
507 }
508
509 func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status int, err error) {
510         reqPath, status, err := h.stripPrefix(r.URL.Path)
511         if err != nil {
512                 return status, err
513         }
514         ctx := getContext(r)
515         fi, err := h.FileSystem.Stat(ctx, reqPath)
516         if err != nil {
517                 if os.IsNotExist(err) {
518                         return http.StatusNotFound, err
519                 }
520                 return http.StatusMethodNotAllowed, err
521         }
522         depth := infiniteDepth
523         if hdr := r.Header.Get("Depth"); hdr != "" {
524                 depth = parseDepth(hdr)
525                 if depth == invalidDepth {
526                         return http.StatusBadRequest, errInvalidDepth
527                 }
528         }
529         pf, status, err := readPropfind(r.Body)
530         if err != nil {
531                 return status, err
532         }
533
534         mw := multistatusWriter{w: w}
535
536         walkFn := func(reqPath string, info os.FileInfo, err error) error {
537                 if err != nil {
538                         return err
539                 }
540                 var pstats []Propstat
541                 if pf.Propname != nil {
542                         pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
543                         if err != nil {
544                                 return err
545                         }
546                         pstat := Propstat{Status: http.StatusOK}
547                         for _, xmlname := range pnames {
548                                 pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
549                         }
550                         pstats = append(pstats, pstat)
551                 } else if pf.Allprop != nil {
552                         pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
553                 } else {
554                         pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
555                 }
556                 if err != nil {
557                         return err
558                 }
559                 return mw.write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats))
560         }
561
562         walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
563         closeErr := mw.close()
564         if walkErr != nil {
565                 return http.StatusInternalServerError, walkErr
566         }
567         if closeErr != nil {
568                 return http.StatusInternalServerError, closeErr
569         }
570         return 0, nil
571 }
572
573 func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) {
574         reqPath, status, err := h.stripPrefix(r.URL.Path)
575         if err != nil {
576                 return status, err
577         }
578         release, status, err := h.confirmLocks(r, reqPath, "")
579         if err != nil {
580                 return status, err
581         }
582         defer release()
583
584         ctx := getContext(r)
585
586         if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
587                 if os.IsNotExist(err) {
588                         return http.StatusNotFound, err
589                 }
590                 return http.StatusMethodNotAllowed, err
591         }
592         patches, status, err := readProppatch(r.Body)
593         if err != nil {
594                 return status, err
595         }
596         pstats, err := patch(ctx, h.FileSystem, h.LockSystem, reqPath, patches)
597         if err != nil {
598                 return http.StatusInternalServerError, err
599         }
600         mw := multistatusWriter{w: w}
601         writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
602         closeErr := mw.close()
603         if writeErr != nil {
604                 return http.StatusInternalServerError, writeErr
605         }
606         if closeErr != nil {
607                 return http.StatusInternalServerError, closeErr
608         }
609         return 0, nil
610 }
611
612 func makePropstatResponse(href string, pstats []Propstat) *response {
613         resp := response{
614                 Href:     []string{(&url.URL{Path: href}).EscapedPath()},
615                 Propstat: make([]propstat, 0, len(pstats)),
616         }
617         for _, p := range pstats {
618                 var xmlErr *xmlError
619                 if p.XMLError != "" {
620                         xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
621                 }
622                 resp.Propstat = append(resp.Propstat, propstat{
623                         Status:              fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
624                         Prop:                p.Props,
625                         ResponseDescription: p.ResponseDescription,
626                         Error:               xmlErr,
627                 })
628         }
629         return &resp
630 }
631
632 const (
633         infiniteDepth = -1
634         invalidDepth  = -2
635 )
636
637 // parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
638 // infiniteDepth. Parsing any other string returns invalidDepth.
639 //
640 // Different WebDAV methods have further constraints on valid depths:
641 //      - PROPFIND has no further restrictions, as per section 9.1.
642 //      - COPY accepts only "0" or "infinity", as per section 9.8.3.
643 //      - MOVE accepts only "infinity", as per section 9.9.2.
644 //      - LOCK accepts only "0" or "infinity", as per section 9.10.3.
645 // These constraints are enforced by the handleXxx methods.
646 func parseDepth(s string) int {
647         switch s {
648         case "0":
649                 return 0
650         case "1":
651                 return 1
652         case "infinity":
653                 return infiniteDepth
654         }
655         return invalidDepth
656 }
657
658 // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
659 const (
660         StatusMulti               = 207
661         StatusUnprocessableEntity = 422
662         StatusLocked              = 423
663         StatusFailedDependency    = 424
664         StatusInsufficientStorage = 507
665 )
666
667 func StatusText(code int) string {
668         switch code {
669         case StatusMulti:
670                 return "Multi-Status"
671         case StatusUnprocessableEntity:
672                 return "Unprocessable Entity"
673         case StatusLocked:
674                 return "Locked"
675         case StatusFailedDependency:
676                 return "Failed Dependency"
677         case StatusInsufficientStorage:
678                 return "Insufficient Storage"
679         }
680         return http.StatusText(code)
681 }
682
683 var (
684         errDestinationEqualsSource = errors.New("webdav: destination equals source")
685         errDirectoryNotEmpty       = errors.New("webdav: directory not empty")
686         errInvalidDepth            = errors.New("webdav: invalid depth")
687         errInvalidDestination      = errors.New("webdav: invalid destination")
688         errInvalidIfHeader         = errors.New("webdav: invalid If header")
689         errInvalidLockInfo         = errors.New("webdav: invalid lock info")
690         errInvalidLockToken        = errors.New("webdav: invalid lock token")
691         errInvalidPropfind         = errors.New("webdav: invalid propfind")
692         errInvalidProppatch        = errors.New("webdav: invalid proppatch")
693         errInvalidResponse         = errors.New("webdav: invalid response")
694         errInvalidTimeout          = errors.New("webdav: invalid timeout")
695         errNoFileSystem            = errors.New("webdav: no file system")
696         errNoLockSystem            = errors.New("webdav: no lock system")
697         errNotADirectory           = errors.New("webdav: not a directory")
698         errPrefixMismatch          = errors.New("webdav: prefix mismatch")
699         errRecursionTooDeep        = errors.New("webdav: recursion too deep")
700         errUnsupportedLockInfo     = errors.New("webdav: unsupported lock info")
701         errUnsupportedMethod       = errors.New("webdav: unsupported method")
702 )