webdav.go 19 KB

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