github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/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 provides a WebDAV server implementation.
     6  package webdav // import "golang.org/x/net/webdav"
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"net/http"
    13  	"net/http/httputil"
    14  	"net/url"
    15  	"path"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	model "github.com/cloudreve/Cloudreve/v3/models"
    22  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
    23  	"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
    24  	"github.com/cloudreve/Cloudreve/v3/pkg/util"
    25  )
    26  
    27  type Handler struct {
    28  	// Prefix is the URL path prefix to strip from WebDAV resource paths.
    29  	Prefix string
    30  	// LockSystem is the lock management system.
    31  	LockSystem map[uint]LockSystem
    32  	// Logger is an optional error logger. If non-nil, it will be called
    33  	// for all HTTP requests.
    34  	Logger func(*http.Request, error)
    35  	Mutex  *sync.Mutex
    36  }
    37  
    38  func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) {
    39  	if h.Prefix == "" {
    40  		return p, http.StatusOK, nil
    41  	}
    42  	prefix := h.Prefix
    43  	if r := strings.TrimPrefix(p, prefix); len(r) < len(p) {
    44  		if len(r) == 0 {
    45  			r = "/"
    46  		}
    47  		return util.RemoveSlash(r), http.StatusOK, nil
    48  	}
    49  	return p, http.StatusNotFound, errPrefixMismatch
    50  }
    51  
    52  // isPathExist 路径是否存在
    53  func isPathExist(ctx context.Context, fs *filesystem.FileSystem, path string) (bool, FileInfo) {
    54  	// 尝试目录
    55  	if ok, folder := fs.IsPathExist(path); ok {
    56  		return ok, folder
    57  	}
    58  	if ok, file := fs.IsFileExist(path); ok {
    59  		return ok, file
    60  	}
    61  	return false, nil
    62  }
    63  
    64  func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) {
    65  	status, err := http.StatusBadRequest, errUnsupportedMethod
    66  	h.Mutex.Lock()
    67  	if h.LockSystem == nil {
    68  		h.Mutex.Unlock()
    69  		status, err = http.StatusInternalServerError, errNoLockSystem
    70  	} else {
    71  		// 检查并新建 LockSystem
    72  		ls, ok := h.LockSystem[fs.User.ID]
    73  		if !ok {
    74  			h.LockSystem[fs.User.ID] = NewMemLS()
    75  			ls = h.LockSystem[fs.User.ID]
    76  		}
    77  		h.Mutex.Unlock()
    78  
    79  		switch r.Method {
    80  		case "OPTIONS":
    81  			status, err = h.handleOptions(w, r, fs)
    82  		case "GET", "HEAD", "POST":
    83  			status, err = h.handleGetHeadPost(w, r, fs)
    84  		case "DELETE":
    85  			status, err = h.handleDelete(w, r, fs)
    86  		case "PUT":
    87  			status, err = h.handlePut(w, r, fs)
    88  		case "MKCOL":
    89  			status, err = h.handleMkcol(w, r, fs)
    90  		case "COPY", "MOVE":
    91  			status, err = h.handleCopyMove(w, r, fs)
    92  		case "LOCK":
    93  			status, err = h.handleLock(w, r, fs, ls)
    94  		case "UNLOCK":
    95  			status, err = h.handleUnlock(w, r, fs, ls)
    96  		case "PROPFIND":
    97  			status, err = h.handlePropfind(w, r, fs, ls)
    98  		case "PROPPATCH":
    99  			status, err = h.handleProppatch(w, r, fs, ls)
   100  		}
   101  	}
   102  
   103  	if status != 0 {
   104  		w.WriteHeader(status)
   105  		if status != http.StatusNoContent {
   106  			w.Write([]byte(StatusText(status)))
   107  		}
   108  	}
   109  	if h.Logger != nil {
   110  		h.Logger(r, err)
   111  	}
   112  }
   113  
   114  // OK
   115  func (h *Handler) lock(now time.Time, root string, fs *filesystem.FileSystem, ls LockSystem) (token string, status int, err error) {
   116  	//token, err = ls.Create(now, LockDetails{
   117  	//	Root:      root,
   118  	//	Duration:  infiniteTimeout,
   119  	//	ZeroDepth: true,
   120  	//})
   121  	//if err != nil {
   122  	//	if err == ErrLocked {
   123  	//		return "", StatusLocked, err
   124  	//	}
   125  	//	return "", http.StatusInternalServerError, err
   126  	//}
   127  
   128  	return fmt.Sprintf("%d", time.Now().Unix()), 0, nil
   129  }
   130  
   131  // ok
   132  func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.FileSystem) (release func(), status int, err error) {
   133  
   134  	//hdr := r.Header.Get("If")
   135  	//h.Mutex.Lock()
   136  	//ls,ok := h.LockSystem[fs.User.ID]
   137  	//h.Mutex.Unlock()
   138  	//if !ok{
   139  	//	return nil, http.StatusInternalServerError, errNoLockSystem
   140  	//}
   141  	//
   142  	//if hdr == "" {
   143  	//	// An empty If header means that the client hasn't previously created locks.
   144  	//	// Even if this client doesn't care about locks, we still need to check that
   145  	//	// the resources aren't locked by another client, so we create temporary
   146  	//	// locks that would conflict with another client's locks. These temporary
   147  	//	// locks are unlocked at the end of the HTTP request.
   148  	//	now, srcToken, dstToken := time.Now(), "", ""
   149  	//	if src != "" {
   150  	//		srcToken, status, err = h.lock(now, src, fs,ls)
   151  	//		if err != nil {
   152  	//			return nil, status, err
   153  	//		}
   154  	//	}
   155  	//	if dst != "" {
   156  	//		dstToken, status, err = h.lock(now, dst, fs,ls)
   157  	//		if err != nil {
   158  	//			if srcToken != "" {
   159  	//				ls.Unlock(now, srcToken)
   160  	//			}
   161  	//			return nil, status, err
   162  	//		}
   163  	//	}
   164  	//
   165  	//	return func() {
   166  	//		if dstToken != "" {
   167  	//			ls.Unlock(now, dstToken)
   168  	//		}
   169  	//		if srcToken != "" {
   170  	//			ls.Unlock(now, srcToken)
   171  	//		}
   172  	//	}, 0, nil
   173  	//}
   174  	//
   175  	//ih, ok := parseIfHeader(hdr)
   176  	//if !ok {
   177  	//	return nil, http.StatusBadRequest, errInvalidIfHeader
   178  	//}
   179  	//// ih is a disjunction (OR) of ifLists, so any ifList will do.
   180  	//for _, l := range ih.lists {
   181  	//	lsrc := l.resourceTag
   182  	//	if lsrc == "" {
   183  	//		lsrc = src
   184  	//	} else {
   185  	//		u, err := url.Parse(lsrc)
   186  	//		if err != nil {
   187  	//			continue
   188  	//		}
   189  	//		//if u.Host != r.Host {
   190  	//		//	continue
   191  	//		//}
   192  	//		lsrc, status, err = h.stripPrefix(u.Path, fs.User.ID)
   193  	//		if err != nil {
   194  	//			return nil, status, err
   195  	//		}
   196  	//	}
   197  	//	release, err = ls.Confirm(
   198  	//		time.Now(),
   199  	//		lsrc,
   200  	//		dst,
   201  	//		l.conditions...,
   202  	//	)
   203  	//	if err == ErrConfirmationFailed {
   204  	//		continue
   205  	//	}
   206  	//	if err != nil {
   207  	//		return nil, http.StatusInternalServerError, err
   208  	//	}
   209  	//	return release, 0, nil
   210  	//}
   211  	//// Section 10.4.1 says that "If this header is evaluated and all state lists
   212  	//// fail, then the request must fail with a 412 (Precondition Failed) status."
   213  	//// We follow the spec even though the cond_put_corrupt_token test case from
   214  	//// the litmus test warns on seeing a 412 instead of a 423 (Locked).
   215  	//return nil, http.StatusPreconditionFailed, ErrLocked
   216  
   217  	return func() {
   218  
   219  	}, 0, nil
   220  }
   221  
   222  // OK
   223  func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   224  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   225  	if err != nil {
   226  		return status, err
   227  	}
   228  	ctx := r.Context()
   229  	allow := "OPTIONS, LOCK, PUT, MKCOL"
   230  	if exist, fi := isPathExist(ctx, fs, reqPath); exist {
   231  		if fi.IsDir() {
   232  			allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
   233  		} else {
   234  			allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
   235  		}
   236  	}
   237  	w.Header().Set("Allow", allow)
   238  	// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
   239  	w.Header().Set("DAV", "1, 2")
   240  	// http://msdn.microsoft.com/en-au/library/cc250217.aspx
   241  	w.Header().Set("MS-Author-Via", "DAV")
   242  	return 0, nil
   243  }
   244  
   245  var proxy = &httputil.ReverseProxy{
   246  	Director: func(request *http.Request) {
   247  		if target, ok := request.Context().Value(fsctx.WebDAVProxyUrlCtx).(*url.URL); ok {
   248  			request.URL.Scheme = target.Scheme
   249  			request.URL.Host = target.Host
   250  			request.URL.Path = target.Path
   251  			request.URL.RawPath = target.RawPath
   252  			request.URL.RawQuery = target.RawQuery
   253  			request.Host = target.Host
   254  			request.Header.Del("Authorization")
   255  		}
   256  	},
   257  	ErrorHandler: func(writer http.ResponseWriter, request *http.Request, err error) {
   258  		writer.WriteHeader(http.StatusInternalServerError)
   259  	},
   260  }
   261  
   262  // OK
   263  func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   264  	defer fs.Recycle()
   265  
   266  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   267  	if err != nil {
   268  		return status, err
   269  	}
   270  
   271  	ctx := r.Context()
   272  
   273  	exist, file := fs.IsFileExist(reqPath)
   274  	if !exist {
   275  		return http.StatusNotFound, nil
   276  	}
   277  	fs.SetTargetFile(&[]model.File{*file})
   278  
   279  	rs, err := fs.Preview(ctx, 0, false)
   280  	if err != nil {
   281  		if err == filesystem.ErrObjectNotExist {
   282  			return http.StatusNotFound, err
   283  		}
   284  		return http.StatusInternalServerError, err
   285  	}
   286  
   287  	etag, err := findETag(ctx, fs, nil, reqPath, &fs.FileTarget[0])
   288  	if err != nil {
   289  		return http.StatusInternalServerError, err
   290  	}
   291  	w.Header().Set("ETag", etag)
   292  
   293  	if !rs.Redirect {
   294  		defer rs.Content.Close()
   295  		// 获取文件内容
   296  		http.ServeContent(w, r, reqPath, fs.FileTarget[0].UpdatedAt, rs.Content)
   297  		return 0, nil
   298  	}
   299  
   300  	if application, ok := r.Context().Value(fsctx.WebDAVCtx).(*model.Webdav); ok && application.UseProxy {
   301  		target, err := url.Parse(rs.URL)
   302  		if err != nil {
   303  			return http.StatusInternalServerError, err
   304  		}
   305  
   306  		r = r.Clone(context.WithValue(r.Context(), fsctx.WebDAVProxyUrlCtx, target))
   307  		// 忽略反向代理在传输错误时报错
   308  		defer func() {
   309  			if err := recover(); err != nil && err != http.ErrAbortHandler {
   310  				panic(err)
   311  			}
   312  		}()
   313  		proxy.ServeHTTP(w, r)
   314  	} else {
   315  		http.Redirect(w, r, rs.URL, 301)
   316  	}
   317  
   318  	return 0, nil
   319  }
   320  
   321  // OK
   322  func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   323  	defer fs.Recycle()
   324  
   325  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   326  	if err != nil {
   327  		return status, err
   328  	}
   329  
   330  	release, status, err := h.confirmLocks(r, reqPath, "", fs)
   331  	if err != nil {
   332  		return status, err
   333  	}
   334  	defer release()
   335  
   336  	ctx := r.Context()
   337  
   338  	// 尝试作为文件删除
   339  	if ok, file := fs.IsFileExist(reqPath); ok {
   340  		if err := fs.Delete(ctx, []uint{}, []uint{file.ID}, false, false); err != nil {
   341  			return http.StatusMethodNotAllowed, err
   342  		}
   343  		return http.StatusNoContent, nil
   344  	}
   345  
   346  	// 尝试作为目录删除
   347  	if ok, folder := fs.IsPathExist(reqPath); ok {
   348  		if err := fs.Delete(ctx, []uint{folder.ID}, []uint{}, false, false); err != nil {
   349  			return http.StatusMethodNotAllowed, err
   350  		}
   351  		return http.StatusNoContent, nil
   352  	}
   353  
   354  	return http.StatusNotFound, nil
   355  }
   356  
   357  // OK
   358  func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   359  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   360  	if err != nil {
   361  		return status, err
   362  	}
   363  	release, status, err := h.confirmLocks(r, reqPath, "", fs)
   364  	if err != nil {
   365  		return status, err
   366  	}
   367  	defer release()
   368  	// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
   369  	// comments in http.checkEtag.
   370  	ctx, cancel := context.WithCancel(context.Background())
   371  	defer cancel()
   372  	ctx = context.WithValue(ctx, fsctx.HTTPCtx, r.Context())
   373  	ctx = context.WithValue(ctx, fsctx.CancelFuncCtx, cancel)
   374  
   375  	fileSize, err := strconv.ParseUint(r.Header.Get("Content-Length"), 10, 64)
   376  	if err != nil {
   377  		return http.StatusMethodNotAllowed, err
   378  	}
   379  	fileName := path.Base(reqPath)
   380  	filePath := path.Dir(reqPath)
   381  	fileData := fsctx.FileStream{
   382  		MimeType:    r.Header.Get("Content-Type"),
   383  		File:        r.Body,
   384  		Size:        fileSize,
   385  		Name:        fileName,
   386  		VirtualPath: filePath,
   387  	}
   388  
   389  	// 判断文件是否已存在
   390  	exist, originFile := fs.IsFileExist(reqPath)
   391  	if exist {
   392  		// 已存在,为更新操作
   393  
   394  		// 检查此文件是否有软链接
   395  		fileList, err := model.RemoveFilesWithSoftLinks([]model.File{*originFile})
   396  		if err == nil && len(fileList) == 0 {
   397  			// 如果包含软连接,应重新生成新文件副本,并更新source_name
   398  			originFile.SourceName = fs.GenerateSavePath(ctx, &fileData)
   399  			fileData.Mode &= ^fsctx.Overwrite
   400  			fs.Use("AfterUpload", filesystem.HookUpdateSourceName)
   401  			fs.Use("AfterUploadCanceled", filesystem.HookUpdateSourceName)
   402  			fs.Use("AfterValidateFailed", filesystem.HookUpdateSourceName)
   403  		}
   404  
   405  		fs.Use("BeforeUpload", filesystem.HookResetPolicy)
   406  		fs.Use("BeforeUpload", filesystem.HookValidateFile)
   407  		fs.Use("BeforeUpload", filesystem.HookValidateCapacityDiff)
   408  		fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent)
   409  		fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize)
   410  		fs.Use("AfterUploadCanceled", filesystem.HookCancelContext)
   411  		fs.Use("AfterUpload", filesystem.GenericAfterUpdate)
   412  		fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent)
   413  		fs.Use("AfterValidateFailed", filesystem.HookClearFileSize)
   414  		ctx = context.WithValue(ctx, fsctx.FileModelCtx, *originFile)
   415  		fileData.Mode |= fsctx.Overwrite
   416  	} else {
   417  		// 给文件系统分配钩子
   418  		fs.Use("BeforeUpload", filesystem.HookValidateFile)
   419  		fs.Use("BeforeUpload", filesystem.HookValidateCapacity)
   420  		fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
   421  		fs.Use("AfterUploadCanceled", filesystem.HookCancelContext)
   422  		fs.Use("AfterUpload", filesystem.GenericAfterUpload)
   423  		fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
   424  	}
   425  
   426  	// rclone 请求
   427  	fs.Use("AfterUpload", filesystem.NewWebdavAfterUploadHook(r))
   428  
   429  	// 执行上传
   430  	err = fs.Upload(ctx, &fileData)
   431  	if err != nil {
   432  		return http.StatusMethodNotAllowed, err
   433  	}
   434  
   435  	etag, err := findETag(ctx, fs, nil, reqPath, fileData.Model.(*model.File))
   436  	if err != nil {
   437  		return http.StatusInternalServerError, err
   438  	}
   439  	w.Header().Set("ETag", etag)
   440  	return http.StatusCreated, nil
   441  }
   442  
   443  // OK
   444  func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   445  	defer fs.Recycle()
   446  
   447  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   448  	if err != nil {
   449  		return status, err
   450  	}
   451  	release, status, err := h.confirmLocks(r, reqPath, "", fs)
   452  	if err != nil {
   453  		return status, err
   454  	}
   455  	defer release()
   456  
   457  	ctx := r.Context()
   458  
   459  	if r.ContentLength > 0 {
   460  		return http.StatusUnsupportedMediaType, nil
   461  	}
   462  
   463  	if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {
   464  		return http.StatusConflict, err
   465  	}
   466  	return http.StatusCreated, nil
   467  }
   468  
   469  // OK
   470  func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
   471  	defer fs.Recycle()
   472  
   473  	hdr := r.Header.Get("Destination")
   474  	if hdr == "" {
   475  		return http.StatusBadRequest, errInvalidDestination
   476  	}
   477  	u, err := url.Parse(hdr)
   478  	if err != nil {
   479  		return http.StatusBadRequest, errInvalidDestination
   480  	}
   481  	//if u.Host != "" && u.Host != r.Host {
   482  	//	return http.StatusBadGateway, errInvalidDestination
   483  	//}
   484  
   485  	src, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   486  	if err != nil {
   487  		return status, err
   488  	}
   489  
   490  	dst, status, err := h.stripPrefix(u.Path, fs.User.ID)
   491  	if err != nil {
   492  		return status, err
   493  	}
   494  
   495  	if dst == "" {
   496  		return http.StatusBadGateway, errInvalidDestination
   497  	}
   498  	if dst == src {
   499  		return http.StatusForbidden, errDestinationEqualsSource
   500  	}
   501  
   502  	ctx := r.Context()
   503  
   504  	isExist, target := isPathExist(ctx, fs, src)
   505  
   506  	if !isExist {
   507  		return http.StatusNotFound, nil
   508  	}
   509  
   510  	if r.Method == "COPY" {
   511  		// Section 7.5.1 says that a COPY only needs to lock the destination,
   512  		// not both destination and source. Strictly speaking, this is racy,
   513  		// even though a COPY doesn't modify the source, if a concurrent
   514  		// operation modifies the source. However, the litmus test explicitly
   515  		// checks that COPYing a locked-by-another source is OK.
   516  		release, status, err := h.confirmLocks(r, "", dst, fs)
   517  		if err != nil {
   518  			return status, err
   519  		}
   520  		defer release()
   521  
   522  		// Section 9.8.3 says that "The COPY method on a collection without a Depth
   523  		// header must act as if a Depth header with value "infinity" was included".
   524  		depth := infiniteDepth
   525  		if hdr := r.Header.Get("Depth"); hdr != "" {
   526  			depth = parseDepth(hdr)
   527  			if depth != 0 && depth != infiniteDepth {
   528  				// Section 9.8.3 says that "A client may submit a Depth header on a
   529  				// COPY on a collection with a value of "0" or "infinity"."
   530  				return http.StatusBadRequest, errInvalidDepth
   531  			}
   532  		}
   533  		status, err = copyFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") != "F", depth, 0)
   534  		if err != nil {
   535  			return status, err
   536  		}
   537  
   538  		err = updateCopyMoveModtime(r, fs, dst)
   539  		if err != nil {
   540  			return http.StatusInternalServerError, err
   541  		}
   542  		return status, nil
   543  	}
   544  
   545  	// windows下,某些情况下(网盘根目录下)Office保存文件时附带的锁token只包含源文件,
   546  	// 此处暂时去除了对dst锁的检查
   547  	release, status, err := h.confirmLocks(r, src, "", fs)
   548  	if err != nil {
   549  		return status, err
   550  	}
   551  	defer release()
   552  
   553  	// Section 9.9.2 says that "The MOVE method on a collection must act as if
   554  	// a "Depth: infinity" header was used on it. A client must not submit a
   555  	// Depth header on a MOVE on a collection with any value but "infinity"."
   556  	if hdr := r.Header.Get("Depth"); hdr != "" {
   557  		if parseDepth(hdr) != infiniteDepth {
   558  			return http.StatusBadRequest, errInvalidDepth
   559  		}
   560  	}
   561  	status, err = moveFiles(ctx, fs, target, dst, r.Header.Get("Overwrite") == "T")
   562  	if err != nil {
   563  		return status, err
   564  	}
   565  
   566  	err = updateCopyMoveModtime(r, fs, dst)
   567  	if err != nil {
   568  		return http.StatusInternalServerError, err
   569  	}
   570  	return status, nil
   571  }
   572  
   573  // OK
   574  func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (retStatus int, retErr error) {
   575  	defer fs.Recycle()
   576  
   577  	duration, err := parseTimeout(r.Header.Get("Timeout"))
   578  	if err != nil {
   579  		return http.StatusBadRequest, err
   580  	}
   581  
   582  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   583  	if err != nil {
   584  		return status, err
   585  	}
   586  
   587  	////ctx := r.Context()
   588  	//token, ld, now, created := "", LockDetails{}, time.Now(), false
   589  	//if li == (lockInfo{}) {
   590  	//	// An empty lockInfo means to refresh the lock.
   591  	//	ih, ok := parseIfHeader(r.Header.Get("If"))
   592  	//	if !ok {
   593  	//		return http.StatusBadRequest, errInvalidIfHeader
   594  	//	}
   595  	//	if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
   596  	//		token = ih.lists[0].conditions[0].Token
   597  	//	}
   598  	//	if token == "" {
   599  	//		return http.StatusBadRequest, errInvalidLockToken
   600  	//	}
   601  	//	ld, err = ls.Refresh(now, token, duration)
   602  	//	if err != nil {
   603  	//		if err == ErrNoSuchLock {
   604  	//			return http.StatusPreconditionFailed, err
   605  	//		}
   606  	//		return http.StatusInternalServerError, err
   607  	//	}
   608  	//
   609  	//} else {
   610  	//	// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
   611  	//	// then the request MUST act as if a "Depth:infinity" had been submitted."
   612  	//	depth := infiniteDepth
   613  	//	if hdr := r.Header.Get("Depth"); hdr != "" {
   614  	//		depth = parseDepth(hdr)
   615  	//		if depth != 0 && depth != infiniteDepth {
   616  	//			// Section 9.10.3 says that "Values other than 0 or infinity must not be
   617  	//			// used with the Depth header on a LOCK method".
   618  	//			return http.StatusBadRequest, errInvalidDepth
   619  	//		}
   620  	//	}
   621  	//	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   622  	//	if err != nil {
   623  	//		return status, err
   624  	//	}
   625  	//	ld = LockDetails{
   626  	//		Root:      reqPath,
   627  	//		Duration:  duration,
   628  	//		OwnerXML:  li.Owner.InnerXML,
   629  	//		ZeroDepth: depth == 0,
   630  	//	}
   631  	//	token, err = ls.Create(now, ld)
   632  	//	if err != nil {
   633  	//		if err == ErrLocked {
   634  	//			return StatusLocked, err
   635  	//		}
   636  	//		return http.StatusInternalServerError, err
   637  	//	}
   638  	//	defer func() {
   639  	//		if retErr != nil {
   640  	//			ls.Unlock(now, token)
   641  	//		}
   642  	//	}()
   643  	//
   644  	//	// Create the resource if it didn't previously exist.
   645  	//	//if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
   646  	//	//	f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   647  	//	//	if err != nil {
   648  	//	//		// TODO: detect missing intermediate dirs and return http.StatusConflict?
   649  	//	//		return http.StatusInternalServerError, err
   650  	//	//	}
   651  	//	//	f.Close()
   652  	//	//	created = true
   653  	//	//}
   654  	//
   655  	//	// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
   656  	//	// Lock-Token value is a Coded-URL. We add angle brackets.
   657  	//	w.Header().Set("Lock-Token", "<"+token+">")
   658  	//}
   659  	//
   660  	//w.Header().Set("Content-Type", "application/xml; charset=utf-8")
   661  	//if created {
   662  	//	// This is "w.WriteHeader(http.StatusCreated)" and not "return
   663  	//	// http.StatusCreated, nil" because we write our own (XML) response to w
   664  	//	// and Handler.ServeHTTP would otherwise write "Created".
   665  	//	w.WriteHeader(http.StatusCreated)
   666  	//}
   667  
   668  	writeLockInfo(w, fmt.Sprintf("%d", time.Now().UnixNano()), LockDetails{
   669  		Duration: duration,
   670  		OwnerXML: fs.User.Email,
   671  		Root:     reqPath,
   672  	})
   673  	return 0, nil
   674  }
   675  
   676  // OK
   677  func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
   678  	defer fs.Recycle()
   679  	return http.StatusNoContent, err
   680  
   681  	//// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
   682  	//// Lock-Token value is a Coded-URL. We strip its angle brackets.
   683  	//t := r.Header.Get("Lock-Token")
   684  	//if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
   685  	//	return http.StatusBadRequest, errInvalidLockToken
   686  	//}
   687  	//t = t[1 : len(t)-1]
   688  	//
   689  	//switch err = ls.Unlock(time.Now(), t); err {
   690  	//case nil:
   691  	//	return http.StatusNoContent, err
   692  	//case ErrForbidden:
   693  	//	return http.StatusForbidden, err
   694  	//case ErrLocked:
   695  	//	return StatusLocked, err
   696  	//case ErrNoSuchLock:
   697  	//	return http.StatusConflict, err
   698  	//default:
   699  	//	return http.StatusInternalServerError, err
   700  	//}
   701  }
   702  
   703  // OK
   704  func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
   705  	defer fs.Recycle()
   706  
   707  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   708  	if err != nil {
   709  		return status, err
   710  	}
   711  	ctx := r.Context()
   712  	ok, fi := isPathExist(ctx, fs, reqPath)
   713  	if !ok {
   714  		return http.StatusNotFound, err
   715  	}
   716  
   717  	depth := infiniteDepth
   718  	if hdr := r.Header.Get("Depth"); hdr != "" {
   719  		depth = parseDepth(hdr)
   720  		if depth == invalidDepth {
   721  			return http.StatusBadRequest, errInvalidDepth
   722  		}
   723  	}
   724  	pf, status, err := readPropfind(r.Body)
   725  	if err != nil {
   726  		return status, err
   727  	}
   728  
   729  	mw := multistatusWriter{w: w}
   730  
   731  	walkFn := func(reqPath string, info FileInfo, err error) error {
   732  
   733  		if err != nil {
   734  			return err
   735  		}
   736  		var pstats []Propstat
   737  		if pf.Propname != nil {
   738  			pnames, err := propnames(ctx, fs, ls, info)
   739  			if err != nil {
   740  				return err
   741  			}
   742  			pstat := Propstat{Status: http.StatusOK}
   743  			for _, xmlname := range pnames {
   744  				pstat.Props = append(pstat.Props, Property{XMLName: xmlname})
   745  			}
   746  			pstats = append(pstats, pstat)
   747  		} else if pf.Allprop != nil {
   748  			pstats, err = allprop(ctx, fs, ls, info, pf.Prop)
   749  		} else {
   750  			pstats, err = props(ctx, fs, ls, info, pf.Prop)
   751  		}
   752  		if err != nil {
   753  			return err
   754  		}
   755  		href := path.Join(h.Prefix, reqPath)
   756  		if href != "/" && info.IsDir() {
   757  			href += "/"
   758  		}
   759  		return mw.write(makePropstatResponse(href, pstats))
   760  	}
   761  
   762  	walkErr := walkFS(ctx, fs, depth, reqPath, fi, walkFn)
   763  	closeErr := mw.close()
   764  	if walkErr != nil {
   765  		return http.StatusInternalServerError, walkErr
   766  	}
   767  	if closeErr != nil {
   768  		return http.StatusInternalServerError, closeErr
   769  	}
   770  	return 0, nil
   771  }
   772  
   773  func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem, ls LockSystem) (status int, err error) {
   774  	defer fs.Recycle()
   775  
   776  	reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
   777  	if err != nil {
   778  		return status, err
   779  	}
   780  	release, status, err := h.confirmLocks(r, reqPath, "", fs)
   781  	if err != nil {
   782  		return status, err
   783  	}
   784  	defer release()
   785  
   786  	ctx := r.Context()
   787  
   788  	if exist, _ := isPathExist(ctx, fs, reqPath); !exist {
   789  		return http.StatusNotFound, nil
   790  	}
   791  	patches, status, err := readProppatch(r.Body)
   792  	if err != nil {
   793  		return status, err
   794  	}
   795  	pstats, err := patch(ctx, fs, ls, reqPath, patches)
   796  	if err != nil {
   797  		return http.StatusInternalServerError, err
   798  	}
   799  	mw := multistatusWriter{w: w}
   800  	writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
   801  	closeErr := mw.close()
   802  	if writeErr != nil {
   803  		return http.StatusInternalServerError, writeErr
   804  	}
   805  	if closeErr != nil {
   806  		return http.StatusInternalServerError, closeErr
   807  	}
   808  	return 0, nil
   809  }
   810  
   811  func makePropstatResponse(href string, pstats []Propstat) *response {
   812  	resp := response{
   813  		Href:     []string{(&url.URL{Path: href}).EscapedPath()},
   814  		Propstat: make([]propstat, 0, len(pstats)),
   815  	}
   816  	for _, p := range pstats {
   817  		var xmlErr *xmlError
   818  		if p.XMLError != "" {
   819  			xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
   820  		}
   821  		resp.Propstat = append(resp.Propstat, propstat{
   822  			Status:              fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
   823  			Prop:                p.Props,
   824  			ResponseDescription: p.ResponseDescription,
   825  			Error:               xmlErr,
   826  		})
   827  	}
   828  	return &resp
   829  }
   830  
   831  const (
   832  	infiniteDepth = -1
   833  	invalidDepth  = -2
   834  )
   835  
   836  // parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
   837  // infiniteDepth. Parsing any other string returns invalidDepth.
   838  //
   839  // Different WebDAV methods have further constraints on valid depths:
   840  //   - PROPFIND has no further restrictions, as per section 9.1.
   841  //   - COPY accepts only "0" or "infinity", as per section 9.8.3.
   842  //   - MOVE accepts only "infinity", as per section 9.9.2.
   843  //   - LOCK accepts only "0" or "infinity", as per section 9.10.3.
   844  //
   845  // These constraints are enforced by the handleXxx methods.
   846  func parseDepth(s string) int {
   847  	switch s {
   848  	case "0":
   849  		return 0
   850  	case "1":
   851  		return 1
   852  	case "infinity":
   853  		return infiniteDepth
   854  	}
   855  	return invalidDepth
   856  }
   857  
   858  // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
   859  const (
   860  	StatusMulti               = 207
   861  	StatusUnprocessableEntity = 422
   862  	StatusLocked              = 423
   863  	StatusFailedDependency    = 424
   864  	StatusInsufficientStorage = 507
   865  )
   866  
   867  func StatusText(code int) string {
   868  	switch code {
   869  	case StatusMulti:
   870  		return "Multi-Status"
   871  	case StatusUnprocessableEntity:
   872  		return "Unprocessable Entity"
   873  	case StatusLocked:
   874  		return "Locked"
   875  	case StatusFailedDependency:
   876  		return "Failed Dependency"
   877  	case StatusInsufficientStorage:
   878  		return "Insufficient Storage"
   879  	}
   880  	return http.StatusText(code)
   881  }
   882  
   883  var (
   884  	errDestinationEqualsSource = errors.New("webdav: destination equals source")
   885  	errDirectoryNotEmpty       = errors.New("webdav: directory not empty")
   886  	errInvalidDepth            = errors.New("webdav: invalid depth")
   887  	errInvalidDestination      = errors.New("webdav: invalid destination")
   888  	errInvalidIfHeader         = errors.New("webdav: invalid If header")
   889  	errInvalidLockInfo         = errors.New("webdav: invalid lock info")
   890  	errInvalidLockToken        = errors.New("webdav: invalid lock token")
   891  	errInvalidPropfind         = errors.New("webdav: invalid propfind")
   892  	errInvalidProppatch        = errors.New("webdav: invalid proppatch")
   893  	errInvalidResponse         = errors.New("webdav: invalid response")
   894  	errInvalidTimeout          = errors.New("webdav: invalid timeout")
   895  	errNoFileSystem            = errors.New("webdav: no file system")
   896  	errNoLockSystem            = errors.New("webdav: no lock system")
   897  	errNotADirectory           = errors.New("webdav: not a directory")
   898  	errPrefixMismatch          = errors.New("webdav: prefix mismatch")
   899  	errRecursionTooDeep        = errors.New("webdav: recursion too deep")
   900  	errUnsupportedLockInfo     = errors.New("webdav: unsupported lock info")
   901  	errUnsupportedMethod       = errors.New("webdav: unsupported method")
   902  )