Liu Song’s Projects


~/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) {