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.
This commit is contained in:
Conrad Hoffmann 2022-03-31 22:15:04 +02:00 committed by Simon Ser
parent 7dafedd290
commit 6401d9ed45
3 changed files with 95 additions and 18 deletions

View File

@ -28,18 +28,29 @@ type CalendarCompRequest struct {
type CompFilter struct {
Name string
IsNotDefined bool
Start, End time.Time
Props []PropFilter
Comps []CompFilter
}
type ParamFilter struct {
Name string
IsNotDefined bool
TextMatch *TextMatch
}
type PropFilter struct {
Name string
IsNotDefined bool
Start, End time.Time
TextMatch *TextMatch
ParamFilter []ParamFilter
}
type TextMatch struct {
Text string
NegateCondition bool
}
type CalendarQuery struct {

View File

@ -22,6 +22,7 @@ var (
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
@ -103,14 +104,44 @@ type propFilter struct {
IsNotDefined *struct{} `xml:"is-not-defined,omitempty"`
TimeRange *timeRange `xml:"time-range,omitempty"`
TextMatch *textMatch `xml:"text-match,omitempty"`
// TODO: param-filter
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"`
// TODO: collation, negate-condition
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

View File

@ -69,17 +69,53 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) error {
return internal.HTTPErrorf(http.StatusBadRequest, "caldav: expected calendar-query or calendar-multiget element in REPORT request")
}
func decodePropFilter(el *propFilter) (*PropFilter, error) {
pf := &PropFilter{Name: el.Name}
func decodeParamFilter(el *paramFilter) (*ParamFilter, error) {
pf := &ParamFilter{Name: el.Name}
if el.IsNotDefined != nil {
if el.TextMatch != nil {
return nil, fmt.Errorf("caldav: failed to parse param-filter: if is-not-defined is provided, text-match can't be provided")
}
pf.IsNotDefined = true
}
if el.TextMatch != nil {
pf.TextMatch = &TextMatch{Text: el.TextMatch.Text}
}
// TODO: IsNotDefined, TimeRange
return pf, nil
}
func decodePropFilter(el *propFilter) (*PropFilter, error) {
pf := &PropFilter{Name: el.Name}
if el.IsNotDefined != nil {
if el.TextMatch != nil || el.TimeRange != nil || len(el.ParamFilter) > 0 {
return nil, fmt.Errorf("caldav: failed to parse prop-filter: if is-not-defined is provided, text-match, time-range, or param-filter can't be provided")
}
pf.IsNotDefined = true
}
if el.TextMatch != nil {
pf.TextMatch = &TextMatch{Text: el.TextMatch.Text}
}
if el.TimeRange != nil {
pf.Start = time.Time(el.TimeRange.Start)
pf.End = time.Time(el.TimeRange.End)
}
for _, paramEl := range el.ParamFilter {
paramFi, err := decodeParamFilter(&paramEl)
if err != nil {
return nil, err
}
pf.ParamFilter = append(pf.ParamFilter, *paramFi)
}
return pf, nil
}
func decodeCompFilter(el *compFilter) (*CompFilter, error) {
cf := &CompFilter{Name: el.Name}
if el.IsNotDefined != nil {
if el.TimeRange != nil || len(el.PropFilters) > 0 || len(el.CompFilters) > 0 {
return nil, fmt.Errorf("caldav: failed to parse comp-filter: if is-not-defined is provided, time-range, prop-filter, or comp-filter can't be provided")
}
cf.IsNotDefined = true
}
if el.TimeRange != nil {
cf.Start = time.Time(el.TimeRange.Start)
cf.End = time.Time(el.TimeRange.End)
@ -98,7 +134,6 @@ func decodeCompFilter(el *compFilter) (*CompFilter, error) {
}
cf.Comps = append(cf.Comps, *child)
}
// TODO: IsNotDefined
return cf, nil
}