webdav: Support empty multistatus responses and simplify error handling.
Currently, the package-internal multistatus writer does not allow to
write empty XML multistatus elements. This conflicts with RFC 4918
(see [1]), and prevents to reuse the writer to implement REPORTs and
other WebDAV extensions. This CL adds support to write empty multi-
status responses. Based on this, it simplifies the error handling of
PROPFIND and PROPPATCH request handling.
[1] http://webdav.org/specs/rfc4918.html#ELEMENT_multistatus
Change-Id: I6ae56ec261ad3a0763b6c197ad1e9c2e30d988a6
Reviewed-on: https://go-review.googlesource.com/9186
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/webdav/webdav.go b/webdav/webdav.go
index 51052fd..897acb2 100644
--- a/webdav/webdav.go
+++ b/webdav/webdav.go
@@ -500,18 +500,15 @@
return mw.write(makePropstatResponse(path, pstats))
}
- err = walkFS(h.FileSystem, depth, r.URL.Path, fi, walkFn)
- if mw.enc == nil {
- if err == nil {
- err = errEmptyMultistatus
- }
- // Not a single response has been written.
- return http.StatusInternalServerError, err
+ walkErr := walkFS(h.FileSystem, depth, r.URL.Path, fi, walkFn)
+ closeErr := mw.close()
+ if walkErr != nil {
+ return http.StatusInternalServerError, walkErr
}
- if err != nil {
- return 0, err
+ if closeErr != nil {
+ return http.StatusInternalServerError, closeErr
}
- return 0, mw.close()
+ return 0, nil
}
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (status int, err error) {
@@ -541,7 +538,10 @@
if writeErr != nil {
return http.StatusInternalServerError, writeErr
}
- return 0, closeErr
+ if closeErr != nil {
+ return http.StatusInternalServerError, closeErr
+ }
+ return 0, nil
}
// davHeaderNames maps the names of DAV properties to their corresponding
@@ -638,7 +638,6 @@
var (
errDestinationEqualsSource = errors.New("webdav: destination equals source")
errDirectoryNotEmpty = errors.New("webdav: directory not empty")
- errEmptyMultistatus = errors.New("webdav: empty multistatus response")
errInvalidDepth = errors.New("webdav: invalid depth")
errInvalidDestination = errors.New("webdav: invalid destination")
errInvalidIfHeader = errors.New("webdav: invalid If header")
diff --git a/webdav/xml.go b/webdav/xml.go
index c7bb5c0..cfb56e8 100644
--- a/webdav/xml.go
+++ b/webdav/xml.go
@@ -264,31 +264,39 @@
return errInvalidResponse
}
}
- if w.enc == nil {
- w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
- w.w.WriteHeader(StatusMulti)
- _, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
- if err != nil {
- return err
- }
- w.enc = xml.NewEncoder(w.w)
- err = w.enc.EncodeToken(xml.StartElement{
- Name: xml.Name{
- Space: "DAV:",
- Local: "multistatus",
- },
- Attr: []xml.Attr{{
- Name: xml.Name{Local: "xmlns"},
- Value: "DAV:",
- }},
- })
- if err != nil {
- return err
- }
+ err := w.writeHeader()
+ if err != nil {
+ return err
}
return w.enc.Encode(r)
}
+// writeHeader writes a XML multistatus start element on w's underlying
+// http.ResponseWriter and returns the result of the write operation.
+// After the first write attempt, writeHeader becomes a no-op.
+func (w *multistatusWriter) writeHeader() error {
+ if w.enc != nil {
+ return nil
+ }
+ w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
+ w.w.WriteHeader(StatusMulti)
+ _, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
+ if err != nil {
+ return err
+ }
+ w.enc = xml.NewEncoder(w.w)
+ return w.enc.EncodeToken(xml.StartElement{
+ Name: xml.Name{
+ Space: "DAV:",
+ Local: "multistatus",
+ },
+ Attr: []xml.Attr{{
+ Name: xml.Name{Local: "xmlns"},
+ Value: "DAV:",
+ }},
+ })
+}
+
// Close completes the marshalling of the multistatus response. It returns
// an error if the multistatus response could not be completed. If both the
// return value and field enc of w are nil, then no multistatus response has
diff --git a/webdav/xml_test.go b/webdav/xml_test.go
index 0559f86..ce40c55 100644
--- a/webdav/xml_test.go
+++ b/webdav/xml_test.go
@@ -348,12 +348,13 @@
///The "section x.y.z" test cases come from section x.y.z of the spec at
// http://www.webdav.org/specs/rfc4918.html
testCases := []struct {
- desc string
- responses []response
- respdesc string
- wantXML string
- wantCode int
- wantErr error
+ desc string
+ responses []response
+ respdesc string
+ writeHeader bool
+ wantXML string
+ wantCode int
+ wantErr error
}{{
desc: "section 9.2.2 (failed dependency)",
responses: []response{{
@@ -472,15 +473,20 @@
`</multistatus>`,
wantCode: StatusMulti,
}, {
- desc: "bad: no response written",
+ desc: "no response written",
// default of http.responseWriter
wantCode: http.StatusOK,
}, {
- desc: "bad: no response written (with description)",
+ desc: "no response written (with description)",
respdesc: "too bad",
// default of http.responseWriter
wantCode: http.StatusOK,
}, {
+ desc: "empty multistatus with header",
+ writeHeader: true,
+ wantXML: `<multistatus xmlns="DAV:"></multistatus>`,
+ wantCode: StatusMulti,
+ }, {
desc: "bad: no href",
responses: []response{{
Propstat: []propstat{{
@@ -556,6 +562,12 @@
for _, tc := range testCases {
rec := httptest.NewRecorder()
w := multistatusWriter{w: rec, responseDescription: tc.respdesc}
+ if tc.writeHeader {
+ if err := w.writeHeader(); err != nil {
+ t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err)
+ continue
+ }
+ }
for _, r := range tc.responses {
if err := w.write(&r); err != nil {
if err != tc.wantErr {