Skip to content

Commit 36d2981

Browse files
committed
fix: add support for more props on CalDAV MKCOL
1 parent 25f1014 commit 36d2981

6 files changed

Lines changed: 243 additions & 9 deletions

File tree

caldav/caldav.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ type Calendar struct {
6767
Path string
6868
Name string
6969
Description string
70+
Color string
7071
MaxResourceSize int64
7172
SupportedComponentSet []string
73+
TimeZone string
7274
}
7375

7476
type CalendarCompRequest struct {

caldav/elements.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,11 @@ func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
230230
}
231231

232232
type mkcolReq struct {
233-
XMLName xml.Name `xml:"DAV: mkcol"`
234-
ResourceType internal.ResourceType `xml:"set>prop>resourcetype"`
235-
DisplayName string `xml:"set>prop>displayname"`
236-
// TODO this could theoretically contain all addressbook properties?
233+
XMLName xml.Name `xml:"DAV: mkcol"`
234+
ResourceType internal.ResourceType `xml:"set>prop>resourcetype"`
235+
DisplayName string `xml:"set>prop>displayname"`
236+
Description string `xml:"set>prop>calendar-description"`
237+
CalendarColor string `xml:"set>prop>calendar-color"`
238+
CalemdarTimeZone string `xml:"set>prop>calendar-timezone"`
239+
SupportedCalendarComponentSet supportedCalendarComponentSet `xml:"set>prop>supported-calendar-component-set"`
237240
}

caldav/server.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,14 @@ func (b *backend) Mkcol(r *http.Request) error {
724724
return internal.HTTPErrorf(http.StatusBadRequest, "carddav: unexpected resource type")
725725
}
726726
cal.Name = m.DisplayName
727-
// TODO ...
727+
cal.Description = m.Description
728+
cal.Color = strings.TrimSpace(m.CalendarColor)
729+
cal.TimeZone = strings.TrimSpace(m.CalemdarTimeZone)
730+
731+
cal.SupportedComponentSet = make([]string, len(m.SupportedCalendarComponentSet.Comp))
732+
for i, v := range m.SupportedCalendarComponentSet.Comp {
733+
cal.SupportedComponentSet[i] = v.Name
734+
}
728735
}
729736

730737
return b.Backend.CreateCalendar(r.Context(), &cal)

caldav/server_test.go

Lines changed: 209 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package caldav
33
import (
44
"context"
55
"fmt"
6+
"github.com/stretchr/testify/assert"
67
"io"
78
"io/ioutil"
89
"net/http/httptest"
@@ -33,7 +34,7 @@ func TestPropFindSupportedCalendarComponent(t *testing.T) {
3334
req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest))
3435
req.Header.Set("Content-Type", "application/xml")
3536
w := httptest.NewRecorder()
36-
handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
37+
handler := Handler{Backend: &testBackend{calendars: []Calendar{*calendar}}}
3738
handler.ServeHTTP(w, req)
3839

3940
res := w.Result()
@@ -68,7 +69,7 @@ func TestPropFindRoot(t *testing.T) {
6869
req.Header.Set("Content-Type", "application/xml")
6970
w := httptest.NewRecorder()
7071
calendar := &Calendar{}
71-
handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}}
72+
handler := Handler{Backend: &testBackend{calendars: []Calendar{*calendar}}}
7273
handler.ServeHTTP(w, req)
7374

