Building a CalDAV interface – Request and Response example

A couple of months ago, I was tasked with building a CalDAV interface for calendar synchronization with an existing, but highly customized webserver with an existing calendar application from scratch. An impressive number of implementations already exist, most in the open source spectrum. But what I could not find was a step by step guide covering the whole communication from discovery to item delivery. Studying the sources is very insightful, though not very efficient. Reading the convoluted rfcs deprecating each other is even worse. So here is my attempt at a basic, language-agnostic guide.

CalDAV uses the standard http methods like GET and PUT, inherits some from DAV, like PROPFIND and defines also its own like REPORT. Let’s take a closer look at the main request types.

OPTIONS

This request is wonderfully simple. The server returns the supported subset of CalDAV functionality in the header and sends it to the client with http status 200 in a response with empty content. Note that it is not always the first request and may be sent after one or more PROPFINDs. An example for the headers that are returned would be this very basic set of features:

DAV “1,2,access-control,calendar-access”
Allow “OPTIONS,GET,DELETE,PROPFIND,PUT,REPORT”

PROPFIND

This request queries calendar and calendar item information, but not the items themselves. The client will send a sequence of PROPFIND requests on different targets to determine their properties. Properties are requested as tags in the xml body. The server should either return a value in the xml if the property is found or indicate its nonexistence by declaring its status 404. Lazy clients who do not want to enumerate properties separately will try something like:

<D:propfind xmlns:D=’DAV:’><D:allprop/></D:propfind>

The requested properties vary across clients. A typical example for calendar discovery would be this request-response ping-pong game from the Mac OS iCalendar application. PROPFIND and REPORT requests should return status code 207.

PROPFIND /caldav/marmoser

<A:propfind xmlns:A=”DAV:”>
<A:prop>
<B:calendar-home-set xmlns:B=”urn:ietf:params:xml:ns:caldav”/>
<B:calendar-user-address-set xmlns:B=”urn:ietf:params:xml:ns:caldav”/>
<A:current-user-principal/>
<A:displayname/>
<C:dropbox-home-URL xmlns:C=”http://calendarserver.org/ns/”/&gt;
<C:email-address-set xmlns:C=”http://calendarserver.org/ns/”/&gt;
<C:notification-URL xmlns:C=”http://calendarserver.org/ns/”/&gt;
<A:principal-collection-set/>
<A:principal-URL/>
<A:resource-id/>
<B:schedule-inbox-URL xmlns:B=”urn:ietf:params:xml:ns:caldav”/>
<B:schedule-outbox-URL xmlns:B=”urn:ietf:params:xml:ns:caldav”/>
<A:supported-report-set/>
</A:prop>
</A:propfind>

The response would look like below. Note the http status for the different attributes.

<d:multistatus xmlns:d=”DAV:” xmlns:cs=”http://calendarserver.org/ns/&#8221; xmlns:c=”urn:ietf:params:xml:ns:caldav” xmlns:ical=”http://apple.com/ns/ical/”&gt;
<d:response>
<d:href>/caldav/marmoser</d:href>
<d:propstat>
<d:prop>
<c:calendar-home-set>
<d:href>/caldav/marmoser/calendar
</c:calendar-home-set>

<d:principal-URL>
<d:href>/caldav/marmoser/principal
</d:principal-URL>
<d:supported-report-set>
<d:supported-report>
<d:report>
<c:calendar-multiget/>
</d:report>
</d:supported-report>
<d:supported-report>
<d:report>
<c:calendar-query/>
</d:report>
</d:supported-report>
</d:supported-report-set>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
<d:propstat>
<d:prop>
<c:calendar-user-address-set/>
<cs:dropbox-home-URL/>
<cs:email-address-set/>
<cs:notification-URL/>
<d:resource-id/>
<c:schedule-inbox-URL/>
<c:schedule-outbox-URL/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>

We notice two different urls: a principal url and a calendar url. So what exactly is the principal url? Some clients require a principal url they can query information from. Some are satisfied with the root url and the calendar url.

Now that the calendar is known, the client will try to query it:

PROPFIND /caldav/marmoser/calendar

<A:propfind xmlns:A=”DAV:”>
<A:prop>
…..
<C:getctag xmlns:C=”http://calendarserver.org/ns/”/&gt;
…..
<A:resource-id/>
<A:resourcetype/>
…..
<A:supported-report-set/>
<A:sync-token/>
</A:prop>
</A:propfind>

Let’s look at the most important attributes in greater detail.

A ctag is an important value indicating if something in the calendar has changed, usually a hash.

supported-report-set will tell the client what the server supports. In our case, we indicate support for calendar-multiget and calendar-query methods for the REPORT method.

<d:supported-report-set>
<d:supported-report>
<d:report>
<c:calendar-multiget/>
</d:report>
</d:supported-report>
<d:supported-report>
<d:report>
<c:calendar-query/>
</d:report>
</d:supported-report>
</d:supported-report-set>

sync-token returns a value if your server supports¬† rfc6578¬† synchronization. The idea is similar to the ctag: the clients sends a sync-token, the server compares if and, if a change is detected, returns the delta between the client’s and the server’s token. This is not easy to implement, since you need a data model that supports calculating and storing the changes between two sync tokens.

resourcetype will return the type of resource found under this url. For a calendar it would be <d:collection/> with DAV namespace and <c:calendar/> with CalDAV namespace.

REPORT

Now as the client will try to get the hash values for the calendar contents, the etags. Notice the time range limitation and the filters restricting the results to VEVENT type ( no VTODOs oder other resources should be returned ).

