Calendars and Address Books Discovery Mechanisms

Calendar and address book folders, as well as your principal items, could be located anywhere in your server folders hierarchy. To assist client application in finding these CalDAV/CardDAV folders and detect server features, your server may provide several discovery mechanisms. With discovery mechanisms, you will specify only the root of your server while the server will provide information where calendars and address books are located.

Note that even though some discovery mechanisms are optional, iOS and OS X requires ALL discovery mechanism described in this article to be supported.

Below we will describe how discovery works. On the picture below you can see items that participate in discovery process and interfaces that they must implement:

Items and interfaces that participate in discovery process.

The numbers specify a typical request sequence during discovery. Note that requests and their sequence submitted by various CalDAV/CardDAV clients may differ. The client application may submit additional OPTIONS and PROPFIND requests not listed here, that must be processed by your server for successful connection. Examine your WebDAV server log (WebDAVLog.txt) to see the exact sequence of requests that is specific to your client.

Here are the major items that participate in discovery:

Site Root

If you wish to enable easy location of calendars and address books your site root must process .well-known URI requests. During this request, your server will return the Context Path Folder path.

Context Path Folder

Context path folder could be located anywhere in your folders hierarchy. This folder must return URL of the currently logged-in principal for further discovery of calendars and address books available for this user.

User (Principal)

Contains information about the user. During discovery process, the client application will request the location of the home-set folder on the principal URL.

Home-Set Folders

Home-set folders contain calendar folders and address books folders available for the currently logged-in user. The client application will list calendar folders and address book folders in every home-set folder returned by your implementation.

Below we will describe the discovery requests that your CalDAV/CardDAV client can submit:

Context Path Discovery (.well-known Request)

During this request the client request the location of ClaDAV or CardDAV root folder (context path). While the .well-known request is optional it is required by iOS and OS X Calendar. Here is an example of CalDAV request and response:

PROPFIND /.well-known/caldav
Depth: 0
Authorization: Basic ************

HTTP/1.1 301 Moved Permanently
Location : /DAV/

Client submits PROPFIND request to /.well-known/caldav or /.well-known/carddav URL. The server must return a redirect to the folder where CalDAV or CardDAV service is available, which is called context path. Do not confuse the context path folder with DavContextBaseAsync class – the later represents a WebDAV HTTP request on a server side.

Typically you do not need to program anything to implement this context path discovery. The Engine will call GetHierarchyItemAsync passing /.well-known/caldav or /.well-known/carddav path, and if your implementation returns null, the Engine redirects to the site root (‘/’).

Note that in all CalDAV/CardDAV samples provided with the SDK and in code generated by Visual Studio wizards the context path folder is a site root (the GetHierarchyItemAsync implementation returns null for the .well-known request). In this article, the context path is on /DAV/ folder for the description convenience.

If required, you can customize the .well-known URI behavior, returning IHierarchyItemAsync from GetHierarchyItemAsync call instead of returning null. Below you can see the sequence diagram for these request:

.well-known CalDAV request sequence diagram.

The returned IHierarchyItemAsync item must represent the context path folder on your CalDAV or CardDAV server. The Engine will request the Path property on the returned item and redirect to the URL constructed from it.

Current User Principal Discovery

During this request the client application requests information about the loged-in user (current user principal). Below you can see an example of such request and response:

PROPFIND /DAV/
Depth: 0
Content-Length: 181
Content-Type: text/xml
Authorization: Basic ************

<?xml version="1.0" encoding="UTF-8"?>
<A:propfind xmlns:A="DAV:">
  <A:prop>
    <A:current-user-principal/>
    <A:principal-URL/>
    <A:resourcetype/>
  </A:prop>
</A:propfind>


HTTP/1.1 207 Multi-Status
DAV : 1, 3, access-control, addressbook, calendar-access
Content-Type : application/xml; charset=utf-8

