webdav.go 8.0 KB

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