diff --git a/carddav/carddav.go b/carddav/carddav.go index 410d250..4fe61a9 100644 --- a/carddav/carddav.go +++ b/carddav/carddav.go @@ -2,6 +2,8 @@ package carddav import ( "encoding/xml" + + "github.com/emersion/go-vcard" ) const namespace = "urn:ietf:params:xml:ns:carddav" @@ -14,5 +16,10 @@ type AddressBook struct { var addressBookName = xml.Name{namespace, "addressbook"} type AddressBookQuery struct { - AddressDataProps []string + Props []string +} + +type Address struct { + Href string + Card vcard.Card } diff --git a/carddav/client.go b/carddav/client.go index a0bdba4..cd9d157 100644 --- a/carddav/client.go +++ b/carddav/client.go @@ -1,9 +1,11 @@ package carddav import ( + "bytes" "encoding/xml" "net/http" + "github.com/emersion/go-vcard" "github.com/emersion/go-webdav" "github.com/emersion/go-webdav/internal" ) @@ -61,8 +63,7 @@ func (c *Client) FindAddressBooks(addressBookHomeSet string) ([]AddressBook, err } l := make([]AddressBook, 0, len(ms.Responses)) - for i := range ms.Responses { - resp := &ms.Responses[i] + for _, resp := range ms.Responses { href, err := resp.Href() if err != nil { return nil, err @@ -89,3 +90,56 @@ func (c *Client) FindAddressBooks(addressBookHomeSet string) ([]AddressBook, err return l, nil } + +func (c *Client) QueryAddressBook(addressBook string, query *AddressBookQuery) ([]Address, error) { + // TODO: add a better way to format the request + addrProps := make([]internal.RawXMLValue, 0, len(query.Props)) + for _, name := range query.Props { + addrProps = append(addrProps, *newProp(name, false)) + } + + addrDataName := xml.Name{namespace, "address-data"} + addrDataReq := internal.NewRawXMLElement(addrDataName, nil, addrProps) + + addressbookQuery := addressbookQuery{ + Prop: &internal.Prop{Raw: []internal.RawXMLValue{*addrDataReq}}, + } + + req, err := c.ic.NewXMLRequest("REPORT", addressBook, &addressbookQuery) + if err != nil { + return nil, err + } + + req.Header.Add("Depth", "1") + + ms, err := c.ic.DoMultiStatus(req) + if err != nil { + return nil, err + } + + addrs := make([]Address, 0, len(ms.Responses)) + for _, resp := range ms.Responses { + href, err := resp.Href() + if err != nil { + return nil, err + } + + var addrData addressDataResp + if err := resp.DecodeProp(addrDataName, &addrData); err != nil { + return nil, err + } + + r := bytes.NewReader(addrData.Data) + card, err := vcard.NewDecoder(r).Decode() + if err != nil { + return nil, err + } + + addrs = append(addrs, Address{ + Href: href, + Card: card, + }) + } + + return addrs, nil +} diff --git a/carddav/elements.go b/carddav/elements.go index 1a332ac..75f22d6 100644 --- a/carddav/elements.go +++ b/carddav/elements.go @@ -2,6 +2,8 @@ package carddav import ( "encoding/xml" + + "github.com/emersion/go-webdav/internal" ) type addressbookHomeSet struct { @@ -21,3 +23,33 @@ type addressbookQuery struct { // TODO: DAV:allprop | DAV:propname // TODO: filter, limit? } + +func newProp(name string, noValue bool) *internal.RawXMLValue { + attrs := []xml.Attr{{Name: xml.Name{namespace, "name"}, Value: name}} + if noValue { + attrs = append(attrs, xml.Attr{Name: xml.Name{namespace, "novalue"}, Value: "yes"}) + } + + xmlName := xml.Name{namespace, "prop"} + return internal.NewRawXMLElement(xmlName, attrs, nil) +} + +// https://tools.ietf.org/html/rfc6352#section-10.4 +type addressDataReq struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"` + Props []prop `xml:"prop"` + // TODO: allprop +} + +// https://tools.ietf.org/html/rfc6352#section-10.4.2 +type prop struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav prop"` + Name string `xml:"name,attr"` + // TODO: novalue +} + +// https://tools.ietf.org/html/rfc6352#section-10.4 +type addressDataResp struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"` + Data []byte `xml:",chardata"` +} diff --git a/go.mod b/go.mod index 5aee758..13d36df 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/emersion/go-webdav go 1.13 + +require github.com/emersion/go-vcard v0.0.0-20191221110513-5f81fa0d3cc7