<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
  <d:response>
    <d:href>/DAV/</d:href>
    <d:propstat>
      <d:status>HTTP/1.1 200 OK</d:status>
      <d:prop>
        <d:current-user-principal>
          <d:href>/DAV/acl/users/User1</d:href>
        </d:current-user-principal>
        <d:principal-URL>
          <d:href>/DAV/acl/users/User1</d:href>
        </d:principal-URL>
        <d:resourcetype>
          <d:collection />
        </d:resourcetype>
      </d:prop>
    </d:propstat>
  </d:response>
</d:multistatus>

Here is the sequence diagram for this request:

Current user principal sequence diagram.

 

  1. Engine calls GetHierarchyItemAsync passing URL specified in the request (typically context path returned during context path discovery). Your implementation must return hierarchy item implementing ICurrentUserPrincipalAsync.
  2. Engine calls ICurrentUserPrincipalAsync.GetCurrentUserPrincipalAsync method. Your implementation must return the logged-in principal implementing ICalendarPrincipalAsync/ IAdderssbookPrincipaAsync. Your principal item will also implement ICalendarDiscoveryAsync / IAddressbookDiscoveryAsync. These interfaces will be used during home-set folders discovery (see below) to query folders containing calendars and address books.
  3. The Engine calls IHierarchyItemAsync.Path on the principal item and returns it o the client.

Typically this request is submitted on the context path folder, but could be submitted on other items too. Below you can see a table listing which item should process this request:

CalDAV clientItems that must process current-user-principal request
iOS Calendar Context path folder
OS X Calendar Context path folder, Principal item
eM Client URL provided when connecting
Mozilla Thunderbird Lightning URL provided when connecting (can be calendar folder URL only)

Features Support Discovery

This request is sent by the CalDAV and CardDAV clients to discover features supported by the server. In the response, at a minimum, your server must provide 1, access-control and calendar-access tokens in the DAV header for CalDAV server or  1, access-control and addressbook for CardDAV server. Below you can see the example of this request and response: 

OPTIONS /DAV/acl/users/User1/
Content-Length: 0
Authorization: Basic ************
 
HTTP/1.1 200 OK
DAV : 1, 3, access-control, addressbook, calendar-access

Here is the sequence diagram for this request for CalDAV server:

 Features support discovery sequence diagram.

  1. The Engine calls GetHierarchyItemAsync passing path specified in the request (typically principal item path returned during current user principal discovery). Your implementation must return hierarchy item implementing ICalendarItem and IAccessControl in case of CalDAV server or IAddressbookItem and IAccessControl in case of CardDAV server. Typically you will return item implementing ICalendarPrincipalAsync/IAddressbookPrincipalAsync and ICalendarDiscoveryAsync/IAddressbookDiscoveryAsync intrefaces that are derived from them.
  2. To form the DAV header the Engine will query interfaces on the item returned and include necessary tokens. The Engine does not call any properties or methods.

Below you can see a table listing what tokens are included depending on interfaces found on the item:

Interface that must be implemented on an itemToken in DAV header
ICalendarItem calendar-access
IAddressbookItem addressbook
IAccessControl access-control
IAppleCalendarAsync calendarserver-sharing
ISchedulingPrincipalAsync
IScheduleInboxFolderAsync
IScheduleOutboxFolderAsync
calendar-auto-schedule

In case of iOS and OS X this request is sent on the principal item. It could be also sent on other items. Below you can see a table listing which item should process this request:

CalDAV clientSent on
iOS Calendar Principal item
OS X Calendar Principal item
eM Client Home-set folder, URL provided when connecting, Principal item
Mozilla Thunderbird Lightning One level above the calendar folder.

Note that you can find the DAV header in other responses too. This is required to support Bynari Collaborator which fails to connect without this header.

Home-Set Folders Discovery

During this request the client application queries the location of calendars and address books available for the loged-in user. Here is the example of such request:

PROPFIND /DAV/acl/users/User1/
Authorization: Basic ************
Content-Type: text/xml
Depth: 0

<?xml version="1.0" encoding="UTF-8"?>
<A:propfind xmlns:A="DAV:">
  <A:prop>
    <B:calendar-home-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
...
  </A:prop>
</A:propfind>
 

HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
DAV: 1, 3, access-control, addressbook, calendar-access
 
