internal: add helpers to parse multistatus

This commit is contained in:
Simon Ser 2020-01-14 21:29:54 +01:00
parent 93f95c7fd2
commit 5748fec4d0
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
4 changed files with 99 additions and 33 deletions

View File

@ -2,7 +2,6 @@ package webdav
import (
"encoding/xml"
"fmt"
"net/http"
"github.com/emersion/go-webdav/internal"
@ -24,31 +23,25 @@ func (c *Client) FindCurrentUserPrincipal() (string, error) {
name := xml.Name{"DAV:", "current-user-principal"}
propfind := internal.NewPropPropfind(name)
req, err := c.c.NewXMLRequest("PROPFIND", "", propfind)
req, err := c.c.NewXMLRequest("PROPFIND", "/", propfind)
if err != nil {
return "", err
}
req.Header.Add("Depth", "0")
resps, err := c.c.DoMultiStatus(req)
ms, err := c.c.DoMultiStatus(req)
if err != nil {
return "", err
}
if len(resps) != 1 {
return "", fmt.Errorf("expected exactly one response in multistatus, got %v", len(resps))
resp, err := ms.Get("/")
if err != nil {
return "", err
}
resp := &resps[0]
// TODO: handle propstats with errors
if len(resp.Propstats) != 1 {
return "", fmt.Errorf("expected exactly one propstat in response")
}
propstat := &resp.Propstats[0]
var prop currentUserPrincipalProp
if err := propstat.Prop.Decode(&prop); err != nil {
if err := resp.DecodeProp(name, &prop); err != nil {
return "", err
}

View File

@ -1,5 +1,10 @@
package webdav
import (
"encoding/xml"
)
type currentUserPrincipalProp struct {
Href string `xml:"current-user-principal>href"`
Name xml.Name `xml:"DAV: current-user-principal"`
Href string `xml:"href"`
}

View File

@ -64,7 +64,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) {
return c.http.Do(req)
}
func (c *Client) DoMultiStatus(req *http.Request) ([]Response, error) {
func (c *Client) DoMultiStatus(req *http.Request) (*Multistatus, error) {
resp, err := c.Do(req)
if err != nil {
return nil, err
@ -76,10 +76,10 @@ func (c *Client) DoMultiStatus(req *http.Request) ([]Response, error) {
}
// TODO: the response can be quite large, support streaming Response elements
var ms multistatus
var ms Multistatus
if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil {
return nil, err
}
return ms.Responses, nil
return &ms, nil
}

View File

@ -2,38 +2,107 @@ package internal
import (
"encoding/xml"
"fmt"
"net/http"
"strconv"
"strings"
)
type Status string
func (s Status) Err() error {
parts := strings.SplitN(string(s), " ", 3)
if len(parts) != 3 {
return fmt.Errorf("webdav: invalid HTTP status %q: expected 3 fields", s)
}
code, err := strconv.Atoi(parts[1])
if err != nil {
return fmt.Errorf("webdav: invalid HTTP status %q: failed to parse code: %v", s, err)
}
msg := parts[2]
// TODO: handle 2xx, 3xx
if code != http.StatusOK {
return fmt.Errorf("webdav: HTTP error: %v %v", code, msg)
}
return nil
}
// https://tools.ietf.org/html/rfc4918#section-14.16
type multistatus struct {
type Multistatus struct {
XMLName xml.Name `xml:"DAV: multistatus"`
Responses []Response `xml:"DAV: response"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
}
func (ms *Multistatus) Get(href string) (*Response, error) {
for i := range ms.Responses {
resp := &ms.Responses[i]
for _, h := range resp.Href {
if h == href {
return resp, nil
}
}
}
return nil, fmt.Errorf("webdav: missing response for href %q", href)
}
// https://tools.ietf.org/html/rfc4918#section-14.24
type Response struct {
XMLName xml.Name `xml:"DAV: response"`
Href string `xml:"DAV: href"`
Propstats []Propstat `xml:"DAV: propstat"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
// TODO: (href*, status)
// TODO: error?, location?
XMLName xml.Name `xml:"DAV: response"`
Href []string `xml:"DAV: href"`
Propstats []Propstat `xml:"DAV: propstat,omitempty"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
Status Status `xml:"DAV: status,omitempty"`
Error *RawXMLValue `xml:"DAV: error,omitempty"`
Location *Location `xml:"DAV: location,omitempty"`
}
func (resp *Response) DecodeProp(name xml.Name, v interface{}) error {
for i := range resp.Propstats {
propstat := &resp.Propstats[i]
for j := range propstat.Prop.Raw {
raw := &propstat.Prop.Raw[j]
if start, ok := raw.tok.(xml.StartElement); ok {
if name == start.Name {
if err := propstat.Status.Err(); err != nil {
return err
}
return raw.Decode(v)
}
}
}
}
return fmt.Errorf("webdav: missing prop %v %v in response", name.Space, name.Local)
}
// https://tools.ietf.org/html/rfc4918#section-14.9
type Location struct {
XMLName xml.Name `xml:"DAV: location"`
Href string `xml:"DAV: href"`
}
// https://tools.ietf.org/html/rfc4918#section-14.22
type Propstat struct {
XMLName xml.Name `xml:"DAV: propstat"`
Prop RawXMLValue `xml:"DAV: prop"`
Status string `xml:"DAV: status"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
// TODO: error?
XMLName xml.Name `xml:"DAV: propstat"`
Prop Prop `xml:"DAV: prop"`
Status Status `xml:"DAV: status"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
Error *RawXMLValue `xml:"DAV: error,omitempty"`
}
// https://tools.ietf.org/html/rfc4918#section-14.18
type Prop struct {
XMLName xml.Name `xml:"DAV: prop"`
Raw []RawXMLValue `xml:",any"`
}
// https://tools.ietf.org/html/rfc4918#section-14.20
type Propfind struct {
XMLName xml.Name `xml:"DAV: propfind"`
Prop *RawXMLValue `xml:"DAV: prop,omitempty"`
XMLName xml.Name `xml:"DAV: propfind"`
Prop *Prop `xml:"DAV: prop,omitempty"`
// TODO: propname | (allprop, include?)
}
@ -42,6 +111,5 @@ func NewPropPropfind(names ...xml.Name) *Propfind {
for i, name := range names {
children[i] = *NewRawXMLElement(name, nil, nil)
}
prop := NewRawXMLElement(xml.Name{"DAV:", "prop"}, nil, children)
return &Propfind{Prop: prop}
return &Propfind{Prop: &Prop{Raw: children}}
}