Expose MultistatusWriter

This commit is contained in:
emersion 2017-09-11 16:27:50 +02:00
parent 9131ab3eec
commit 777948e9c1
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
3 changed files with 51 additions and 40 deletions

View File

@ -8,7 +8,6 @@ package webdav
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -560,7 +559,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
return status, err return status, err
} }
mw := multistatusWriter{w: w} mw := MultistatusWriter{w: w}
walkFn := func(reqPath string, info os.FileInfo, err error) error { walkFn := func(reqPath string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@ -585,11 +584,11 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
if err != nil { if err != nil {
return err return err
} }
return mw.write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats)) return mw.Write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats))
} }
walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn) walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
closeErr := mw.close() closeErr := mw.Close()
if walkErr != nil { if walkErr != nil {
return http.StatusInternalServerError, walkErr return http.StatusInternalServerError, walkErr
} }
@ -628,9 +627,9 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
mw := multistatusWriter{w: w} mw := MultistatusWriter{w: w}
writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats)) writeErr := mw.Write(makePropstatResponse(r.URL.Path, pstats))
closeErr := mw.close() closeErr := mw.Close()
if writeErr != nil { if writeErr != nil {
return http.StatusInternalServerError, writeErr return http.StatusInternalServerError, writeErr
} }
@ -640,24 +639,11 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
return 0, nil return 0, nil
} }
func makePropstatResponse(href string, pstats []Propstat) *response { func makePropstatResponse(href string, pstats []Propstat) *Response {
resp := response{ return &Response{
Href: []string{(&url.URL{Path: href}).EscapedPath()}, Href: []string{(&url.URL{Path: href}).EscapedPath()},
Propstat: make([]propstat, 0, len(pstats)), Propstat: pstats,
} }
for _, p := range pstats {
var xmlErr *xmlError
if p.XMLError != "" {
xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
}
resp.Propstat = append(resp.Propstat, propstat{
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
Prop: p.Props,
ResponseDescription: p.ResponseDescription,
Error: xmlErr,
})
}
return &resp
} }
const ( const (

55
xml.go
View File

@ -18,7 +18,7 @@ import (
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo // http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo
type lockInfo struct { type lockInfo struct {
XMLName xml.Name `xml:"lockinfo"` XMLName xml.Name `xml:"lockinfo"`
Exclusive *struct{} `xml:"lockscope>exclusive"` Exclusive *struct{} `xml:"lockscope>exclusive"`
Shared *struct{} `xml:"lockscope>shared"` Shared *struct{} `xml:"lockscope>shared"`
Write *struct{} `xml:"locktype>write"` Write *struct{} `xml:"locktype>write"`
@ -149,7 +149,7 @@ func (pn *propfindProps) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind
type propfind struct { type propfind struct {
XMLName xml.Name `xml:"DAV: propfind"` XMLName xml.Name `xml:"DAV: propfind"`
Allprop *struct{} `xml:"DAV: allprop"` Allprop *struct{} `xml:"DAV: allprop"`
Propname *struct{} `xml:"DAV: propname"` Propname *struct{} `xml:"DAV: propname"`
Prop propfindProps `xml:"DAV: prop"` Prop propfindProps `xml:"DAV: prop"`
@ -208,7 +208,7 @@ type Property struct {
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
type xmlError struct { type xmlError struct {
XMLName xml.Name `xml:"DAV: error"` XMLName xml.Name `xml:"DAV: error"`
InnerXML []byte `xml:",innerxml"` InnerXML []byte `xml:",innerxml"`
} }
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
@ -219,9 +219,18 @@ type propstat struct {
ResponseDescription string `xml:"DAV: responsedescription,omitempty"` ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
} }
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
type Response struct {
Href []string
Propstat []Propstat
Status int
XMLError string
ResponseDescription string
}
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
type response struct { type response struct {
XMLName xml.Name `xml:"DAV: response"` XMLName xml.Name `xml:"DAV: response"`
Href []string `xml:"DAV: href"` Href []string `xml:"DAV: href"`
Propstat []propstat `xml:"DAV: propstat"` Propstat []propstat `xml:"DAV: propstat"`
Status string `xml:"DAV: status,omitempty"` Status string `xml:"DAV: status,omitempty"`
@ -232,13 +241,7 @@ type response struct {
// MultistatusWriter marshals one or more Responses into a XML // MultistatusWriter marshals one or more Responses into a XML
// multistatus response. // multistatus response.
// See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus
// TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as type MultistatusWriter struct {
// "DAV:" on this element, is prepended on the nested response, as well as on all
// its nested elements. All property names in the DAV: namespace are prefixed as
// well. This is because some versions of Mini-Redirector (on windows 7) ignore
// elements with a default namespace (no prefixed namespace). A less intrusive fix
// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
type multistatusWriter struct {
// ResponseDescription contains the optional responsedescription // ResponseDescription contains the optional responsedescription
// of the multistatus XML element. Only the latest content before // of the multistatus XML element. Only the latest content before
// close will be emitted. Empty response descriptions are not // close will be emitted. Empty response descriptions are not
@ -257,7 +260,29 @@ type multistatusWriter struct {
// first, valid response to be written, Write prepends the XML representation // first, valid response to be written, Write prepends the XML representation
// of r with a multistatus tag. Callers must call close after the last response // of r with a multistatus tag. Callers must call close after the last response
// has been written. // has been written.
func (w *multistatusWriter) write(r *response) error { func (w *MultistatusWriter) Write(r *Response) error {
rr := &response{
Href: r.Href,
Propstat: make([]propstat, 0, len(r.Propstat)),
}
for _, p := range r.Propstat {
var xmlErr *xmlError
if p.XMLError != "" {
xmlErr = &xmlError{InnerXML: []byte(p.XMLError)}
}
rr.Propstat = append(rr.Propstat, propstat{
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, StatusText(p.Status)),
Prop: p.Props,
ResponseDescription: p.ResponseDescription,
Error: xmlErr,
})
}
return w.write(rr)
}
func (w *MultistatusWriter) write(r *response) error {
switch len(r.Href) { switch len(r.Href) {
case 0: case 0:
return errInvalidResponse return errInvalidResponse
@ -280,7 +305,7 @@ func (w *multistatusWriter) write(r *response) error {
// writeHeader writes a XML multistatus start element on w's underlying // writeHeader writes a XML multistatus start element on w's underlying
// http.ResponseWriter and returns the result of the write operation. // http.ResponseWriter and returns the result of the write operation.
// After the first write attempt, writeHeader becomes a no-op. // After the first write attempt, writeHeader becomes a no-op.
func (w *multistatusWriter) writeHeader() error { func (w *MultistatusWriter) writeHeader() error {
if w.enc != nil { if w.enc != nil {
return nil return nil
} }
@ -307,7 +332,7 @@ func (w *multistatusWriter) writeHeader() error {
// an error if the multistatus response could not be completed. If both the // 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 // return value and field enc of w are nil, then no multistatus response has
// been written. // been written.
func (w *multistatusWriter) close() error { func (w *MultistatusWriter) Close() error {
if w.enc == nil { if w.enc == nil {
return nil return nil
} }
@ -420,7 +445,7 @@ type setRemove struct {
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate
type propertyupdate struct { type propertyupdate struct {
XMLName xml.Name `xml:"DAV: propertyupdate"` XMLName xml.Name `xml:"DAV: propertyupdate"`
Lang string `xml:"xml:lang,attr,omitempty"` Lang string `xml:"xml:lang,attr,omitempty"`
SetRemove []setRemove `xml:",any"` SetRemove []setRemove `xml:",any"`
} }

View File

@ -564,7 +564,7 @@ func TestMultistatusWriter(t *testing.T) {
loop: loop:
for _, tc := range testCases { for _, tc := range testCases {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
w := multistatusWriter{w: rec, responseDescription: tc.respdesc} w := MultistatusWriter{w: rec, responseDescription: tc.respdesc}
if tc.writeHeader { if tc.writeHeader {
if err := w.writeHeader(); err != nil { if err := w.writeHeader(); err != nil {
t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err) t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err)
@ -580,7 +580,7 @@ loop:
continue loop continue loop
} }
} }
if err := w.close(); err != tc.wantErr { if err := w.Close(); err != tc.wantErr {
t.Errorf("%s: got close error %v, want %v", t.Errorf("%s: got close error %v, want %v",
tc.desc, err, tc.wantErr) tc.desc, err, tc.wantErr)
continue continue