diff --git a/caldav/caldav.go b/caldav/caldav.go index 3840c7d..62cc94d 100644 --- a/caldav/caldav.go +++ b/caldav/caldav.go @@ -27,19 +27,30 @@ type CalendarCompRequest struct { } type CompFilter struct { - Name string - Start, End time.Time - Props []PropFilter - Comps []CompFilter + 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 - TextMatch *TextMatch + Name string + IsNotDefined bool + Start, End time.Time + TextMatch *TextMatch + ParamFilter []ParamFilter } type TextMatch struct { - Text string + Text string + NegateCondition bool } type CalendarQuery struct { diff --git a/caldav/elements.go b/caldav/elements.go index e1f8c32..7b00fd5 100644 --- a/caldav/elements.go +++ b/caldav/elements.go @@ -21,7 +21,8 @@ var ( calendarQueryName = xml.Name{namespace, "calendar-query"} calendarMultigetName = xml.Name{namespace, "calendar-multiget"} - calendarName = xml.Name{namespace, "calendar"} + calendarName = xml.Name{namespace, "calendar"} + calendarDataName = xml.Name{namespace, "calendar-data"} ) // https://tools.ietf.org/html/rfc4791#section-6.2.1 @@ -98,19 +99,49 @@ type compFilter struct { // 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"` + 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"` - TimeRange *timeRange `xml:"time-range,omitempty"` TextMatch *textMatch `xml:"text-match,omitempty"` - // TODO: param-filter } // 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 + 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 diff --git a/caldav/server.go b/caldav/server.go index b0ea301..c72acde 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -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(¶mEl) + 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 }