Commit 7b488c19 authored by Nick Cooper's avatar Nick Cooper

webdav: fixes for RFC/litmus compliance.

- Handles absence of If header.
- Reject Mkcol requests with a body.
- Delete on non-existant item should return 404.
- Support OPTIONS request.
- Ensure logger is invoked for all requests.

See original CL: golang.org/cl/178930043

Change-Id: Ic96aed10c54bb5ed0641092178ad6f15b1440cb4
parent 7006f7a1
...@@ -26,7 +26,7 @@ type Handler struct { ...@@ -26,7 +26,7 @@ type Handler struct {
// PropSystem is an optional property management system. If non-nil, TODO. // PropSystem is an optional property management system. If non-nil, TODO.
PropSystem PropSystem PropSystem PropSystem
// Logger is an optional error logger. If non-nil, it will be called // Logger is an optional error logger. If non-nil, it will be called
// whenever handling a http.Request results in an error. // for all HTTP requests.
Logger func(*http.Request, error) Logger func(*http.Request, error)
} }
...@@ -37,8 +37,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -37,8 +37,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else if h.LockSystem == nil { } else if h.LockSystem == nil {
status, err = http.StatusInternalServerError, errNoLockSystem status, err = http.StatusInternalServerError, errNoLockSystem
} else { } else {
// TODO: COPY, MOVE, PROPFIND, PROPPATCH methods. Also, OPTIONS?? // TODO: COPY, MOVE, PROPFIND, PROPPATCH methods.
switch r.Method { switch r.Method {
case "OPTIONS":
status, err = h.handleOptions(w, r)
case "GET", "HEAD", "POST": case "GET", "HEAD", "POST":
status, err = h.handleGetHeadPost(w, r) status, err = h.handleGetHeadPost(w, r)
case "DELETE": case "DELETE":
...@@ -60,13 +62,23 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -60,13 +62,23 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(StatusText(status))) w.Write([]byte(StatusText(status)))
} }
} }
if h.Logger != nil && err != nil { if h.Logger != nil {
h.Logger(r, err) h.Logger(r, err)
} }
} }
type nopCloser struct{}
func (nopCloser) Close() error {
return nil
}
func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, err error) { func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, err error) {
ih, ok := parseIfHeader(r.Header.Get("If")) hdr := r.Header.Get("If")
if hdr == "" {
return nopCloser{}, 0, nil
}
ih, ok := parseIfHeader(hdr)
if !ok { if !ok {
return nil, http.StatusBadRequest, errInvalidIfHeader return nil, http.StatusBadRequest, errInvalidIfHeader
} }
...@@ -88,6 +100,24 @@ func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, e ...@@ -88,6 +100,24 @@ func (h *Handler) confirmLocks(r *http.Request) (closer io.Closer, status int, e
return nil, http.StatusPreconditionFailed, errLocked return nil, http.StatusPreconditionFailed, errLocked
} }
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status int, err error) {
allow := "OPTIONS, LOCK, PUT, MKCOL"
if fi, err := h.FileSystem.Stat(r.URL.Path); err == nil {
if fi.IsDir() {
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, UNLOCK, PUT, PROPFIND"
} else {
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, UNLOCK"
}
}
// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
w.Header().Set("DAV", "1, 2")
// http://msdn.microsoft.com/en-au/library/cc250217.aspx
w.Header().Set("MS-Author-Via", "DAV")
w.Header().Set("Allow", allow)
return 0, nil
}
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) { func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (status int, err error) {
// TODO: check locks for read-only access?? // TODO: check locks for read-only access??
f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDONLY, 0) f, err := h.FileSystem.OpenFile(r.URL.Path, os.O_RDONLY, 0)
...@@ -111,6 +141,9 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i ...@@ -111,6 +141,9 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
defer closer.Close() defer closer.Close()
if err := h.FileSystem.RemoveAll(r.URL.Path); err != nil { if err := h.FileSystem.RemoveAll(r.URL.Path); err != nil {
if os.IsNotExist(err) {
return http.StatusNotFound, err
}
// TODO: MultiStatus. // TODO: MultiStatus.
return http.StatusMethodNotAllowed, err return http.StatusMethodNotAllowed, err
} }
...@@ -142,6 +175,9 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in ...@@ -142,6 +175,9 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
} }
defer closer.Close() defer closer.Close()
if r.ContentLength > 0 {
return http.StatusUnsupportedMediaType, nil
}
if err := h.FileSystem.Mkdir(r.URL.Path, 0777); err != nil { if err := h.FileSystem.Mkdir(r.URL.Path, 0777); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return http.StatusConflict, err return http.StatusConflict, err
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment