webdav.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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
  6. // TODO: ETag, properties.
  7. import (
  8. "errors"
  9. "io"
  10. "net/http"
  11. "os"
  12. "time"
  13. )
  14. // TODO: define the PropSystem interface.
  15. type PropSystem interface{}
  16. type Handler struct {
  17. // FileSystem is the virtual file system.
  18. FileSystem FileSystem
  19. // LockSystem is the lock management system.
  20. LockSystem LockSystem
  21. // PropSystem is an optional property management system. If non-nil, TODO.
  22. PropSystem PropSystem
  23. // Logger is an optional error logger. If non-nil, it will be called
  24. // whenever handling a http.Request results in an error.
  25. Logger func(*http.Request, error)
  26. }
  27. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  28. status, err := http.StatusBadRequest, error(nil)
  29. if h.FileSystem == nil {
  30. status, err = http.StatusInternalServerError, errNoFileSystem
  31. } else if h.LockSystem == nil {
  32. status, err = http.StatusInternalServerError, errNoLockSystem
  33. } else {
  34. // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods. Also, OPTIONS??
  35. switch r.Method {
  36. case "GET", "HEAD", "POST":
  37. status, err = h.handleGetHeadPost(w, r)
  38. case "DELETE":
  39. status, err = h.handleDelete(w, r)
  40. case "PUT":
  41. status, err = h.handlePut(w, r)
  42. case "MKCOL":
  43. status, err = h.handleMkcol(w, r)
  44. case "LOCK":
  45. status, err = h.handleLock(w, r)
  46. case "UNLOCK":
  47. status, err = h.handleUnlock(w, r)
  48. }
  49. }
  50. if status != 0 {
  51. w.WriteHeader(status)
  52. if status != http.StatusNoContent {
  53. w.Write([]byte(StatusText(status)))
  54. }
  55. }
  56. if h.Logger != nil && err != nil {
  57. h.Logger(r, err)
  58. }
  59. }
  60. func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, err error) {
  61. ih, ok := parseIfHeader(r.Header.Get("If"))
  62. if !ok {
  63. return nil, http.StatusBadRequest, errInvalidIfHeader
  64. }
  65. // ih is a disjunction (OR) of ifLists, so any ifList will do.
  66. for _, l := range ih.lists {
  67. path := l.resourceTag
  68. if path == "" {
  69. path = r.URL.Path
  70. }
  71. closer, err = h.LockSystem.Confirm(path, l.conditions...)
  72. if err == ErrConfirmationFailed {
  73. continue
  74. }
  75. if err != nil {
  76. return nil, http.StatusInternalServerError, err
  77. }
  78. return closer, 0, nil
  79. }
  80. return nil, http.StatusPreconditionFailed, errLocked
  81. }
  82. func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
  83. // TODO: check locks for read-only access??
  84. f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDONLY, 0)
  85. if err != nil {
  86. return http.StatusNotFound, err
  87. }
  88. defer f.Close()
  89. fi, err := f.Stat()
  90. if err != nil {
  91. return http.StatusNotFound, err
  92. }
  93. http.ServeContent(w, r, r.URL.Path, fi.ModTime(), f)
  94. return 0, nil
  95. }
  96. func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
  97. closer, status, err := h.confirmLocks(r)
  98. if err != nil {
  99. return status, err
  100. }
  101. defer closer.Close()
  102. if err := h.FileSystem.RemoveAll(r.URL.Path); err != nil {
  103. // TODO: MultiStatus.
  104. return http.StatusMethodNotAllowed, err
  105. }
  106. return http.StatusNoContent, nil
  107. }
  108. func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
  109. closer, status, err := h.confirmLocks(r)
  110. if err != nil {
  111. return status, err
  112. }
  113. defer closer.Close()
  114. f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
  115. if err != nil {
  116. return http.StatusNotFound, err
  117. }
  118. defer f.Close()
  119. if _, err := io.Copy(f, r.Body); err != nil {
  120. return http.StatusMethodNotAllowed, err
  121. }
  122. return http.StatusCreated, nil
  123. }
  124. func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
  125. closer, status, err := h.confirmLocks(r)
  126. if err != nil {
  127. return status, err
  128. }
  129. defer closer.Close()
  130. if err := h.FileSystem.Mkdir(r.URL.Path, 0777); err != nil {
  131. if os.IsNotExist(err) {
  132. return http.StatusConflict, err
  133. }
  134. return http.StatusMethodNotAllowed, err
  135. }
  136. return http.StatusCreated, nil
  137. }
  138. func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
  139. duration, err := parseTimeout(r.Header.Get("Timeout"))
  140. if err != nil {
  141. return http.StatusBadRequest, err
  142. }
  143. li, status, err := readLockInfo(r.Body)
  144. if err != nil {
  145. return status, err
  146. }
  147. token, ld := "", LockDetails{}
  148. if li == (lockInfo{}) {
  149. // An empty lockInfo means to refresh the lock.
  150. ih, ok := parseIfHeader(r.Header.Get("If"))
  151. if !ok {
  152. return http.StatusBadRequest, errInvalidIfHeader
  153. }
  154. if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
  155. token = ih.lists[0].conditions[0].Token
  156. }
  157. if token == "" {
  158. return http.StatusBadRequest, errInvalidLockToken
  159. }
  160. var closer io.Closer
  161. ld, closer, err = h.LockSystem.Refresh(token, time.Now(), duration)
  162. if err != nil {
  163. if err == ErrNoSuchLock {
  164. return http.StatusPreconditionFailed, err
  165. }
  166. return http.StatusInternalServerError, err
  167. }
  168. defer closer.Close()
  169. } else {
  170. depth, err := parseDepth(r.Header.Get("Depth"))
  171. if err != nil {
  172. return http.StatusBadRequest, err
  173. }
  174. ld = LockDetails{
  175. Depth: depth,
  176. Duration: duration,
  177. OwnerXML: li.Owner.InnerXML,
  178. Path: r.URL.Path,
  179. }
  180. var closer io.Closer
  181. token, closer, err = h.LockSystem.Create(r.URL.Path, time.Now(), ld)
  182. if err != nil {
  183. return http.StatusInternalServerError, err
  184. }
  185. defer func() {
  186. if retErr != nil {
  187. h.LockSystem.Unlock(token)
  188. }
  189. }()
  190. defer closer.Close()
  191. // Create the resource if it didn't previously exist.
  192. if _, err := h.FileSystem.Stat(r.URL.Path); err != nil {
  193. f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
  194. if err != nil {
  195. // TODO: detect missing intermediate dirs and return http.StatusConflict?
  196. return http.StatusInternalServerError, err
  197. }
  198. f.Close()
  199. w.WriteHeader(http.StatusCreated)
  200. // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
  201. // Lock-Token value is a Coded-URL. We add angle brackets.
  202. w.Header().Set("Lock-Token", "<"+token+">")
  203. }
  204. }
  205. w.Header().Set("Content-Type", "application/xml; charset=utf-8")
  206. writeLockInfo(w, token, ld)
  207. return 0, nil
  208. }
  209. func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) {
  210. // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
  211. // Lock-Token value is a Coded-URL. We strip its angle brackets.
  212. t := r.Header.Get("Lock-Token")
  213. if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
  214. return http.StatusBadRequest, errInvalidLockToken
  215. }
  216. t = t[1 : len(t)-1]
  217. switch err = h.LockSystem.Unlock(t); err {
  218. case nil:
  219. return http.StatusNoContent, err
  220. case ErrForbidden:
  221. return http.StatusForbidden, err
  222. case ErrNoSuchLock:
  223. return http.StatusConflict, err
  224. default:
  225. return http.StatusInternalServerError, err
  226. }
  227. }
  228. func parseDepth(s string) (int, error) {
  229. // TODO: implement.
  230. return -1, nil
  231. }
  232. func parseTimeout(s string) (time.Duration, error) {
  233. // TODO: implement.
  234. return 1 * time.Second, nil
  235. }
  236. // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
  237. const (
  238. StatusMulti = 207
  239. StatusUnprocessableEntity = 422
  240. StatusLocked = 423
  241. StatusFailedDependency = 424
  242. StatusInsufficientStorage = 507
  243. )
  244. func StatusText(code int) string {
  245. switch code {
  246. case StatusMulti:
  247. return "Multi-Status"
  248. case StatusUnprocessableEntity:
  249. return "Unprocessable Entity"
  250. case StatusLocked:
  251. return "Locked"
  252. case StatusFailedDependency:
  253. return "Failed Dependency"
  254. case StatusInsufficientStorage:
  255. return "Insufficient Storage"
  256. }
  257. return http.StatusText(code)
  258. }
  259. var (
  260. errInvalidIfHeader = errors.New("webdav: invalid If header")
  261. errInvalidLockInfo = errors.New("webdav: invalid lock info")
  262. errInvalidLockToken = errors.New("webdav: invalid lock token")
  263. errLocked = errors.New("webdav: locked")
  264. errNoFileSystem = errors.New("webdav: no file system")
  265. errNoLockSystem = errors.New("webdav: no lock system")
  266. errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
  267. )