webdav.go 8.9 KB

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