diff --git a/caldav/caldav.go b/caldav/caldav.go index 014e257..22d3fdb 100644 --- a/caldav/caldav.go +++ b/caldav/caldav.go @@ -69,6 +69,17 @@ type Calendar struct { SupportedComponentSet []string } +type TimeRange struct { + Start, End time.Time +} + +type CalendarDataRequest struct { + Comp CalendarCompRequest + Expand *TimeRange + LimitRecurrence *TimeRange + LimitFreeBusy *TimeRange +} + type CalendarCompRequest struct { Name string @@ -107,13 +118,13 @@ type TextMatch struct { } type CalendarQuery struct { - CompRequest CalendarCompRequest + DataRequest CalendarDataRequest CompFilter CompFilter } type CalendarMultiGet struct { Paths []string - CompRequest CalendarCompRequest + DataRequest CalendarDataRequest } type CalendarObject struct { diff --git a/caldav/client.go b/caldav/client.go index bb76813..1f5bc87 100644 --- a/caldav/client.go +++ b/caldav/client.go @@ -141,17 +141,30 @@ func encodeCalendarCompReq(c *CalendarCompRequest) (*comp, error) { return &encoded, nil } -func encodeCalendarReq(c *CalendarCompRequest) (*internal.Prop, error) { - compReq, err := encodeCalendarCompReq(c) +func encodeCalendarDataReq(d *CalendarDataRequest) (*calendarDataReq, error) { + encodedComp, err := encodeCalendarCompReq(&d.Comp) if err != nil { return nil, err } - calDataReq := calendarDataReq{Comp: compReq} + encoded := calendarDataReq{ + Comp: encodedComp, + } + + // TODO expand, limit-recurrence, free-busy + + return &encoded, nil +} + +func encodeCalendarReq(c *CalendarDataRequest) (*internal.Prop, error) { + calDataReq, err := encodeCalendarDataReq(c) + if err != nil { + return nil, err + } getLastModReq := internal.NewRawXMLElement(internal.GetLastModifiedName, nil, nil) getETagReq := internal.NewRawXMLElement(internal.GetETagName, nil, nil) - return internal.EncodeProp(&calDataReq, getLastModReq, getETagReq) + return internal.EncodeProp(calDataReq, getLastModReq, getETagReq) } func encodeCompFilter(filter *CompFilter) *compFilter { @@ -215,7 +228,7 @@ func decodeCalendarObjectList(ms *internal.MultiStatus) ([]CalendarObject, error } func (c *Client) QueryCalendar(calendar string, query *CalendarQuery) ([]CalendarObject, error) { - propReq, err := encodeCalendarReq(&query.CompRequest) + propReq, err := encodeCalendarReq(&query.DataRequest) if err != nil { return nil, err } @@ -237,7 +250,7 @@ func (c *Client) QueryCalendar(calendar string, query *CalendarQuery) ([]Calenda } func (c *Client) MultiGetCalendar(path string, multiGet *CalendarMultiGet) ([]CalendarObject, error) { - propReq, err := encodeCalendarReq(&multiGet.CompRequest) + propReq, err := encodeCalendarReq(&multiGet.DataRequest) if err != nil { return nil, err } diff --git a/caldav/elements.go b/caldav/elements.go index 255ab7a..511fdbe 100644 --- a/caldav/elements.go +++ b/caldav/elements.go @@ -177,9 +177,11 @@ func (t *dateWithUTCTime) MarshalText() ([]byte, error) { // 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 + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"` + Comp *comp `xml:"comp,omitempty"` + Expand *expand `xml:"expand,omitempty` + LimitRecurrence *limitRecurrenceSet `xml:"limit-recurrence-set,omitempty` + LimitFreeBusy *limitFreeBusySet `xml:"limit-freebusy-set,omitempty` } // https://tools.ietf.org/html/rfc4791#section-9.6.1 @@ -201,6 +203,27 @@ type prop struct { // TODO: novalue } +// https://tools.ietf.org/html/rfc4791#section-9.6.5 +type expand struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav expand"` + Start dateWithUTCTime `xml:"start,attr,omitempty"` + End dateWithUTCTime `xml:"end,attr,omitempty"` +} + +// https://tools.ietf.org/html/rfc4791#section-9.6.6 +type limitRecurrenceSet struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-recurrence-set"` + Start dateWithUTCTime `xml:"start,attr,omitempty"` + End dateWithUTCTime `xml:"end,attr,omitempty"` +} + +// https://tools.ietf.org/html/rfc4791#section-9.6.7 +type limitFreeBusySet struct { + XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-freebusy-set"` + Start dateWithUTCTime `xml:"start,attr,omitempty"` + End dateWithUTCTime `xml:"end,attr,omitempty"` +} + // 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"` diff --git a/caldav/server.go b/caldav/server.go index 12422b1..969baa4 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -188,25 +188,69 @@ func decodeComp(comp *comp) (*CalendarCompRequest, error) { return req, nil } -func decodeCalendarDataReq(calendarData *calendarDataReq) (*CalendarCompRequest, error) { - if calendarData.Comp == nil { - return &CalendarCompRequest{ - AllProps: true, - AllComps: true, - }, nil - } - return decodeComp(calendarData.Comp) +func decodeCalendarDataReq(calendarData *calendarDataReq) (*CalendarDataRequest, error) { + if calendarData == nil { + return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: unexpected missing calendar-data in request") + } + if calendarData.Expand != nil && calendarData.LimitRecurrence != nil { + return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: only one of expand or limit-recurrence-set can be specified in calendar-data") + } + + var comp = &CalendarCompRequest{} + if calendarData.Comp != nil { + var err error + comp, err = decodeComp(calendarData.Comp) + if err != nil { + return nil, err + } + } + + result := &CalendarDataRequest{ + Comp: *comp, + } + + if calendarData.Expand != nil { + result.Expand = &TimeRange{ + Start: time.Time(calendarData.Expand.Start), + End: time.Time(calendarData.Expand.End), + } + } + if calendarData.LimitRecurrence != nil { + result.Expand = &TimeRange{ + Start: time.Time(calendarData.LimitRecurrence.Start), + End: time.Time(calendarData.LimitRecurrence.End), + } + } + if calendarData.LimitFreeBusy != nil { + result.Expand = &TimeRange{ + Start: time.Time(calendarData.LimitFreeBusy.Start), + End: time.Time(calendarData.LimitFreeBusy.End), + } + } + + return result, nil } func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *calendarQuery) error { var q CalendarQuery - // TODO: calendar-data in query.Prop cf, err := decodeCompFilter(&query.Filter.CompFilter) if err != nil { return err } q.CompFilter = *cf + if query.Prop != nil { + var calendarData calendarDataReq + if err := query.Prop.Decode(&calendarData); err != nil && !internal.IsNotFound(err) { + return err + } + decoded, err := decodeCalendarDataReq(&calendarData) + if err != nil { + return err + } + q.DataRequest = *decoded + } + cos, err := h.Backend.QueryCalendarObjects(r.Context(), &q) if err != nil { return err @@ -233,7 +277,7 @@ func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *cal } func (h *Handler) handleMultiget(ctx context.Context, w http.ResponseWriter, multiget *calendarMultiget) error { - var dataReq CalendarCompRequest + var dataReq CalendarDataRequest if multiget.Prop != nil { var calendarData calendarDataReq if err := multiget.Prop.Decode(&calendarData); err != nil && !internal.IsNotFound(err) { @@ -248,7 +292,7 @@ func (h *Handler) handleMultiget(ctx context.Context, w http.ResponseWriter, mul var resps []internal.Response for _, href := range multiget.Hrefs { - co, err := h.Backend.GetCalendarObject(ctx, href.Path, &dataReq) + co, err := h.Backend.GetCalendarObject(ctx, href.Path, &dataReq.Comp) if err != nil { resp := internal.NewErrorResponse(href.Path, err) resps = append(resps, *resp)