diff --git a/internal/elements.go b/internal/elements.go index dacf6b1..173a1ae 100644 --- a/internal/elements.go +++ b/internal/elements.go @@ -117,7 +117,7 @@ func (resp *Response) DecodeProp(v interface{}) error { propstat := &resp.Propstats[i] for j := range propstat.Prop.Raw { raw := &propstat.Prop.Raw[j] - if start, ok := raw.tok.(xml.StartElement); ok && name == start.Name { + if n, ok := raw.XMLName(); ok && name == n { if err := propstat.Status.Err(); err != nil { return err } @@ -183,16 +183,6 @@ func EncodeProp(values ...interface{}) (*Prop, error) { return &Prop{Raw: l}, nil } -func (prop *Prop) XMLNames() []xml.Name { - l := make([]xml.Name, 0, len(prop.Raw)) - for _, raw := range prop.Raw { - if start, ok := raw.tok.(xml.StartElement); ok { - l = append(l, start.Name) - } - } - return l -} - // https://tools.ietf.org/html/rfc4918#section-14.20 type Propfind struct { XMLName xml.Name `xml:"DAV: propfind"` @@ -232,7 +222,7 @@ func NewResourceType(names ...xml.Name) *ResourceType { func (t *ResourceType) Is(name xml.Name) bool { for _, raw := range t.Raw { - if start, ok := raw.tok.(xml.StartElement); ok && name == start.Name { + if n, ok := raw.XMLName(); ok && name == n { return true } } diff --git a/internal/server.go b/internal/server.go index e88aed2..0db957b 100644 --- a/internal/server.go +++ b/internal/server.go @@ -13,6 +13,17 @@ type HTTPError struct { Err error } +func HTTPErrorFromError(err error) *HTTPError { + if err == nil { + return nil + } + if httpErr, ok := err.(*HTTPError); ok { + return httpErr + } else { + return &HTTPError{http.StatusInternalServerError, err} + } +} + func HTTPErrorf(code int, format string, a ...interface{}) *HTTPError { return &HTTPError{code, fmt.Errorf(format, a...)} } @@ -104,3 +115,68 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error { w.Write([]byte(xml.Header)) return xml.NewEncoder(w).Encode(&ms) } + +type PropfindFunc func(raw *RawXMLValue) (interface{}, error) + +func NewPropfindResponse(href string, propfind *Propfind, props map[xml.Name]PropfindFunc) (*Response, error) { + resp := NewOKResponse(href) + + if propfind.PropName != nil { + for xmlName, _ := range props { + emptyVal := NewRawXMLElement(xmlName, nil, nil) + if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil { + return nil, err + } + } + } else if propfind.AllProp != nil { + // TODO: add support for propfind.Include + for xmlName, f := range props { + emptyVal := NewRawXMLElement(xmlName, nil, nil) + + val, err := f(emptyVal) + + code := http.StatusOK + if err != nil { + // TODO: don't throw away error message here + code = HTTPErrorFromError(err).Code + val = emptyVal + } + + if err := resp.EncodeProp(code, val); err != nil { + return nil, err + } + } + } else if prop := propfind.Prop; prop != nil { + for _, raw := range prop.Raw { + xmlName, ok := raw.XMLName() + if !ok { + continue + } + + emptyVal := NewRawXMLElement(xmlName, nil, nil) + + var code int + var val interface{} = emptyVal + f, ok := props[xmlName] + if ok { + if v, err := f(&raw); err != nil { + // TODO: don't throw away error message here + code = HTTPErrorFromError(err).Code + } else { + code = http.StatusOK + val = v + } + } else { + code = http.StatusNotFound + } + + if err := resp.EncodeProp(code, val); err != nil { + return nil, err + } + } + } else { + return nil, HTTPErrorf(http.StatusBadRequest, "webdav: propfind request missing propname, allprop or prop element") + } + + return resp, nil +} diff --git a/internal/xml.go b/internal/xml.go index 69d5ffc..2f4c61c 100644 --- a/internal/xml.go +++ b/internal/xml.go @@ -89,6 +89,13 @@ func (val *RawXMLValue) Decode(v interface{}) error { return xml.NewTokenDecoder(val.TokenReader()).Decode(&v) } +func (val *RawXMLValue) XMLName() (name xml.Name, ok bool) { + if start, ok := val.tok.(xml.StartElement); ok { + return start.Name, true + } + return xml.Name{}, false +} + // TokenReader returns a stream of tokens for the XML value. func (val *RawXMLValue) TokenReader() xml.TokenReader { if val.out != nil { diff --git a/server.go b/server.go index f6ab544..ab60026 100644 --- a/server.go +++ b/server.go @@ -135,99 +135,33 @@ func (b *backend) propfind(propfind *internal.Propfind, name string, fi os.FileI } func (b *backend) propfindFile(propfind *internal.Propfind, name string, fi os.FileInfo) (*internal.Response, error) { - resp := internal.NewOKResponse(name) + props := make(map[xml.Name]internal.PropfindFunc) - if propfind.PropName != nil { - for xmlName, f := range liveProps { - emptyVal := internal.NewRawXMLElement(xmlName, nil, nil) - - _, err := f(b, name, fi) - if err != nil { - continue - } - - if err := resp.EncodeProp(http.StatusOK, emptyVal); err != nil { - return nil, err - } - } - } else if propfind.AllProp != nil { - // TODO: add support for propfind.Include - for _, f := range liveProps { - val, err := f(b, name, fi) - if err != nil { - continue - } - - if err := resp.EncodeProp(http.StatusOK, val); err != nil { - return nil, err - } - } - } else if prop := propfind.Prop; prop != nil { - for _, xmlName := range prop.XMLNames() { - emptyVal := internal.NewRawXMLElement(xmlName, nil, nil) - - var code int - var val interface{} = emptyVal - f, ok := liveProps[xmlName] - if ok { - if v, err := f(b, name, fi); err != nil { - // TODO: don't throw away error message here - if httpErr, ok := err.(*internal.HTTPError); ok { - code = httpErr.Code - } else { - code = http.StatusInternalServerError - } - } else { - code = http.StatusOK - val = v - } - } else { - code = http.StatusNotFound - } - - if err := resp.EncodeProp(code, val); err != nil { - return nil, err - } - } - } else { - return nil, internal.HTTPErrorf(http.StatusBadRequest, "webdav: propfind request missing propname, allprop or prop element") - } - - return resp, nil -} - -type PropfindFunc func(b *backend, name string, fi os.FileInfo) (interface{}, error) - -var liveProps = map[xml.Name]PropfindFunc{ - {"DAV:", "resourcetype"}: func(b *backend, name string, fi os.FileInfo) (interface{}, error) { + props[xml.Name{"DAV:", "resourcetype"}] = func(*internal.RawXMLValue) (interface{}, error) { var types []xml.Name if fi.IsDir() { types = append(types, internal.CollectionName) } return internal.NewResourceType(types...), nil - }, - {"DAV:", "getcontentlength"}: func(b *backend, name string, fi os.FileInfo) (interface{}, error) { - if fi.IsDir() { - return nil, &internal.HTTPError{Code: http.StatusNotFound} + } + + if !fi.IsDir() { + props[xml.Name{"DAV:", "getcontentlength"}] = func(*internal.RawXMLValue) (interface{}, error) { + return &internal.GetContentLength{Length: fi.Size()}, nil } - return &internal.GetContentLength{Length: fi.Size()}, nil - }, - {"DAV:", "getcontenttype"}: func(b *backend, name string, fi os.FileInfo) (interface{}, error) { - if fi.IsDir() { - return nil, &internal.HTTPError{Code: http.StatusNotFound} + props[xml.Name{"DAV:", "getcontenttype"}] = func(*internal.RawXMLValue) (interface{}, error) { + t := mime.TypeByExtension(path.Ext(name)) + if t == "" { + // TODO: use http.DetectContentType + return nil, &internal.HTTPError{Code: http.StatusNotFound} + } + return &internal.GetContentType{Type: t}, nil } - t := mime.TypeByExtension(path.Ext(name)) - if t == "" { - // TODO: use http.DetectContentType - return nil, &internal.HTTPError{Code: http.StatusNotFound} + props[xml.Name{"DAV:", "getlastmodified"}] = func(*internal.RawXMLValue) (interface{}, error) { + return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime())}, nil } - return &internal.GetContentType{Type: t}, nil - }, - {"DAV:", "getlastmodified"}: func(b *backend, name string, fi os.FileInfo) (interface{}, error) { - if fi.IsDir() { - return nil, &internal.HTTPError{Code: http.StatusNotFound} - } - return &internal.GetLastModified{LastModified: internal.Time(fi.ModTime())}, nil - }, - // TODO: getetag + // TODO: getetag + } + + return internal.NewPropfindResponse(name, propfind, props) }