go-webdav/carddav/elements.go
Conrad Hoffmann 71bd967b43 carddav: support address book creation/deletion
Now that the handling for multiple address books is in place, this
commit adds initial support for creation and deletion of address books.

These operations obviously require support from the backend, so the
interface gains two new methods. All properties of the address book
passed to `CreateAddressBook()` may be unset (e.g. when a client sends a
MKCOL request without a body), except for the path, which is always set.
It is up to the backend to put any desired default values in place.
2024-02-07 17:20:48 +01:00

222 lines
6.8 KiB
Go

package carddav
import (
"encoding/xml"
"fmt"
"github.com/emersion/go-webdav/internal"
)
const namespace = "urn:ietf:params:xml:ns:carddav"
var (
addressBookHomeSetName = xml.Name{namespace, "addressbook-home-set"}
addressBookName = xml.Name{namespace, "addressbook"}
addressBookDescriptionName = xml.Name{namespace, "addressbook-description"}
supportedAddressDataName = xml.Name{namespace, "supported-address-data"}
maxResourceSizeName = xml.Name{namespace, "max-resource-size"}
addressBookQueryName = xml.Name{namespace, "addressbook-query"}
addressBookMultigetName = xml.Name{namespace, "addressbook-multiget"}
addressDataName = xml.Name{namespace, "address-data"}
)
// https://tools.ietf.org/html/rfc6352#section-6.2.3
type addressbookHomeSet struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-home-set"`
Href internal.Href `xml:"DAV: href"`
}
func (a *addressbookHomeSet) GetXMLName() xml.Name {
return addressBookHomeSetName
}
type addressbookDescription struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-description"`
Description string `xml:",chardata"`
}
// https://tools.ietf.org/html/rfc6352#section-6.2.2
type supportedAddressData struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav supported-address-data"`
Types []addressDataType `xml:"address-data-type"`
}
type addressDataType struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data-type"`
ContentType string `xml:"content-type,attr"`
Version string `xml:"version,attr"`
}
// https://tools.ietf.org/html/rfc6352#section-6.2.3
type maxResourceSize struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav max-resource-size"`
Size int64 `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"`
AllProp *struct{} `xml:"DAV: allprop,omitempty"`
PropName *struct{} `xml:"DAV: propname,omitempty"`
Filter filter `xml:"filter"`
Limit *limit `xml:"limit,omitempty"`
}
// https://tools.ietf.org/html/rfc6352#section-10.5
type filter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav filter"`
Test filterTest `xml:"test,attr,omitempty"`
Props []propFilter `xml:"prop-filter"`
}
type filterTest string
func (ft *filterTest) UnmarshalText(b []byte) error {
switch FilterTest(b) {
case FilterAnyOf, FilterAllOf:
*ft = filterTest(b)
return nil
default:
return fmt.Errorf("carddav: invalid filter test value: %q", string(b))
}
}
// https://tools.ietf.org/html/rfc6352#section-10.5.1
type propFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav prop-filter"`
Name string `xml:"name,attr"`
Test filterTest `xml:"test,attr,omitempty"`
IsNotDefined *struct{} `xml:"is-not-defined,omitempty"`
TextMatches []textMatch `xml:"text-match,omitempty"`
Params []paramFilter `xml:"param-filter,omitempty"`
}
// https://tools.ietf.org/html/rfc6352#section-10.5.4
type textMatch struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav text-match"`
Text string `xml:",chardata"`
Collation string `xml:"collation,attr,omitempty"`
NegateCondition negateCondition `xml:"negate-condition,attr,omitempty"`
MatchType matchType `xml:"match-type,attr,omitempty"`
}
type negateCondition bool
func (nc *negateCondition) UnmarshalText(b []byte) error {
switch s := string(b); s {
case "yes":
*nc = true
case "no":
*nc = false
default:
return fmt.Errorf("carddav: invalid negate-condition value: %q", s)
}
return nil
}
func (nc negateCondition) MarshalText() ([]byte, error) {
if nc {
return []byte("yes"), nil
}
return nil, nil
}
type matchType MatchType
func (mt *matchType) UnmarshalText(b []byte) error {
switch MatchType(b) {
case MatchEquals, MatchContains, MatchStartsWith, MatchEndsWith:
*mt = matchType(b)
return nil
default:
return fmt.Errorf("carddav: invalid match type value: %q", string(b))
}
}
// https://tools.ietf.org/html/rfc6352#section-10.5.2
type paramFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav param-filter"`
Name string `xml:"name,attr"`
IsNotDefined *struct{} `xml:"is-not-defined"`
TextMatch *textMatch `xml:"text-match"`
}
// https://tools.ietf.org/html/rfc6352#section-10.6
type limit struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav limit"`
NResults uint `xml:"nresults"`
}
// https://tools.ietf.org/html/rfc6352#section-8.7
type addressbookMultiget struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav addressbook-multiget"`
Hrefs []internal.Href `xml:"DAV: href"`
Prop *internal.Prop `xml:"DAV: prop,omitempty"`
AllProp *struct{} `xml:"DAV: allprop,omitempty"`
PropName *struct{} `xml:"DAV: propname,omitempty"`
}
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"`
Allprop *struct{} `xml:"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"`
}
type reportReq struct {
Query *addressbookQuery
Multiget *addressbookMultiget
}
func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v interface{}
switch start.Name {
case addressBookQueryName:
r.Query = &addressbookQuery{}
v = r.Query
case addressBookMultigetName:
r.Multiget = &addressbookMultiget{}
v = r.Multiget
default:
return fmt.Errorf("carddav: unsupported REPORT root %q %q", start.Name.Space, start.Name.Local)
}
return d.DecodeElement(v, &start)
}
type mkcolReq struct {
XMLName xml.Name `xml:"DAV: mkcol"`
ResourceType internal.ResourceType `xml:"set>prop>resourcetype"`
DisplayName string `xml:"set>prop>displayname"`
Description addressbookDescription `xml:"set>prop>addressbook-description"`
// TODO this could theoretically contain all addressbook properties?
}