Rust CalDav Client from Scratch
By: Marshal Shi
Tags: Rust, Request, Programming, Caldav, Calendar

Requests

CalDav is based on WebDav which is an extension of HTTP. SO, not only some methods which are GET , PUT and DELETE are used in CalDav, but also there are some other extended methods implemented, such as PROPFIND , REPORT , etc… The main methods which we will demo here are the extended methods.

Below is a sample code to get events via Python caldav module, and we will do the same steps in Rust.

client = caldav.DAVClient(url=url, username=username, password=password)

my_principal = client.principal()

calendars = my_principal.calendars()

for cal in calendars:

events = cal.events()

Request Principal

We want event data from our URL but why we need to principal here. Actually, the CalDav URL is just a starting URL that will help us to get all calendar URLs. Then we will use calendar URLs to fetch events from each of them. The reason that we have different calendars is they are shared with the current account. The fetching data flowchart will look like below:

Image by author

For getting a principal response, the main things which we will change comparing with an HTTP request are:

Request method, it will be PROPFIND

Request content type, it will be application/xml

Extra request header value Depth with value 0 ( Depth will tell the server how deep we want to get data recursively).

with value ( will tell the server how deep we want to get data recursively). Request body, it will be an XML format data with the request field, which is called tags.

Below is the demo code on how to do the above request. I have added comments for explanation.

Fetch ` principal ` content using `reqwest`

After running the above function, the server will send back an XML response it will look like below:

<d:multistatus xmlns:d=”DAV:”

xmlns:s=”http://sabredav.org/ns"

xmlns:cal=”urn:ietf:params:xml:ns:caldav”

xmlns:cs=”http://calendarserver.org/ns/"

xmlns:oc=”http://owncloud.org/ns">

<d:response>

<d:href>/remote.php/dav/calendars/USERNAME/</d:href>

<d:propstat>

<d:prop>

<d:current-user-principal>

<d:href>

/remote.php/dav/principals/users/USERNAME/

</d:href>

</d:current-user-principal>

</d:prop>

<d:status>HTTP/1.1 200 OK</d:status>

</d:propstat>

</d:response>

</d:multistatus>

Please note the URL used and the URL returned by the server may be different from my example.

After getting a response from the server, it’s easier for us to get the principal URL. I will use minidom crate because minidom will parse XML into a tree structure and be easy to use. I won’t write code for parsing XML here. Please feel free to read minidom doc or check my repo link which is listed at the end of the article.

Requests calendar-home-set URL and Calendars URLs are using the same method as principal . I won’t show full code here, instead I will show the request body only.

// request body to PROPFIND calendar-home-set static HOMESET_BODY: &str = r#”

<d:propfind xmlns:d=”DAV:” xmlns:c=”urn:ietf:params:xml:ns:caldav” >

<d:self/>

<d:prop>

<c:calendar-home-set />

</d:prop>

</d:propfind>

“#; // request body to PROPFIND calendars

static CAL_BODY: &str = r#”

<d:propfind xmlns:d=”DAV:” xmlns:c=”urn:ietf:params:xml:ns:caldav” >

<d:prop>

<d:displayname />

<d:resourcetype />

<c:supported-calendar-component-set />

</d:prop>

</d:propfind>

“#;

The response has a list of calendars with request properties after getting data from calendars’ request. For instance, below is one entry in my return response:

<d:response>

<d:href>/remote.php/dav/calendars/USERNAME/personal/</d:href>

<d:propstat>

<d:prop>

<d:displayname>Personal</d:displayname>

<d:resourcetype>

<d:collection/>

<cal:calendar/>

</d:resourcetype>

<cal:supported-calendar-component-set>

<cal:comp name=”VEVENT”/>

<cal:comp name=”VTODO”/>

</cal:supported-calendar-component-set>

</d:prop>

<d:status>HTTP/1.1 200 OK</d:status>

</d:propstat>

</d:response>

Please note that some of the responses may be empty, we can just filter them out when parsing.

Request Events

Now we have found the calendar URLs in the above calendar request. We could use it to fetch our events. Before starting fetching, please check cal:supported-calendar-component-set . It lists two types of the calendar we can fetch from the server, VEVENT and VTODO . This is important since it’s telling us what kind of data we can get.

For getting events from a single calendar URL, we will have the following changes:

Use REPORT method

method Use 1 as the value of Depth

as the value of Add filter VEVENT in XML body (we will only fetch VEVENT data)

in XML body (we will only fetch data) Add time range filter in XML body (target on a specific time range)

Below is the demo code to fetch events from a calendar URL.

Request time-range events from a calendar.

After running the code, the output like below. The whole event data is showing in the calendar-data tag. There is a summary, date, status, etc… in it.

<d:multistatus xmlns:d=”DAV:” xmlns:s=”http://sabredav.org/ns"

xmlns:cal=”urn:ietf:params:xml:ns:caldav”

xmlns:cs=”http://calendarserver.org/ns/"

xmlns:oc=”http://owncloud.org/ns">

<d:response>

<d:href>/remote.php/dav/calendars/USERNAME/personal/ownCloud-ICSIDSTRING.ics</d:href>

<d:propstat>

<d:prop>

<d:getetag>"da86fa060d06bfdedf79ea7b6f050af4" </d:getetag>

<cal:calendar-data>

BEGIN:VCALENDAR

PRODID:-//ownCloud calendar v1.6.3

VERSION:2.0

CALSCALE:GREGORIAN

BEGIN:VEVENT

CREATED:20200323T021525Z

DTSTAMP:20200323T021836Z

LAST-MODIFIED:20200323T021836Z

UID:OG2XT87F6F6MWLXYVWD3N

SUMMARY:test event

CLASS:CONFIDENTIAL

STATUS:CONFIRMED

SEQUENCE:4

RRULE:FREQ=DAILY

DTSTART;TZID=Asia/Hong_Kong:20200316T000000

DTEND;TZID=Asia/Hong_Kong:20200316T010000

END:VEVENT

BEGIN:VTIMEZONE

TZID:Asia/Hong_Kong

BEGIN:STANDARD

TZOFFSETFROM:+0800

TZOFFSETTO:+0800

TZNAME:HKT

DTSTART:19700101T000000

END:STANDARD

END:VTIMEZONE

END:VCALENDAR

</cal:calendar-data>

</d:prop>

<d:status>HTTP/1.1 200 OK</d:status>

</d:propstat>

</d:response>

</d:multistatus>

Now we successfully got events from the original CalDav URL. If we want to send email or SMS to ourselves, we can add these event content in email or message.