From 5748fec4d04d25ccf584e9f63749644c1e878a6a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 14 Jan 2020 21:29:54 +0100 Subject: [PATCH] internal: add helpers to parse multistatus --- client.go | 19 +++----- elements.go | 7 ++- internal/client.go | 6 +-- internal/elements.go | 100 ++++++++++++++++++++++++++++++++++++------- 4 files changed, 99 insertions(+), 33 deletions(-) diff --git a/client.go b/client.go index 01b750b..0bcae6d 100644 --- a/client.go +++ b/client.go @@ -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 } diff --git a/elements.go b/elements.go index 28e85d7..04abb0c 100644 --- a/elements.go +++ b/elements.go @@ -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"` } diff --git a/internal/client.go b/internal/client.go index 3fcf3c7..e9fb89c 100644 --- a/internal/client.go +++ b/internal/client.go @@ -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 } diff --git a/internal/elements.go b/internal/elements.go index c11c9c6..a37272c 100644 --- a/internal/elements.go +++ b/internal/elements.go @@ -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}} }