diff --git a/carddav/carddav.go b/carddav/carddav.go index 61d50bb..410d250 100644 --- a/carddav/carddav.go +++ b/carddav/carddav.go @@ -1,3 +1,18 @@ package carddav +import ( + "encoding/xml" +) + const namespace = "urn:ietf:params:xml:ns:carddav" + +type AddressBook struct { + Href string + Description string +} + +var addressBookName = xml.Name{namespace, "addressbook"} + +type AddressBookQuery struct { + AddressDataProps []string +} diff --git a/carddav/client.go b/carddav/client.go index 44723c4..a0bdba4 100644 --- a/carddav/client.go +++ b/carddav/client.go @@ -26,7 +26,7 @@ func NewClient(c *http.Client, endpoint string) (*Client, error) { return &Client{wc, ic}, nil } -func (c *Client) FindAddressbookHomeSet(principal string) (string, error) { +func (c *Client) FindAddressBookHomeSet(principal string) (string, error) { name := xml.Name{namespace, "addressbook-home-set"} propfind := internal.NewPropPropfind(name) @@ -42,3 +42,50 @@ func (c *Client) FindAddressbookHomeSet(principal string) (string, error) { return prop.Href, nil } + +func (c *Client) FindAddressBooks(addressBookHomeSet string) ([]AddressBook, error) { + resTypeName := xml.Name{"DAV:", "resourcetype"} + descName := xml.Name{namespace, "addressbook-description"} + propfind := internal.NewPropPropfind(resTypeName, descName) + + req, err := c.ic.NewXMLRequest("PROPFIND", addressBookHomeSet, propfind) + if err != nil { + return nil, err + } + + req.Header.Add("Depth", "1") + + ms, err := c.ic.DoMultiStatus(req) + if err != nil { + return nil, err + } + + l := make([]AddressBook, 0, len(ms.Responses)) + for i := range ms.Responses { + resp := &ms.Responses[i] + href, err := resp.Href() + if err != nil { + return nil, err + } + + var resTypeProp internal.ResourceType + if err := resp.DecodeProp(resTypeName, &resTypeProp); err != nil { + return nil, err + } + if !resTypeProp.Is(addressBookName) { + continue + } + + var descProp addressbookDescription + if err := resp.DecodeProp(descName, &descProp); err != nil { + return nil, err + } + + l = append(l, AddressBook{ + Href: href, + Description: descProp.Data, + }) + } + + return l, nil +} diff --git a/carddav/elements.go b/carddav/elements.go index 8188585..1a332ac 100644 --- a/carddav/elements.go +++ b/carddav/elements.go @@ -5,6 +5,19 @@ import ( ) type addressbookHomeSet struct { - Name xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-home-set"` - Href string `xml:"href"` + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-home-set"` + Href string `xml:"href"` +} + +type addressbookDescription struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-description"` + Data string `xml:",chardata"` +} + +// https://tools.ietf.org/html/rfc6352#section-10.3 +type addressbookQuery struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-query"` + Prop *internal.Prop `xml:"DAV: prop,omitempty"` + // TODO: DAV:allprop | DAV:propname + // TODO: filter, limit? } diff --git a/elements.go b/elements.go index 5c554db..04bc9c7 100644 --- a/elements.go +++ b/elements.go @@ -5,6 +5,6 @@ import ( ) type currentUserPrincipal struct { - Name xml.Name `xml:"DAV: current-user-principal"` - Href string `xml:"href"` + XMLName xml.Name `xml:"DAV: current-user-principal"` + Href string `xml:"href"` } diff --git a/internal/client.go b/internal/client.go index 01cd1ee..0c03bd5 100644 --- a/internal/client.go +++ b/internal/client.go @@ -56,7 +56,7 @@ func (c *Client) NewXMLRequest(method string, href string, v interface{}) (*http func (c *Client) Do(req *http.Request) (*http.Response, error) { // TODO: remove this quirk - req.SetBasicAuth("simon", "") + req.SetBasicAuth("emersion", "") return c.http.Do(req) } diff --git a/internal/elements.go b/internal/elements.go index 9c7eb91..aa2ace1 100644 --- a/internal/elements.go +++ b/internal/elements.go @@ -42,7 +42,7 @@ type Multistatus struct { func (ms *Multistatus) Get(href string) (*Response, error) { for i := range ms.Responses { resp := &ms.Responses[i] - for _, h := range resp.Href { + for _, h := range resp.Hrefs { if h == href { return resp, nil } @@ -55,7 +55,7 @@ func (ms *Multistatus) Get(href string) (*Response, error) { // https://tools.ietf.org/html/rfc4918#section-14.24 type Response struct { XMLName xml.Name `xml:"DAV: response"` - Href []string `xml:"href"` + Hrefs []string `xml:"href"` Propstats []Propstat `xml:"propstat,omitempty"` ResponseDescription string `xml:"responsedescription,omitempty"` Status Status `xml:"status,omitempty"` @@ -63,6 +63,16 @@ type Response struct { Location *Location `xml:"location,omitempty"` } +func (resp *Response) Href() (string, error) { + if err := resp.Status.Err(); err != nil { + return "", err + } + if len(resp.Hrefs) != 1 { + return "", fmt.Errorf("webdav: malformed response: expected exactly one href element, got %v", len(resp.Hrefs)) + } + return resp.Hrefs[0], nil +} + func (resp *Response) DecodeProp(name xml.Name, v interface{}) error { if err := resp.Status.Err(); err != nil { return err @@ -71,13 +81,11 @@ func (resp *Response) DecodeProp(name xml.Name, 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 { - if name == start.Name { - if err := propstat.Status.Err(); err != nil { - return err - } - return raw.Decode(v) + if start, ok := raw.tok.(xml.StartElement); ok && name == start.Name { + if err := propstat.Status.Err(); err != nil { + return err } + return raw.Decode(v) } } } @@ -120,3 +128,20 @@ func NewPropPropfind(names ...xml.Name) *Propfind { } return &Propfind{Prop: &Prop{Raw: children}} } + +// https://tools.ietf.org/html/rfc4918#section-15.9 +type ResourceType struct { + XMLName xml.Name `xml:"DAV: resourcetype"` + Raw []RawXMLValue `xml:",any"` +} + +func (t *ResourceType) Is(name xml.Name) bool { + for _, raw := range t.Raw { + if start, ok := raw.tok.(xml.StartElement); ok && name == start.Name { + return true + } + } + return false +} + +var CollectionName = xml.Name{"DAV:", "collection"}