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

View File

@ -1,5 +1,10 @@
package webdav package webdav
import (
"encoding/xml"
)
type currentUserPrincipalProp struct { 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) 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) resp, err := c.Do(req)
if err != nil { if err != nil {
return nil, err 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 // 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 { if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil {
return nil, err return nil, err
} }
return ms.Responses, nil return &ms, nil
} }

View File

@ -2,38 +2,107 @@ package internal
import ( import (
"encoding/xml" "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 // https://tools.ietf.org/html/rfc4918#section-14.16
type multistatus struct { type Multistatus struct {
XMLName xml.Name `xml:"DAV: multistatus"` XMLName xml.Name `xml:"DAV: multistatus"`
Responses []Response `xml:"DAV: response"` Responses []Response `xml:"DAV: response"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"` 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 // https://tools.ietf.org/html/rfc4918#section-14.24
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"`
Propstats []Propstat `xml:"DAV: propstat"` Propstats []Propstat `xml:"DAV: propstat,omitempty"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"` ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
// TODO: (href*, status) Status Status `xml:"DAV: status,omitempty"`
// TODO: error?, location? 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 // https://tools.ietf.org/html/rfc4918#section-14.22
type Propstat struct { type Propstat struct {
XMLName xml.Name `xml:"DAV: propstat"` XMLName xml.Name `xml:"DAV: propstat"`
Prop RawXMLValue `xml:"DAV: prop"` Prop Prop `xml:"DAV: prop"`
Status string `xml:"DAV: status"` Status Status `xml:"DAV: status"`
ResponseDescription string `xml:"DAV: responsedescription,omitempty"` ResponseDescription string `xml:"DAV: responsedescription,omitempty"`
// TODO: error? 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 // https://tools.ietf.org/html/rfc4918#section-14.20
type Propfind struct { type Propfind struct {
XMLName xml.Name `xml:"DAV: propfind"` XMLName xml.Name `xml:"DAV: propfind"`
Prop *RawXMLValue `xml:"DAV: prop,omitempty"` Prop *Prop `xml:"DAV: prop,omitempty"`
// TODO: propname | (allprop, include?) // TODO: propname | (allprop, include?)
} }
@ -42,6 +111,5 @@ func NewPropPropfind(names ...xml.Name) *Propfind {
for i, name := range names { for i, name := range names {
children[i] = *NewRawXMLElement(name, nil, nil) children[i] = *NewRawXMLElement(name, nil, nil)
} }
prop := NewRawXMLElement(xml.Name{"DAV:", "prop"}, nil, children) return &Propfind{Prop: &Prop{Raw: children}}
return &Propfind{Prop: prop}
} }