github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/net/webdav/webdav.go (about)

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