REPORT /caldav/marmoser/calendar/

<B:calendar-query xmlns:B=”urn:ietf:params:xml:ns:caldav”>
<A:prop xmlns:A=”DAV:”>
<A:getetag/>
<A:getcontenttype/>
</A:prop>
<B:filter>
<B:comp-filter name=”VCALENDAR”>
<B:comp-filter name=”VEVENT”>
<B:time-range start=”20170412T010101Z” end=”20170503T010101Z”/>
</B:comp-filter>
</B:comp-filter>
</B:filter>
</B:calendar-query>

response:

<D:multistatus xmlns:D=”DAV:” xmlns:C=”urn:ietf:params:xml:ns:caldav” xmlns:CS=”http://calendarserver.org/ns”&gt;
<D:response>
<D:href>/caldav/marmoser/calendar/20161205T084507Z-153527473.ics
<D:propstat>
<D:prop>
<D:getetag>”0bdef6e4a53d616c314fa99ff6f24aa8″</D:getetag>
<D:getcontenttype>text/calendar;charset=utf-8;component=vevent</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>

If the etag has changed, the client will request the ics data for this calendar item. In this example, only one item is requested, but it is possible to request more than one inside a multiget. Some clients are stupid and request only one item at the time, so beware of request storms coming from android apps. The url to the ics file in href should also work when requested with a GET, and some clients will go for this.

REPORT /caldav/marmoser/calendar/

<B:calendar-multiget xmlns:B=”urn:ietf:params:xml:ns:caldav”>
<A:prop xmlns:A=”DAV:”>
<A:getetag/>
<B:calendar-data/>
<C:updated-by xmlns:C=”http://calendarserver.org/ns/”/&gt;
<C:created-by xmlns:C=”http://calendarserver.org/ns/”/&gt;
</A:prop>
<A:href xmlns:A=”DAV:”>/caldav/marmoser/calendar/20161205T084507Z-153527473.ics
</B:calendar-multiget>

Finally some calendar item data the client will be able to display.

<D:multistatus xmlns:D=”DAV:” xmlns:C=”urn:ietf:params:xml:ns:caldav” xmlns:CS=”http://calendarserver.org/ns”&gt;
<D:response>
<D:href>/caldav/marmoser/calendar/20161205T084507Z-153527473.ics</D:href>
<D:propstat>
<D:prop>
<D:getetag>”88e7895d9805b5835091108631f0ffb1″</D:getetag>
<C:calendar-data>BEGIN:VCALENDAR
X-WR-TIMEZONE:Europe/Vienna
CALSCALE:GREGORIAN
VERSION:2.0
METHOD:PUBLISH
BEGIN:VEVENT
CREATED;VALUE=DATE-TIME:20161117T092336Z
LAST-MODIFIED;VALUE=DATE-TIME:20161117T092336Z
DTSTAMP;VALUE=DATE-TIME:20161117T092336Z
DTSTART;VALUE=DATE-TIME;TZID=Europe/Vienna:20161117T150000
DTEND;VALUE=DATE-TIME;TZID=Europe/Vienna:20161117T160000
UID:20161205T084507Z-153527473
DESCRIPTION:Too wonderful for words
SUMMARY:Something wonderful
RRULE:FREQ=WEEKLY;BYDAY=TH;INTERVAL=1;UNTIL=20201116T230000Z
END:VEVENT
END:VCALENDAR
</C:calendar-data>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
<D:propstat>
<D:prop>
<CS:updated-by/>
<CS:created-by/>
</D:prop>
<D:status>HTTP/1.1 404 Not Found</D:status>
</D:propstat>
</D:response>
</D:multistatus>

Keep in mind that this is only a very basic example and does not cover the immense range of CalDAV’s possibilities. Every client ( Mac OS X, iOS, android sync apps, evolution, thunderbird etc ) will send different requests and behave differently.

Trying the Solarized Colorscheme

Usually, I prefer functionality over aesthetic appeal. Sometimes those two terms cannot be fully separated or can be reconciled in a unity of both. An example for this is the colorscheme applied to a user interface.

I decided to give the Solarized Colorscheme a try, it is still quite a hype and supposed to be immensely popular.

Solarized was created by a man with the passion of an artist and the precision of a scientist who put unbelievable energies into finding the best colors for a programmer’s text editor. The result is remarkable and can be found here:

http://ethanschoonover.com/solarized

( I wonder where he got the name from. Was it the Stanislaw Lem novel, the Tarkovsky film, one of its remakes or the old fashioned unix brand? My guess is the latter. Ironically the default colorscheme of the unix variant was a visual nightmare. )

The result is a light colorscheme and a dark one, if your workplace gets enough daylight, you are supposed to choose the light one, if you shun daylight or love to work in the night, the dark scheme is for you. I almost work exclusively with the dark scheme, in daytime and nighttime.

Solarized themes exist for the console, gtk, some window managers and many editors.

Arch Linux provides about 30 packages in AUR for the search term. You can solarize about anything from mc, emacs, vi and mutt up window manager decorations, consoles and text editors.  My own hacky ( not strictly canonical in terms of text color choices ) variant for SciTE, which I created because I could not find any for this editor fitting my purpose can be found on my github: https://github.com/marmoser/config/blob/master/.SciTEUser.properties

It covers most of the languages I use, but still leaves many looking very ugly due to missing style definitions.

My conclusions: For long working hours, a colorscheme with dark background and brighter text causes less eyestrain. I’m not switching back to dark text on bright backgrounds anytime soon. If UI colorschemes are something like a religion, Solarized manages to appeal even to agnostics.