Skip to content

Commit

Permalink
caldav: support more features for recurring events
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bitfehler committed Sep 13, 2022
1 parent dc63df9 commit a5ea3ab
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 22 deletions.
15 changes: 13 additions & 2 deletions caldav/caldav.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 19 additions & 6 deletions caldav/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
29 changes: 26 additions & 3 deletions caldav/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"`
Expand Down
66 changes: 55 additions & 11 deletions caldav/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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)
Expand Down

0 comments on commit a5ea3ab

Please sign in to comment.