webdav.go 21 KB

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