From e971269ffb381be98564cc8637ddb5645d7b7a47 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 31 Mar 2022 11:22:23 +0200 Subject: [PATCH] Add function to validate calendar for CalDAV CalDAV imposes a set of constraints on iCal Calendar objects. They are spelled out in RFC 4791, section 4.1 [1]. Add an exported function to validate a calendar according to those constraints, and return data that is necessary for further CalDAV processing and which can only be extracted if the calendar meets these constraints. [1]: https://datatracker.ietf.org/doc/html/rfc4791#section-4.1 --- caldav/caldav.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/caldav/caldav.go b/caldav/caldav.go index 60d84f4..9d5ca11 100644 --- a/caldav/caldav.go +++ b/caldav/caldav.go @@ -4,6 +4,7 @@ package caldav import ( + "fmt" "time" "github.com/emersion/go-ical" @@ -15,6 +16,51 @@ func NewCalendarHomeSet(path string) webdav.BackendSuppliedHomeSet { return &calendarHomeSet{Href: internal.Href{Path: path}} } +// ValidateCalendarObject checks the validity of a calendar object according to +// the contraints layed out in RFC 4791 section 4.1 and returns the only event +// type and UID occuring in this calendar, or an error if the calendar could +// not be validated. +func ValidateCalendarObject(cal *ical.Calendar) (eventType string, uid string, err error) { + // Calendar object resources contained in calendar collections + // MUST NOT specify the iCalendar METHOD property. + if prop := cal.Props.Get(ical.PropMethod); prop != nil { + return "", "", fmt.Errorf("calendar resource must not specify METHOD property") + } + + for _, comp := range cal.Children { + // Calendar object resources contained in calendar collections + // MUST NOT contain more than one type of calendar component + // (e.g., VEVENT, VTODO, VJOURNAL, VFREEBUSY, etc.) with the + // exception of VTIMEZONE components, which MUST be specified + // for each unique TZID parameter value specified in the + // iCalendar object. + if comp.Name != ical.CompTimezone { + if eventType == "" { + eventType = comp.Name + } + if eventType != comp.Name { + return "", "", fmt.Errorf("conflicting event types in calendar: %s, %s", eventType, comp.Name) + } + // TODO check VTIMEZONE for each TZID? + } + + // Calendar components in a calendar collection that have + // different UID property values MUST be stored in separate + // calendar object resources. + compUID, err := comp.Props.Text(ical.PropUID) + if err != nil { + return "", "", fmt.Errorf("error checking component UID: %v", err) + } + if uid == "" { + uid = compUID + } + if uid != compUID { + return "", "", fmt.Errorf("conflicting UID values in calendar: %s, %s", uid, compUID) + } + } + return eventType, uid, nil +} + type Calendar struct { Path string Name string