Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(782)

Unified Diff: webdav/webdav.go

Issue 169240043: code review 169240043: go.net/webdav: new Handler, FileSystem, LockSystem and ... (Closed)
Patch Set: diff -r ff9f61369d2cf93c4dc753b0e5737fdeeb7b99f6 https://code.google.com/p/go.net Created 10 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « webdav/lock.go ('k') | webdav/xml.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webdav/webdav.go
===================================================================
new file mode 100644
--- /dev/null
+++ b/webdav/webdav.go
@@ -0,0 +1,295 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package webdav etc etc TODO.
+package webdav
+
+// TODO: ETag, properties.
+// TODO: figure out what/when is responsible for path cleaning: no "../../etc/passwd"s.
+
+import (
+ "errors"
+ "io"
+ "net/http"
+ "os"
+ "time"
+)
+
+// TODO: define the PropSystem interface.
+type PropSystem interface{}
+
+type Handler struct {
+ // FileSystem is the virtual file system.
+ FileSystem FileSystem
+ // LockSystem is the lock management system.
+ LockSystem LockSystem
+ // PropSystem is an optional property management system. If non-nil, TODO.
+ PropSystem PropSystem
+ // Logger is an optional error logger. If non-nil, it will be called
+ // whenever handling a http.Request results in an error.
+ Logger func(*http.Request, error)
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ status, err := http.StatusBadRequest, error(nil)
+ if h.FileSystem == nil {
+ status, err = http.StatusInternalServerError, errNoFileSystem
+ } else if h.LockSystem == nil {
+ status, err = http.StatusInternalServerError, errNoLockSystem
+ } else {
+ // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods. Also, OPTIONS??
+ switch r.Method {
+ case "GET", "HEAD", "POST":
+ status, err = h.handleGetHeadPost(w, r)
+ case "DELETE":
+ status, err = h.handleDelete(w, r)
+ case "PUT":
+ status, err = h.handlePut(w, r)
+ case "MKCOL":
+ status, err = h.handleMkcol(w, r)
+ case "LOCK":
+ status, err = h.handleLock(w, r)
+ case "UNLOCK":
+ status, err = h.handleUnlock(w, r)
+ }
+ }
+
+ if status != 0 {
+ w.WriteHeader(status)
+ if status != http.StatusNoContent {
+ w.Write([]byte(StatusText(status)))
+ }
+ }
+ if h.Logger != nil && err != nil {
+ h.Logger(r, err)
+ }
+}
+
+func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, err error) {
+ ih, ok := parseIfHeader(r.Header.Get("If"))
+ if !ok {
+ return nil, http.StatusBadRequest, errInvalidIfHeader
+ }
+ // ih is a disjunction (OR) of ifLists, so any ifList will do.
+ for _, l := range ih.lists {
+ path := l.resourceTag
+ if path == "" {
+ path = r.URL.Path
+ }
+ closer, err = h.LockSystem.Confirm(path, l.conditions...)
+ if err == ErrConfirmationFailed {
+ continue
+ }
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
+ return closer, 0, nil
+ }
+ return nil, http.StatusPreconditionFailed, errLocked
+}
+
+func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ // TODO: check locks for read-only access??
+ f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDONLY, 0)
+ if err != nil {
+ return http.StatusNotFound, err
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ return http.StatusNotFound, err
+ }
+ http.ServeContent(w, r, r.URL.Path, fi.ModTime(), f)
+ return 0, nil
+}
+
+func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ closer, status, err := h.confirmLocks(r)
+ if err != nil {
+ return status, err
+ }
+ defer closer.Close()
+
+ if err := h.FileSystem.RemoveAll(r.URL.Path); err != nil {
+ // TODO: MultiStatus.
+ return http.StatusMethodNotAllowed, err
+ }
+ return http.StatusNoContent, nil
+}
+
+func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ closer, status, err := h.confirmLocks(r)
+ if err != nil {
+ return status, err
+ }
+ defer closer.Close()
+
+ f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ return http.StatusNotFound, err
+ }
+ defer f.Close()
+ if _, err := io.Copy(f, r.Body); err != nil {
+ return http.StatusMethodNotAllowed, err
+ }
+ return http.StatusCreated, nil
+}
+
+func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ closer, status, err := h.confirmLocks(r)
+ if err != nil {
+ return status, err
+ }
+ defer closer.Close()
+
+ if err := h.FileSystem.Mkdir(r.URL.Path, 0777); err != nil {
+ if os.IsNotExist(err) {
+ return http.StatusConflict, err
+ }
+ return http.StatusMethodNotAllowed, err
+ }
+ return http.StatusCreated, nil
+}
+
+func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus int, retErr error) {
+ duration, err := parseTimeout(r.Header.Get("Timeout"))
+ if err != nil {
+ return http.StatusBadRequest, err
+ }
+ li, status, err := readLockInfo(r.Body)
+ if err != nil {
+ return status, err
+ }
+
+ token, ld := "", LockDetails{}
+ if li == (lockInfo{}) {
+ // An empty lockInfo means to refresh the lock.
+ ih, ok := parseIfHeader(r.Header.Get("If"))
+ if !ok {
+ return http.StatusBadRequest, errInvalidIfHeader
+ }
+ if len(ih.lists) == 1 && len(ih.lists[0].conditions) == 1 {
+ token = ih.lists[0].conditions[0].Token
+ }
+ if token == "" {
+ return http.StatusBadRequest, errInvalidLockToken
+ }
+ var closer io.Closer
+ ld, closer, err = h.LockSystem.Refresh(token, time.Now(), duration)
+ if err != nil {
+ if err == ErrNoSuchLock {
+ return http.StatusPreconditionFailed, err
+ }
+ return http.StatusInternalServerError, err
+ }
+ defer closer.Close()
+
+ } else {
+ depth, err := parseDepth(r.Header.Get("Depth"))
+ if err != nil {
+ return http.StatusBadRequest, err
+ }
+ ld = LockDetails{
+ Depth: depth,
+ Duration: duration,
+ OwnerXML: li.Owner.InnerXML,
+ Path: r.URL.Path,
+ }
+ var closer io.Closer
+ token, closer, err = h.LockSystem.Create(r.URL.Path, time.Now(), ld)
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+ defer func() {
+ if retErr != nil {
+ h.LockSystem.Unlock(token)
+ }
+ }()
+ defer closer.Close()
+
+ // Create the resource if it didn't previously exist.
+ if _, err := h.FileSystem.Stat(r.URL.Path); err != nil {
+ f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ if err != nil {
+ // TODO: detect missing intermediate dirs and return http.StatusConflict?
+ return http.StatusInternalServerError, err
+ }
+ f.Close()
+ w.WriteHeader(http.StatusCreated)
+ // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
+ // Lock-Token value is a Coded-URL. We add angle brackets.
+ w.Header().Set("Lock-Token", "<"+token+">")
+ }
+ }
+
+ w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+ writeLockInfo(w, token, ld)
+ return 0, nil
+}
+
+func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request) (status int, err error) {
+ // http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
+ // Lock-Token value is a Coded-URL. We strip its angle brackets.
+ t := r.Header.Get("Lock-Token")
+ if len(t) < 2 || t[0] != '<' || t[len(t)-1] != '>' {
+ return http.StatusBadRequest, errInvalidLockToken
+ }
+ t = t[1 : len(t)-1]
+
+ switch err = h.LockSystem.Unlock(t); err {
+ case nil:
+ return http.StatusNoContent, err
+ case ErrForbidden:
+ return http.StatusForbidden, err
+ case ErrNoSuchLock:
+ return http.StatusConflict, err
+ default:
+ return http.StatusInternalServerError, err
+ }
+}
+
+func parseDepth(s string) (int, error) {
+ // TODO: implement.
+ return -1, nil
+}
+
+func parseTimeout(s string) (time.Duration, error) {
+ // TODO: implement.
+ return 1 * time.Second, nil
+}
+
+// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
+const (
+ StatusMulti = 207
+ StatusUnprocessableEntity = 422
+ StatusLocked = 423
+ StatusFailedDependency = 424
+ StatusInsufficientStorage = 507
+)
+
+func StatusText(code int) string {
+ switch code {
+ case StatusMulti:
+ return "Multi-Status"
+ case StatusUnprocessableEntity:
+ return "Unprocessable Entity"
+ case StatusLocked:
+ return "Locked"
+ case StatusFailedDependency:
+ return "Failed Dependency"
+ case StatusInsufficientStorage:
+ return "Insufficient Storage"
+ }
+ return http.StatusText(code)
+}
+
+var (
+ errInvalidIfHeader = errors.New("webdav: invalid If header")
+ errInvalidLockInfo = errors.New("webdav: invalid lock info")
+ errInvalidLockToken = errors.New("webdav: invalid lock token")
+ errLocked = errors.New("webdav: locked")
+ errNoFileSystem = errors.New("webdav: no file system")
+ errNoLockSystem = errors.New("webdav: no lock system")
+ errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
+)
« no previous file with comments | « webdav/lock.go ('k') | webdav/xml.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b