Skip to content

Commit ac2e356

Browse files
committed
caldav: support more features for recurring events
The protocol allows clients to ask the server to e.g. expand recurring events to a set of simple events in a given time-frame, so that the client does not have to calculate the recurrences. This commit adds the foundation for support for expand [1], limit-recurrence-set [2], and limit-freebusy-set [3] functionality. However, the actual transformation of events returned to clients will have to be implemented in the backend, which now has the required information to do so. [1]: https://datatracker.ietf.org/doc/html/rfc4791#section-9.6.5 [2]: https://datatracker.ietf.org/doc/html/rfc4791#section-9.6.6 [3]: https://datatracker.ietf.org/doc/html/rfc4791#section-9.6.7
1 parent dc63df9 commit ac2e356

File tree

4 files changed

+98
-20
lines changed

4 files changed

+98
-20
lines changed

caldav/caldav.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ type Calendar struct {
6969
SupportedComponentSet []string
7070
}
7171

72+
type TimeRange struct {
73+
Start, End time.Time
74+
}
75+
76+
type CalendarDataRequest struct {
77+
Comp CalendarCompRequest
78+
Expand *TimeRange
79+
LimitRecurrence *TimeRange
80+
LimitFreeBusy *TimeRange
81+
}
82+
7283
type CalendarCompRequest struct {
7384
Name string
7485

@@ -107,13 +118,13 @@ type TextMatch struct {
107118
}
108119

109120
type CalendarQuery struct {
110-
CompRequest CalendarCompRequest
121+
DataRequest CalendarDataRequest
111122
CompFilter CompFilter
112123
}
113124

114125
type CalendarMultiGet struct {
115126
Paths []string
116-
CompRequest CalendarCompRequest
127+
DataRequest CalendarDataRequest
117128
}
118129

119130
type CalendarObject struct {

caldav/client.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ func encodeCalendarCompReq(c *CalendarCompRequest) (*comp, error) {
141141
return &encoded, nil
142142
}
143143

144-
func encodeCalendarReq(c *CalendarCompRequest) (*internal.Prop, error) {
145-
compReq, err := encodeCalendarCompReq(c)
144+
func encodeCalendarReq(c *CalendarDataRequest) (*internal.Prop, error) {
145+
compReq, err := encodeCalendarCompReq(&c.Comp)
146146
if err != nil {
147147
return nil, err
148148
}
@@ -215,7 +215,7 @@ func decodeCalendarObjectList(ms *internal.MultiStatus) ([]CalendarObject, error
215215
}
216216

217217
func (c *Client) QueryCalendar(calendar string, query *CalendarQuery) ([]CalendarObject, error) {
218-
propReq, err := encodeCalendarReq(&query.CompRequest)
218+
propReq, err := encodeCalendarReq(&query.DataRequest)
219219
if err != nil {
220220
return nil, err
221221
}
@@ -237,7 +237,7 @@ func (c *Client) QueryCalendar(calendar string, query *CalendarQuery) ([]Calenda
237237
}
238238

239239
func (c *Client) MultiGetCalendar(path string, multiGet *CalendarMultiGet) ([]CalendarObject, error) {
240-
propReq, err := encodeCalendarReq(&multiGet.CompRequest)
240+
propReq, err := encodeCalendarReq(&multiGet.DataRequest)
241241
if err != nil {
242242
return nil, err
243243
}

caldav/elements.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,11 @@ func (t *dateWithUTCTime) MarshalText() ([]byte, error) {
177177

178178
// Request variant of https://tools.ietf.org/html/rfc4791#section-9.6
179179
type calendarDataReq struct {
180-
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
181-
Comp *comp `xml:"comp,omitempty"`
182-
// TODO: expand, limit-recurrence-set, limit-freebusy-set
180+
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
181+
Comp *comp `xml:"comp,omitempty"`
182+
Expand *expand `xml:"expand,omitempty`
183+
LimitRecurrence *limitRecurrenceSet `xml:"limit-recurrence-set,omitempty`
184+
LimitFreeBusy *limitFreeBusySet `xml:"limit-freebusy-set,omitempty`
183185
}
184186

185187
// https://tools.ietf.org/html/rfc4791#section-9.6.1
@@ -201,6 +203,27 @@ type prop struct {
201203
// TODO: novalue
202204
}
203205

206+
// https://tools.ietf.org/html/rfc4791#section-9.6.5
207+
type expand struct {
208+
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav expand"`
209+
Start dateWithUTCTime `xml:"start,attr,omitempty"`
210+
End dateWithUTCTime `xml:"end,attr,omitempty"`
211+
}
212+
213+
// https://tools.ietf.org/html/rfc4791#section-9.6.6
214+
type limitRecurrenceSet struct {
215+
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-recurrence-set"`
216+
Start dateWithUTCTime `xml:"start,attr,omitempty"`
217+
End dateWithUTCTime `xml:"end,attr,omitempty"`
218+
}
219+
220+
// https://tools.ietf.org/html/rfc4791#section-9.6.7
221+
type limitFreeBusySet struct {
222+
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-freebusy-set"`
223+
Start dateWithUTCTime `xml:"start,attr,omitempty"`
224+
End dateWithUTCTime `xml:"end,attr,omitempty"`
225+
}
226+
204227
// Response variant of https://tools.ietf.org/html/rfc4791#section-9.6
205228
type calendarDataResp struct {
206229
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`

caldav/server.go

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -188,25 +188,69 @@ func decodeComp(comp *comp) (*CalendarCompRequest, error) {
188188
return req, nil
189189
}
190190

191-
func decodeCalendarDataReq(calendarData *calendarDataReq) (*CalendarCompRequest, error) {
192-
if calendarData.Comp == nil {
193-
return &CalendarCompRequest{
194-
AllProps: true,
195-
AllComps: true,
196-
}, nil
197-
}
198-
return decodeComp(calendarData.Comp)
191+
func decodeCalendarDataReq(calendarData *calendarDataReq) (*CalendarDataRequest, error) {
192+
if calendarData == nil {
193+
return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: unexpected missing calendar-data in request")
194+
}
195+
if calendarData.Expand != nil && calendarData.LimitRecurrence != nil {
196+
return nil, internal.HTTPErrorf(http.StatusBadRequest, "caldav: only one of expand or limit-recurrence-set can be specified in calendar-data")
197+
}
198+
199+
var comp = &CalendarCompRequest{}
200+
if calendarData.Comp != nil {
201+
var err error
202+
comp, err = decodeComp(calendarData.Comp)
203+
if err != nil {
204+
return nil, err
205+
}
206+
}
207+
208+
result := &CalendarDataRequest{
209+
Comp: *comp,
210+
}
211+
212+
if calendarData.Expand != nil {
213+
result.Expand = &TimeRange{
214+
Start: time.Time(calendarData.Expand.Start),
215+
End: time.Time(calendarData.Expand.End),
216+
}
217+
}
218+
if calendarData.LimitRecurrence != nil {
219+
result.Expand = &TimeRange{
220+
Start: time.Time(calendarData.LimitRecurrence.Start),
221+
End: time.Time(calendarData.LimitRecurrence.End),
222+
}
223+
}
224+
if calendarData.LimitFreeBusy != nil {
225+
result.Expand = &TimeRange{
226+
Start: time.Time(calendarData.LimitFreeBusy.Start),
227+
End: time.Time(calendarData.LimitFreeBusy.End),
228+
}
229+
}
230+
231+
return result, nil
199232
}
200233

201234
func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *calendarQuery) error {
202235
var q CalendarQuery
203-
// TODO: calendar-data in query.Prop
204236
cf, err := decodeCompFilter(&query.Filter.CompFilter)
205237
if err != nil {
206238
return err
207239
}
208240
q.CompFilter = *cf
209241

242+
if query.Prop != nil {
243+
var calendarData calendarDataReq
244+
if err := query.Prop.Decode(&calendarData); err != nil && !internal.IsNotFound(err) {
245+
return err
246+
}
247+
decoded, err := decodeCalendarDataReq(&calendarData)
248+
if err != nil {
249+
return err
250+
}
251+
q.DataRequest = *decoded
252+
}
253+
210254
cos, err := h.Backend.QueryCalendarObjects(r.Context(), &q)
211255
if err != nil {
212256
return err
@@ -233,7 +277,7 @@ func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *cal
233277
}
234278

235279
func (h *Handler) handleMultiget(ctx context.Context, w http.ResponseWriter, multiget *calendarMultiget) error {
236-
var dataReq CalendarCompRequest
280+
var dataReq CalendarDataRequest
237281
if multiget.Prop != nil {
238282
var calendarData calendarDataReq
239283
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
248292

249293
var resps []internal.Response
250294
for _, href := range multiget.Hrefs {
251-
co, err := h.Backend.GetCalendarObject(ctx, href.Path, &dataReq)
295+
co, err := h.Backend.GetCalendarObject(ctx, href.Path, &dataReq.Comp)
252296
if err != nil {
253297
resp := internal.NewErrorResponse(href.Path, err)
254298
resps = append(resps, *resp)

0 commit comments

Comments
 (0)