mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 16:24:14 +00:00
6887b6b812
Currently, the user principal path and the home set path are both hardcoded to "/", for both CalDAV and CardDAV. This poses a challenge if one wishes to run a CardDAV and CalDAV server in the same server. This commit introduces the concept of a UserPrincipalBackend. This backend must provide the path of the current user's principal URL from the given request context. The CalDAV and CardDAV backends are extended to also function as UserPrincipalBackend. In addition, they are required to supply the path of the respective home set (`calendar-home-set` and `addressbook-home-set`). The CardDAV and CalDAV servers act accordingly. The individual servers will continue to work as before (including the option of keeping everything at "/"). If one wishes to run CardDAV and CalDAV in parallel, the new `webdav.ServeUserPrincipal()` can be used as a convenience function to serve a common user principal URL for both servers. The input for this function can be easily computed by the application by getting the home set paths from the backends and using `caldav.NewCalendarHomeSet()` and `carddav.NewAddressbookHomeSet()` to create the home sets. Note that the storage backend will have to know about these paths as well. For any non-trivial use case, a storage backend should probably have access to the same UserPrincipalBackend. That is, however, an implementation detail and doesn't have to be reflected in the interfaces.
214 lines
6.4 KiB
Go
214 lines
6.4 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)
|
|
}
|