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:
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)
- Current User Principal Discovery
- Features Support Discovery
- Home-Set Folders Discovery
- Listing of Calendars / Address Books
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 ContextAsync 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 IHierarchyItem from GetHierarchyItemAsync call instead of returning null. Below you can see the sequence diagram for these request:
The returned IHierarchyItem 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:
- Engine calls GetHierarchyItem passing URL specified in the request (typically context path returned during context path discovery). Your implementation must return hierarchy item implementing ICurrentUserPrincipal.
- Engine calls ICurrentUserPrincipal.GetCurrentUserPrincipalAsync method. Your implementation must return the logged-in principal implementing ICalendarPrincipal/ IAdderssbookPrincipal. Your principal item will also implement ICalendarDiscovery / IAddressbookDiscovery. These interfaces will be used during home-set folders discovery (see below) to query folders containing calendars and address books.
- The Engine calls IHierarchyItem.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 client | Items 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:
- The Engine calls GetHierarchyItem 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 ICalendarPrincipal/IAddressbookPrincipal and ICalendarDiscovery/IAddressbookDiscovery intrefaces that are derived from them.
- 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 item | Token in DAV header |
---|---|
ICalendarItem | calendar-access |
IAddressbookItem | addressbook |
IAccessControl | access-control |
IAppleCalendar | calendarserver-sharing |
ISchedulingPrincipal IScheduleInboxFolder IScheduleOutboxFolder |
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 client | Sent 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:
Note that home-set is a list of folders that contain your calendars and address books, NOT the calendar and address book folders themselves.
- The Engine calls GetHierarchyItemAsync passing principal path returned during current user principal discovery. Your implementation must return principal item implementing ICalendarDiscovery / IAddressbookDiscovery.
- The Engine calls ICalendarDiscovery.GetCalendarHomeSetAsync in case of CalDAV server or IAddressbookDiscovery.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:
- 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 IItemCollection.
- The Engine lists home-set folder content calling IItemCollection.GetChildrenAsync method. Your implementation must return list of child items – typically list of calendar folders each implementing ICalendarFolder in case of CalDAV server or IAddressbookFolder in case of CardDAV server.
- The Engine calls IHierarchyItem.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.