go-webdav/caldav/elements.go
Conrad Hoffmann 6401d9ed45 caldav: extend query filter types
The basic types related to queries and filtering are missing some
features specified in the RFC (as also noted in the TODO comments). This
adds several of the missing elements, working towards being able to
handle all RFC-compliant queries.

The work is not fully done, e.g. the collation for text-match is still
not handled, but it's getting pretty close.
2022-04-01 18:29:58 +02:00

227 lines
7.1 KiB
Go

package caldav
import (
"encoding/xml"
"fmt"
"time"
"github.com/emersion/go-webdav/internal"
)
const namespace = "urn:ietf:params:xml:ns:caldav"
var (
calendarHomeSetName = xml.Name{namespace, "calendar-home-set"}
calendarDescriptionName = xml.Name{namespace, "calendar-description"}
supportedCalendarDataName = xml.Name{namespace, "supported-calendar-data"}
supportedCalendarComponentSetName = xml.Name{namespace, "supported-calendar-component-set"}
maxResourceSizeName = xml.Name{namespace, "max-resource-size"}
calendarQueryName = xml.Name{namespace, "calendar-query"}
calendarMultigetName = xml.Name{namespace, "calendar-multiget"}
calendarName = xml.Name{namespace, "calendar"}
calendarDataName = xml.Name{namespace, "calendar-data"}
)
// https://tools.ietf.org/html/rfc4791#section-6.2.1
type calendarHomeSet struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-home-set"`
Href internal.Href `xml:"DAV: href"`
}
// https://tools.ietf.org/html/rfc4791#section-5.2.1
type calendarDescription struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-description"`
Description string `xml:",chardata"`
}
// https://tools.ietf.org/html/rfc4791#section-5.2.4
type supportedCalendarData struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav supported-calendar-data"`
Types []calendarDataType `xml:"calendar-data"`
}
// https://tools.ietf.org/html/rfc4791#section-5.2.3
type supportedCalendarComponentSet struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav supported-calendar-component-set"`
Comp []comp `xml:"comp"`
}
// https://tools.ietf.org/html/rfc4791#section-9.6
type calendarDataType struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
ContentType string `xml:"content-type,attr"`
Version string `xml:"version,attr"`
}
// https://tools.ietf.org/html/rfc4791#section-5.2.5
type maxResourceSize struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav max-resource-size"`
Size int64 `xml:",chardata"`
}
// https://tools.ietf.org/html/rfc4791#section-9.5
type calendarQuery struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-query"`
Prop *internal.Prop `xml:"DAV: prop,omitempty"`
AllProp *struct{} `xml:"DAV: allprop,omitempty"`
PropName *struct{} `xml:"DAV: propname,omitempty"`
Filter filter `xml:"filter"`
// TODO: timezone
}
// https://tools.ietf.org/html/rfc4791#section-9.10
type calendarMultiget struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-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"`
}
// https://tools.ietf.org/html/rfc4791#section-9.7
type filter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav filter"`
CompFilter compFilter `xml:"comp-filter"`
}
// https://tools.ietf.org/html/rfc4791#section-9.7.1
type compFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp-filter"`
Name string `xml:"name,attr"`
IsNotDefined *struct{} `xml:"is-not-defined,omitempty"`
TimeRange *timeRange `xml:"time-range,omitempty"`
PropFilters []propFilter `xml:"prop-filter,omitempty"`
CompFilters []compFilter `xml:"comp-filter,omitempty"`
}
// https://tools.ietf.org/html/rfc4791#section-9.7.2
type propFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop-filter"`
Name string `xml:"name,attr"`
IsNotDefined *struct{} `xml:"is-not-defined,omitempty"`
TimeRange *timeRange `xml:"time-range,omitempty"`
TextMatch *textMatch `xml:"text-match,omitempty"`
ParamFilter []paramFilter `xml:"param-filter,omitempty"`
}
// https://tools.ietf.org/html/rfc4791#section-9.7.3
type paramFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav param-filter"`
Name string `xml:"name,attr"`
IsNotDefined *struct{} `xml:"is-not-defined,omitempty"`
TextMatch *textMatch `xml:"text-match,omitempty"`
}
// https://tools.ietf.org/html/rfc4791#section-9.7.5
type textMatch struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav text-match"`
Text string `xml:",chardata"`
Collation string `xml:"collation,attr,omitempty"`
NegateCondition negateCondition `xml:"negate-condition,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("caldav: invalid negate-condition value: %q", s)
}
return nil
}
func (nc negateCondition) MarshalText() ([]byte, error) {
if nc {
return []byte("yes"), nil
}
return nil, nil
}
// https://tools.ietf.org/html/rfc4791#section-9.9
type timeRange struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav time-range"`
Start dateWithUTCTime `xml:"start,attr,omitempty"`
End dateWithUTCTime `xml:"end,attr,omitempty"`
}
const dateWithUTCTimeLayout = "20060102T150405Z"
// dateWithUTCTime is the "date with UTC time" format defined in RFC 5545 page
// 34.
type dateWithUTCTime time.Time
func (t *dateWithUTCTime) UnmarshalText(b []byte) error {
tt, err := time.Parse(dateWithUTCTimeLayout, string(b))
if err != nil {
return err
}
*t = dateWithUTCTime(tt)
return nil
}
func (t *dateWithUTCTime) MarshalText() ([]byte, error) {
s := time.Time(*t).Format(dateWithUTCTimeLayout)
return []byte(s), nil
}
// Request variant of https://tools.ietf.org/html/rfc4791#section-9.6
type calendarDataReq struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
Comp *comp `xml:"comp,omitempty"`
// TODO: expand, limit-recurrence-set, limit-freebusy-set
}
// https://tools.ietf.org/html/rfc4791#section-9.6.1
type comp struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp"`
Name string `xml:"name,attr"`
Allprop *struct{} `xml:"allprop,omitempty"`
Prop []prop `xml:"prop,omitempty"`
Allcomp *struct{} `xml:"allcomp,omitempty"`
Comp []comp `xml:"comp,omitempty"`
}
// https://tools.ietf.org/html/rfc4791#section-9.6.4
type prop struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop"`
Name string `xml:"name,attr"`
// TODO: novalue
}
// Response variant of https://tools.ietf.org/html/rfc4791#section-9.6
type calendarDataResp struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
Data []byte `xml:",chardata"`
}
type reportReq struct {
Query *calendarQuery
Multiget *calendarMultiget
// TODO: CALDAV:free-busy-query
}
func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v interface{}
switch start.Name {
case calendarQueryName:
r.Query = &calendarQuery{}
v = r.Query
case calendarMultigetName:
r.Multiget = &calendarMultiget{}
v = r.Multiget
default:
return fmt.Errorf("caldav: unsupported REPORT root %q %q", start.Name.Space, start.Name.Local)
}
return d.DecodeElement(v, &start)
}