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.

Advertisements

SWF archeology: how to retrieve the dimensions from a flash (swf) file

Embedding flash objects can be troublesome, if you don’t know the native resolution of the file you want to display.

Of course you can use the embed tag or the object tag and set its width and height to 100%, but you have to stop editing and view your page to find out how big the object actually is.

In a WYSIWYG html richtext editor a placeholder that has exactly the size of the swf object helps a great deal to get an impression how big the element will actually be when viewing it.

Let’s see how this can be accomplished:
swfs files store the width and height of the object in the file’s header.
All we need to do is read the file header and interpret our findings.

The first 3 bytes of a swf file indicate the file type.
FWS is the magic byte number for normal swf, CWS indicates a compressed file, where everything beyond byte 8 is zlib compressed. We can also find version info and file size in the header after byte 3, but this has nothing to do with the file’s dimensions.

Reading another 9 bytes starting with byte 9 (this is where the header ends) will give us a rect structure, which stores the file’s dimensions. Read it in high-to-low order.

If you don’t know what a rect structure is (I didn’t know either), fortunately things are well documented.

structure of rect
Nbits nBits = UB[5]
Bits in each rect value field
Xmin SB[nBits] X minimum position for rect
Xmax SB[nBits] X maximum position for rect
Ymin SB[nBits] Y minimum position for rect
Ymax SB[nBits] Y maximum position for rect
Xmax-Xmin will give us the dimensions for x. The length of the Xmax and Xmin field in bits is determined by nbits.

Still, the results appear off the scale. Nothing wrong here, x and y dimensions are stored in twips (some invention of ressourceful typesetters?). Just divide the results by 20 and you get the size in pixel.


Here is a simple example in tcl (if you are looking for examples in c, there’s an overabundance of them on the web):

set f [open $swffile r]
fconfigure $f -translation binary -encoding binary
set header [read $f 3]
set version [read $f 1]
#size is uint32
set size [read $f 4]
set data [read $f]
close $f

if {$header ni {FWS CWS}} {
  #file is not flash
  return
}

#if the file is compressed, uncompress it first
if {$header eq {CWS}} {
  #compressed file, uncompress with zlib
  #we could use something nicer here, like ns_zlib or the zlib package
  set fileID [open "/tmp/swfuncompressed_XXXXXX" w]
  fconfigure $fileID -translation binary -encoding binary
  puts $fileID $data
  close $fileID
  set data [eval exec "/usr/bin/openssl zlib -d -in $tmp_file"]
  file delete $tmp_file
}

#rect contains dimensions
set rect [string range $data 0 9]

#structure of rect
#Nbits nBits = UB[5]
#Bits in each rect value field
#Xmin SB[nBits] X minimum position for rect
#Xmax SB[nBits] X maximum position for rect
#Ymin SB[nBits] Y minimum position for rect
#Ymax SB[nBits] Y maximum position for rect
binary scan $rect B* bin_rect
set end 4
set start 0
set nbit 0
foreach char [split [string range $bin_rect $start $end] ""] {
  set nbit [expr {($nbit << 1) + $char}]
}

for {set i 0} {$i<4} {incr i} {
  set start [expr {$end+1}]
  set end [expr {$end+$nbit}]
  switch $i {
    0 {
      set xmin 0
      foreach char [split [string range $bin_rect $start $end] ""] {
        set xmin [expr {($xmin << 1) + $char}]
      }
    }
    1 {
      set xmax 0
      foreach char [split [string range $bin_rect $start $end] ""] {
        set xmax [expr {($xmax << 1) + $char}]
      }
    }
    2 {
      set ymin 0
      foreach char [split [string range $bin_rect $start $end] ""] {
        set ymin [expr {($ymin << 1) + $char}]
      }
    }
    3 {
      set ymax 0
      foreach char [split [string range $bin_rect $start $end] ""] {
        set ymax [expr {($ymax << 1) + $char}]
      }
   }
  }
}

set dimensions(width) [expr {($xmax-$xmin)/20}]
set dimensions(height) [expr {($ymax-$ymin)/20}]
return [array get dimensions]