diff --git a/carddav/client.go b/carddav/client.go index b939760..2c6b00f 100644 --- a/carddav/client.go +++ b/carddav/client.go @@ -70,6 +70,18 @@ func (c *Client) SetBasicAuth(username, password string) { c.ic.SetBasicAuth(username, password) } +func (c *Client) HasSupport() error { + classes, _, err := c.ic.Options("/") + if err != nil { + return err + } + + if !classes["addressbook"] { + return fmt.Errorf("carddav: server doesn't support the DAV addressbook class") + } + return nil +} + func (c *Client) FindAddressBookHomeSet(principal string) (string, error) { propfind := internal.NewPropNamePropfind(addressBookHomeSetName) resp, err := c.ic.PropfindFlat(principal, propfind) diff --git a/internal/client.go b/internal/client.go index 17f84f0..78da9a2 100644 --- a/internal/client.go +++ b/internal/client.go @@ -8,6 +8,8 @@ import ( "net/http" "net/url" "path" + "unicode" + "strings" ) type Client struct { @@ -119,3 +121,42 @@ func (c *Client) PropfindFlat(path string, propfind *Propfind) (*Response, error return ms.Get(path) } + +func parseCommaSeparatedSet(values []string, upper bool) map[string]bool { + m := make(map[string]bool) + for _, v := range values { + fields := strings.FieldsFunc(v, func(r rune) bool { + return unicode.IsSpace(r) || r == ',' + }) + for _, f := range fields { + if upper { + f = strings.ToUpper(f) + } else { + f = strings.ToLower(f) + } + m[f] = true + } + } + return m +} + +func (c *Client) Options(path string) (classes map[string]bool, methods map[string]bool, err error) { + req, err := c.NewRequest(http.MethodOptions, path, nil) + if err != nil { + return nil, nil, err + } + + resp, err := c.Do(req) + if err != nil { + return nil, nil, err + } + resp.Body.Close() + + classes = parseCommaSeparatedSet(resp.Header["Dav"], false) + if !classes["1"] { + return nil, nil, fmt.Errorf("webdav: server doesn't support DAV class 1") + } + + methods = parseCommaSeparatedSet(resp.Header["Allow"], true) + return classes, methods, nil +}