7475
res := w.Result()
@@ -118,7 +119,7 @@ func TestMultiCalendarBackend(t *testing.T) {
118119
req := httptest.NewRequest("PROPFIND", "/user/calendars/", strings.NewReader(propFindUserPrincipal))
119120
req.Header.Set("Content-Type", "application/xml")
120121
w := httptest.NewRecorder()
121-
handler := Handler{Backend: testBackend{
122+
handler := Handler{Backend: &testBackend{
122123
calendars: calendars,
123124
objectMap: map[string][]CalendarObject{
124125
calendarB.Path: []CalendarObject{object},
@@ -177,12 +178,216 @@ func TestMultiCalendarBackend(t *testing.T) {
177178
}
178179
}
179180

181+
var mkcolRequestData = `
182+
<?xml version='1.0' encoding='UTF-8' ?>
183+
<mkcol
184+
xmlns="DAV:"
185+
xmlns:CAL="urn:ietf:params:xml:ns:caldav"
186+
xmlns:CARD="urn:ietf:params:xml:ns:carddav">
187+
<set>
188+
<prop>
189+
<resourcetype>
190+
<collection />
191+
<CAL:calendar />
192+
</resourcetype>
193+
<displayname>Test calendar</displayname>
194+
<CAL:calendar-description>A calendar for testing</CAL:calendar-description>
195+
<n0:calendar-color
196+
xmlns:n0="http://apple.com/ns/ical/">#009688FF
197+
</n0:calendar-color>
198+
<CAL:calendar-timezone>
199+
<![CDATA[BEGIN:VCALENDAR
200+
BEGIN:VTIMEZONE
201+
TZID:Europe/Berlin
202+
LAST-MODIFIED:20230104T023643Z
203+
TZURL:https://www.tzurl.org/zoneinfo/Europe/Berlin
204+
X-LIC-LOCATION:Europe/Berlin
205+
X-PROLEPTIC-TZNAME:LMT
206+
BEGIN:STANDARD
207+
TZNAME:CET
208+
TZOFFSETFROM:+005328
209+
TZOFFSETTO:+0100
210+
DTSTART:18930401T000632
211+
END:STANDARD
212+
BEGIN:DAYLIGHT
213+
TZNAME:CEST
214+
TZOFFSETFROM:+0100
215+
TZOFFSETTO:+0200
216+
DTSTART:19160430T230000
217+
RDATE:19400401T020000
218+
RDATE:19430329T020000
219+
RDATE:19460414T020000
220+
RDATE:19470406T030000
221+
RDATE:19480418T020000
222+
RDATE:19490410T020000
223+
RDATE:19800406T020000
224+
END:DAYLIGHT
225+
BEGIN:STANDARD
226+
TZNAME:CET
227+
TZOFFSETFROM:+0200
228+
TZOFFSETTO:+0100
229+
DTSTART:19161001T010000
230+
RDATE:19421102T030000
231+
RDATE:19431004T030000
232+
RDATE:19441002T030000
233+
RDATE:19451118T030000
234+
RDATE:19461007T030000
235+
END:STANDARD
236+
BEGIN:DAYLIGHT
237+
TZNAME:CEST
238+
TZOFFSETFROM:+0100
239+
TZOFFSETTO:+0200
240+
DTSTART:19170416T020000
241+
RRULE:FREQ=YEARLY;UNTIL=19180415T010000Z;BYMONTH=4;BYDAY=3MO
242+
END:DAYLIGHT
243+
BEGIN:STANDARD
244+
TZNAME:CET
245+
TZOFFSETFROM:+0200
246+
TZOFFSETTO:+0100
247+
DTSTART:19170917T030000
248+
RRULE:FREQ=YEARLY;UNTIL=19180916T010000Z;BYMONTH=9;BYDAY=3MO
249+
END:STANDARD
250+
BEGIN:DAYLIGHT
251+
TZNAME:CEST
252+
TZOFFSETFROM:+0100
253+
TZOFFSETTO:+0200
254+
DTSTART:19440403T020000
255+
RRULE:FREQ=YEARLY;UNTIL=19450402T010000Z;BYMONTH=4;BYDAY=1MO
256+
END:DAYLIGHT
257+
BEGIN:DAYLIGHT
258+
TZNAME:CEMT
259+
TZOFFSETFROM:+0200
260+
TZOFFSETTO:+0300
261+
DTSTART:19450524T010000
262+
RDATE:19470511T020000
263+
END:DAYLIGHT
264+
BEGIN:DAYLIGHT
265+
TZNAME:CEST
266+
TZOFFSETFROM:+0300
267+
TZOFFSETTO:+0200
268+
DTSTART:19450924T030000
269+
RDATE:19470629T030000
270+
END:DAYLIGHT
271+
BEGIN:STANDARD
272+
TZNAME:CET
273+
TZOFFSETFROM:+0100
274+
TZOFFSETTO:+0100
275+
DTSTART:19460101T000000
276+
RDATE:19800101T000000
277+
END:STANDARD
278+
BEGIN:STANDARD
279+
TZNAME:CET
280+
TZOFFSETFROM:+0200
281+
TZOFFSETTO:+0100
282+
DTSTART:19471005T030000
283+
RRULE:FREQ=YEARLY;UNTIL=19491002T010000Z;BYMONTH=10;BYDAY=1SU
284+
END:STANDARD
285+
BEGIN:STANDARD
286+
TZNAME:CET
287+
TZOFFSETFROM:+0200
288+
TZOFFSETTO:+0100
289+
DTSTART:19800928T030000
290+
RRULE:FREQ=YEARLY;UNTIL=19950924T010000Z;BYMONTH=9;BYDAY=-1SU
291+
END:STANDARD
292+
BEGIN:DAYLIGHT
293+
TZNAME:CEST
294+
TZOFFSETFROM:+0100
295+
TZOFFSETTO:+0200
296+
DTSTART:19810329T020000
297+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
298+
END:DAYLIGHT
299+
BEGIN:STANDARD
300+
TZNAME:CET
301+
TZOFFSETFROM:+0200
302+
TZOFFSETTO:+0100
303+
DTSTART:19961027T030000
304+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
305+
END:STANDARD
306+
END:VTIMEZONE
307+
END:VCALENDAR
308+
]]>
309+
</CAL:calendar-timezone>
310+
<CAL:supported-calendar-component-set>
311+
<CAL:comp name="VEVENT" />
312+
<CAL:comp name="VTODO" />
313+
<CAL:comp name="VJOURNAL" />
314+
</CAL:supported-calendar-component-set>
315+
</prop>
316+
</set>
317+
</mkcol>`
318+
319+
func TestCreateCalendar(t *testing.T) {
320+
tb := testBackend{
321+
calendars: nil,
322+
objectMap: nil,
323+
}
324+
b := backend{
325+
Backend: &tb,
326+
Prefix: "/dav",
327+
}
328+
req := httptest.NewRequest("MKCOL", "/dav/calendars/user0/test-calendar", strings.NewReader(mkcolRequestData))
329+
req.Header.Set("Content-Type", "application/xml")
330+
331+
err := b.Mkcol(req)
332+
assert.NoError(t, err)
333+
assert.Len(t, tb.calendars, 1)
334+
c := tb.calendars[0]
335+
assert.Equal(t, "Test calendar", c.Name)
336+
assert.Equal(t, "/dav/calendars/user0/test-calendar", c.Path)
337+
assert.Equal(t, "A calendar for testing", c.Description)
338+
assert.Equal(t, "#009688FF", c.Color)
339+
assert.Equal(t, []string{"VEVENT", "VTODO", "VJOURNAL"}, c.SupportedComponentSet)
340+
assert.Contains(t, c.TimeZone, "BEGIN:VCALENDAR")
341+
}
342+
343+
var mkcolRequestDataMinimalBody = `
344+
<?xml version='1.0' encoding='UTF-8' ?>
345+
<mkcol
346+
xmlns="DAV:"
347+
xmlns:CAL="urn:ietf:params:xml:ns:caldav"
348+
xmlns:CARD="urn:ietf:params:xml:ns:carddav">
349+
<set>
350+
<prop>
351+
<resourcetype>
352+
<collection />
353+
<CAL:calendar />
354+
</resourcetype>
355+
<displayname>Test calendar</displayname>
356+
</prop>
357+
</set>
358+
</mkcol>`
359+
360+
func TestCreateCalendarMinimalBody(t *testing.T) {
361+
tb := testBackend{
362+
calendars: nil,
363+
objectMap: nil,
364+
}
365+
b := backend{
366+
Backend: &tb,
367+
Prefix: "/dav",
368+
}
369+
req := httptest.NewRequest("MKCOL", "/dav/calendars/user0/test-calendar", strings.NewReader(mkcolRequestDataMinimalBody))
370+
req.Header.Set("Content-Type", "application/xml")
371+
372+
err := b.Mkcol(req)
373+
assert.NoError(t, err)
374+
assert.Len(t, tb.calendars, 1)
375+
c := tb.calendars[0]
376+
assert.Equal(t, "Test calendar", c.Name)
377+
assert.Equal(t, "/dav/calendars/user0/test-calendar", c.Path)
378+
assert.Equal(t, "", c.Description)
379+
assert.Equal(t, "", c.Color)
380+
assert.Equal(t, []string{}, c.SupportedComponentSet)
381+
assert.Equal(t, "", c.TimeZone)
382+
}
383+
180384
type testBackend struct {
181385
calendars []Calendar
182386
objectMap map[string][]CalendarObject
183387
}
184388

185-
func (t testBackend) CreateCalendar(ctx context.Context, calendar *Calendar) error {
389+
func (t *testBackend) CreateCalendar(ctx context.Context, calendar *Calendar) error {
390+
t.calendars = append(t.calendars, *calendar)
186391
return nil
187392
}
188393

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ go 1.13
55
require (
66
github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f
77
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9
8+
github.com/stretchr/testify v1.8.4 // indirect
89
github.com/teambition/rrule-go v1.8.2 // indirect
910
)

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
14
github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f h1:feGUUxxvOtWVOhTko8Cbmp33a+tU0IMZxMEmnkoAISQ=
25
github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgfNMULRxqZkadG1Vh44we3y5gJAtTBlVsx1BKQ=
36
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9 h1:ATgqloALX6cHCranzkLb8/zjivwQ9DWWDCQRnxTPfaA=
47
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
8+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
11+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
12+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
13+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
14+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
15+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
16+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
517
github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU=
618
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
719
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
20+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
21+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
22+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
23+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)