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 )