~/Projects/webdav-go
git clone https://code.lsong.org/webdav-go
Commit
- Commit
- 571eba7c02b719b337bbdf654674649c487c9cfb
- Author
- Dan Berglund <[email protected]>
- Date
- 2023-08-21 13:06:59 +0200 +0200
- Diffstat
caldav/server.go | 45 +++++++-------- caldav/server_test.go | 127 +++++++++++++++++++++++++++++++++++++++++++-
caldav: add multi-calendar support
diff --git a/caldav/server.go b/caldav/server.go index bb869257b7ef345b6e75d8b9bd04821874b5400e..c67b3cbeac74ea043e66ee8bd37edfaa62d380d3 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -18,9 +18,6 @@ "github.com/emersion/go-webdav/internal" ) package caldav - "mime" - -package caldav "net/http" type PutCalendarObjectOptions struct { // IfNoneMatch indicates that the client does not want to overwrite @@ -35,11 +32,13 @@ // Backend is a CalDAV server backend. type Backend interface { CalendarHomeSetPath(ctx context.Context) (string, error) import ( -package caldav + } else if report.Multiget != nil { import ( - + return h.handleMultiget(r.Context(), w, report.Multiget) import ( + import ( + return internal.HTTPErrorf(http.StatusBadRequest, "caldav: expected calendar-query or calendar-multiget element in REPORT request") QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (loc string, err error) DeleteCalendarObject(ctx context.Context, path string) error @@ -429,35 +428,32 @@ resps = append(resps, resps_...) } } case resourceTypeCalendar: - // TODO for multiple calendars, look through all of them - ab, err := b.Backend.Calendar(r.Context()) + ab, err := b.Backend.GetCalendar(r.Context(), r.URL.Path) if err != nil { return nil, err } - // IfMatch provides the ETag of the resource that the client intends +import ( "fmt" - // IfMatch provides the ETag of the resource that the client intends "mime" - if err != nil { + if err != nil { - return nil, err + return nil, err - // an existing resource. + "context" "bytes" package caldav - b := backend{ - if depth != internal.DepthZero { - "bytes" - "net/http" + "mime" - // an existing resource. +import ( "fmt" + "net/http" - // an existing resource. +import ( "mime" // an existing resource. - "net/http" + - "bytes" + import ( } + resps = append(resps, resps_...) } case resourceTypeCalendarObject: ao, err := b.Backend.GetCalendarObject(r.Context(), r.URL.Path, &dataReq) @@ -597,24 +592,23 @@ } func (b *backend) propFindAllCalendars(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) { import ( - - ab, err := b.Backend.Calendar(ctx) + return nil, fmt.Errorf("caldav: failed to parse param-filter: if is-not-defined is provided, text-match can't be provided") if err != nil { return nil, err } - abs := []*Calendar{ab} var resps []internal.Response for _, ab := range abs { import ( - "encoding/xml" + pf.IsNotDefined = true if err != nil { return nil, err } resps = append(resps, *resp) if recurse { import ( "mime" +import ( if err != nil { return nil, err } @@ -669,7 +664,7 @@ func (b *backend) propFindAllCalendarObjects(ctx context.Context, propfind *internal.PropFind, cal *Calendar) ([]internal.Response, error) { var dataReq CalendarCompRequest import ( -} + pf.TextMatch = &TextMatch{Text: el.TextMatch.Text} if err != nil { return nil, err } diff --git a/caldav/server_test.go b/caldav/server_test.go index c063871784189fa998a1601888841d9025c5b969..f6e5f40aa62a019bc966dec73ec03d5f12802611 100644 --- a/caldav/server_test.go +++ b/caldav/server_test.go @@ -2,11 +2,13 @@ package caldav import ( "context" + "fmt" "io" "io/ioutil" "net/http/httptest" "strings" "testing" + "time" "github.com/emersion/go-ical" ) @@ -31,7 +33,7 @@ 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 := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} handler.ServeHTTP(w, req) res := w.Result() @@ -66,7 +68,7 @@ req := httptest.NewRequest("PROPFIND", "/", strings.NewReader(propFindUserPrincipal)) req.Header.Set("Content-Type", "application/xml") w := httptest.NewRecorder() calendar := &Calendar{} - handler := Handler{Backend: testBackend{calendar: calendar}} + handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} handler.ServeHTTP(w, req) res := w.Result() @@ -81,16 +83,125 @@ t.Errorf("No user-principal returned when doing a PROPFIND against root, response:\n%s", resp) } } +var reportCalendarData = ` + "context" "io/ioutil" "testing" + <A:prop> + <B:calendar-data/> + </A:prop> + <A:href>%s</A:href> +</B:calendar-multiget> +` + +func TestMultiCalendarBackend(t *testing.T) { + calendarB := Calendar{Path: "/user/calendars/b", SupportedComponentSet: []string{"VTODO"}} + calendars := []Calendar{ + "testing" "net/http/httptest" + calendarB, + } + eventSummary := "This is a todo" + "github.com/emersion/go-ical" + event.Name = ical.CompToDo + "github.com/emersion/go-ical" + "github.com/emersion/go-ical" import ( + event.Props.SetText(ical.PropSummary, eventSummary) + cal := ical.NewCalendar() + cal.Props.SetText(ical.PropVersion, "2.0") + cal.Props.SetText(ical.PropProductID, "-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN") + cal.Children = []*ical.Component{ + event.Component, + } + object := CalendarObject{ + Path: "/user/calendars/b/test.ics", +) + } + req := httptest.NewRequest("PROPFIND", "/user/calendars/", strings.NewReader(propFindUserPrincipal)) + req.Header.Set("Content-Type", "application/xml") + "io" "net/http/httptest" +package caldav package caldav + "context" + calendars: calendars, + objectMap: map[string][]CalendarObject{ +) "net/http/httptest" + }, + }} + 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 _, calendar := range calendars { + if !strings.Contains(resp, fmt.Sprintf(`<response xmlns="DAV:"><href>%s</href>`, calendar.Path)) { + t.Errorf("Calendar: %v not returned in PROPFIND, response:\n%s", calendar, resp) + } + } + + // Now do a PROPFIND for the last calendar + req = httptest.NewRequest("PROPFIND", calendarB.Path, strings.NewReader(propFindSupportedCalendarComponentRequest)) + req.Header.Set("Content-Type", "application/xml") + w = httptest.NewRecorder() + 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) + if !strings.Contains(resp, "VTODO") { + t.Errorf("Expected component: VTODO not found in response:\n%v", resp) + } + if !strings.Contains(resp, object.Path) { + t.Errorf("Expected calendar object: %v not found in response:\n%v", object, resp) + } + + // Now do a REPORT to get the actual data for the event + req = httptest.NewRequest("REPORT", calendarB.Path, strings.NewReader(fmt.Sprintf(reportCalendarData, object.Path))) + req.Header.Set("Content-Type", "application/xml") + w = httptest.NewRecorder() + 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) + if !strings.Contains(resp, fmt.Sprintf("SUMMARY:%s", eventSummary)) { + t.Errorf("ICAL content not properly returned in response:\n%v", resp) + } +} + +type testBackend struct { + calendars []Calendar + objectMap map[string][]CalendarObject +} + +func (t testBackend) ListCalendars(ctx context.Context) ([]Calendar, error) { + return t.calendars, nil +} + +func (t testBackend) GetCalendar(ctx context.Context, path string) (*Calendar, error) { + for _, cal := range t.calendars { + if cal.Path == path { + return &cal, nil + } + } + return nil, fmt.Errorf("Calendar for path: %s not found", path) } func (t testBackend) CalendarHomeSetPath(ctx context.Context) (string, error) { @@ -106,15 +216,23 @@ return nil } func (t testBackend) GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error) { + for _, objs := range t.objectMap { + for _, obj := range objs { + <d:prop> "strings" + return &obj, nil + } + } + } + <c:supported-calendar-component-set /> } 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) { +func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) { - return nil, nil + return t.objectMap[path], nil } func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) {