From 46dbba12fe37e439730e2dd0773f65514b1b7e74 Mon Sep 17 00:00:00 2001 From: Dan Berglund Date: Mon, 3 Jul 2023 10:47:34 +0200 Subject: [PATCH] caldav: return SupportedComponentSet in PROPFIND I started using this project to export tasks over CalDav, more specifically to Reminders on iOS/macOS. I quickly realized that even if you specify that `SupportedComponentSet` contains `VTODO`, that isn't reflected properly when doing the `PROPFIND`. This patch should fix that, while keeping the behaviour of defaulting to `VEVENT` for propfind. Also added some tests to make sure that I didn't break anything (Which I hope I didn't :sweat_smile:). --- caldav/server.go | 12 ++++-- caldav/server_test.go | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 caldav/server_test.go diff --git a/caldav/server.go b/caldav/server.go index e29fe2e..a3b9289 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -525,10 +525,16 @@ func (b *backend) propFindCalendar(ctx context.Context, propfind *internal.PropF }, nil }, supportedCalendarComponentSetName: func(*internal.RawXMLValue) (interface{}, error) { + components := []comp{} + if cal.SupportedComponentSet != nil { + for _, name := range cal.SupportedComponentSet { + components = append(components, comp{Name: name}) + } + } else { + components = append(components, comp{Name: ical.CompEvent}) + } return &supportedCalendarComponentSet{ - Comp: []comp{ - {Name: ical.CompEvent}, - }, + Comp: components, }, nil }, } diff --git a/caldav/server_test.go b/caldav/server_test.go new file mode 100644 index 0000000..0ee5fbd --- /dev/null +++ b/caldav/server_test.go @@ -0,0 +1,87 @@ +package caldav + +import ( + "context" + "io" + "io/ioutil" + "net/http/httptest" + "strings" + "testing" + + "github.com/emersion/go-ical" +) + +var propFindSupportedCalendarComponentRequest = ` + + + + + +` + +var testPropFindSupportedCalendarComponentCases = map[*Calendar][]string{ + &Calendar{Path: "/user/calendars/cal"}: []string{"VEVENT"}, + &Calendar{Path: "/user/calendars/cal", SupportedComponentSet: []string{"VTODO"}}: []string{"VTODO"}, + &Calendar{Path: "/user/calendars/cal", SupportedComponentSet: []string{"VEVENT", "VTODO"}}: []string{"VEVENT", "VTODO"}, +} + +func TestPropFindSupportedCalendarComponent(t *testing.T) { + for calendar, expected := range testPropFindSupportedCalendarComponentCases { + req := httptest.NewRequest("PROPFIND", calendar.Path, nil) + req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest)) + req.Header.Set("Content-Type", "application/xml") + w := httptest.NewRecorder() + handler := Handler{Backend: testBackend{calendar: calendar}} + handler.ServeHTTP(w, req) + + res := w.Result() + defer res.Body.Close() + data, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Error(err) + } + resp := string(data) + for _, comp := range expected { + // Would be nicer to do a proper XML-decoding here, but this is probably good enough for now. + if !strings.Contains(resp, comp) { + t.Errorf("Expected component: %v not found in response:\n%v", comp, resp) + } + } + } +} + +type testBackend struct { + calendar *Calendar +} + +func (t testBackend) Calendar(ctx context.Context) (*Calendar, error) { + return t.calendar, nil +} + +func (t testBackend) CalendarHomeSetPath(ctx context.Context) (string, error) { + return "", nil +} + +func (t testBackend) CurrentUserPrincipal(ctx context.Context) (string, error) { + return "", nil +} + +func (t testBackend) DeleteCalendarObject(ctx context.Context, path string) error { + return nil +} + +func (t testBackend) GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error) { + return nil, nil +} + +func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (string, error) { + return "", nil +} + +func (t testBackend) ListCalendarObjects(ctx context.Context, req *CalendarCompRequest) ([]CalendarObject, error) { + return nil, nil +} + +func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) { + return nil, nil +}