<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
  <d:response>
    <d:href>/acl/users/User1</d:href>
    <d:propstat>
      <d:status>HTTP/1.1 200 OK</d:status>
      <d:prop>
        <calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav">
          <d:href>/DAV/calendars/</d:href>
        </calendar-home-set>
...
      </d:prop>
    </d:propstat>
  </d:response>
</d:multistatus>

Here is the sequence diagram for this request for CalDAV server:

Home-set discovery sequence diagram.

Note that home-set is a list of folders that contain your calendars and address books, NOT the calendar and address book folders themselves.

  1. The Engine calls GetHierarchyItemAsync passing principal path returned during current user principal discovery. Your implementation must return principal item implementing ICalendarDiscoveryAsync / IAddressbookDiscoveryAsync.
  2. The Engine calls ICalendarDiscoveryAsync.GetCalendarHomeSetAsync in case of CalDAV server or IAddressbookDiscoveryAsync.GetAddressbookHomeSetAsync in Case of CardDAV server on the returned item. Your implementation must return folders that contain calendars / address books as a descendants of these folders.

Note that even though you can return more than one home-set folder, iOS and OS X will pick the last folder from the list of home-set folders, they does not support more than one home-set folder in the response. iOS and OS X also does not traverse the hierarchy down the home-set folder, they displays only calendars/address books that are children of the home-set folder.

Typically in your application either every user will have its own home-set folder or all calendars of all users will be stored in a single folder, with permissions configured to restrict users to see each other calendars and address books.

Listing of Calendars / Address Books

During this request CalDAV and CardDAV client application lists calendars and address books located down the hierarchy of the home-set folder. The exact scenario in the client UI depends on the client application. The client will either present the list of calendars / address books to be selected for synchronization or will just start synchronizing all found calendars / address books.

Example of request and response:

PROPFIND /DAV/calendars/
Authorization: Basic ************
Content-Type: text/xml
Depth: 1

<?xml version="1.0" encoding="UTF-8"?>
<A:propfind xmlns:A="DAV:">
  <A:prop>
    <A:displayname/>
    <A:resourcetype/>
    <B:supported-calendar-component-set xmlns:B="urn:ietf:params:xml:ns:caldav"/>
    ...
  </A:prop>
</A:propfind>


HTTP/1.1 207 Multi-Status
Content-Type: application/xml; charset=utf-8
DAV: 1, 3, access-control, addressbook, calendar-access

<?xml version="1.0" encoding="utf-8"?>
<d:multistatus xmlns:d="DAV:">
...
  <d:response>
    <d:href>/DAV/calendars/da70b4b7-089c-499f-9a2d-6e6fa82a6e24/</d:href>
    <d:propstat>
      <d:status>HTTP/1.1 200 OK</d:status>
      <d:prop>
        <calendar-description xmlns="urn:ietf:params:xml:ns:caldav">Calendar 1</calendar-description>
        <d:displayname>Cal 1</d:displayname>
        <d:resourcetype>
          <d:collection />
          <calendar xmlns="urn:ietf:params:xml:ns:caldav" />
        </d:resourcetype>
        <supported-calendar-component-set xmlns="urn:ietf:params:xml:ns:caldav">
          <comp name="VEVENT" />
          <comp name="VTODO" />
        </supported-calendar-component-set>
        ...
      </d:prop>
    </d:propstat>
  </d:response>
</d:multistatus>

Here is the sequence diagram for this request for CalDAV server:

Listing of calendars in home set folder sequence diagram.

  1. The Engine calls GetHierarchyItemAsync passing home-set folder path returned during home-set folder discovery. Your implementation must return home-set folder instance that implements IItemCollectionAsync.
  2. The Engine lists home-set folder content calling IItemCollectionAsync.GetChildrenAsync method. Your implementation must return list of child items – typically list of calendar folders each implementing ICalendarFolderAsync in case of CalDAV server or IAddressbookFolderAsync in case of CardDAV server.
  3. The Engine calls IHierarchyItemAsync.Path for each item and returns the list to the client. Depending on the properties requested by the client the Engine can call other properties or methods on each item returned.