Getting User's List of Calendars from CalDAV

Jonathan Wood picture Jonathan Wood · Jul 24, 2012 · Viewed 7.4k times · Source

I'm trying to get a list of calendars owned by the current user from a CalDAV server.

I was able to obtain this information using our initial test account with the following request:

PROPFIND /calendars/users/test/
<propfind xmlns='DAV:'>
    <allprop/>
</propfind>

The result is a <multistatus> element with several <response> elements. If I extract the elements where resourcetype is calendar, I get my list of calendars.

However, when we added additional users, this produces a "Not Found" error, so I instead used a "principal-match" request to obtain the current user's "calendar-home-set" path.

That path looks like /d817aaec-7d24-5b38-bc2f-6369da72cdd9/. So I tried the above request with this path. Now the result is a <multistatus> element with only a single <response> element. It does not contain any calendars. The first response is exactly like the first response from in my original request.

I can't for the life of me figure out the magic sauce that would allow me to get the users list of calendars in all cases.

EDIT:

Here's some of my code. The "/calendars/users/test/" URL I tried initially is returned from GetRequestAddress(). My second case where I used principal-match to get the calendar path used CalendarHomeSet (both shown below).

Headers["Depth"] = "1";
//XElement xmlResult = UploadXml(GetRequestAddress(), // Alternatively, CalendarHomeSet
    method: CalDavMethod.PropertyFind,
    xml: XDocument.Parse("<propfind xmlns='DAV:'>" +
        "<allprop/>" +
        "</propfind>").Root);

private string GetRequestAddress(string calendarHRef = null, string resource = null)
{
    string path = calendarHRef;
    if (String.IsNullOrWhiteSpace(path))
        path = String.Format("/calendars/users/{0}/", UserName);
    if (!String.IsNullOrWhiteSpace(resource))
        path = Path.Combine(path, resource);
    return path;
}

/// <summary>
/// Gets/sets the path to the parent folder of any calendar subfolders
/// owned by the current user.
/// </summary>
public string CalendarHomeSet
{
    get
    {
        if (calendarHomeSet == null)
        {
            Headers["Depth"] = "0";

            XElement xmlResult = UploadXml(String.Format("/principals/users/{0}/", UserName),
                method: "REPORT",
                xml: XDocument.Parse(XmlHeader +
                    "<D:principal-match xmlns:D=\"DAV:\">" +
                        "<D:self/>" +
                        "<D:prop>" +
                            "<C:calendar-home-set xmlns:C=\"urn:ietf:params:xml:ns:caldav\"/>" +
                        "</D:prop>" +
                    "</D:principal-match>").Root);
            //
            XElement el = xmlResult.Descendants(CalDavXmlns + "calendar-home-set").FirstOrDefault();
            if (el != null)
            {
                calendarHomeSet = (string)el;
                if (!calendarHomeSet.EndsWith("/"))
                    calendarHomeSet += '/';
            }
        }
        return calendarHomeSet;
    }

    set
    {
        calendarHomeSet = value;
    }
}

private string calendarHomeSet = null;

SECOND EDIT:

Here are some more details about the exact contents of my requests and responses. In the first one, notice that the results include a collection followed by two calendar collections.

PROPFIND /calendars/users/test/

<propfind xmlns="DAV:">
  <allprop />
</propfind>

Response:

<multistatus xmlns="DAV:">
  <response>
    <href>/calendars/users/test/</href>
    <propstat>
      <prop>
        <getetag>"4293-1000-4FFC9A16"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <displayname>Test User</displayname>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
        </resourcetype>
        <getcontentlength />
        <getlastmodified>Tue, 10 Jul 2012 21:09:42 GMT</getlastmodified>
        <creationdate>2012-07-10T21:09:42Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">CalendarHomeFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
  <response>
    <href>/calendars/users/test/calendar/</href>
    <propstat>
      <prop>
        <getetag>"42DB-1000-50108ABC"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <calendar-order xmlns="http://apple.com/ns/ical/">1</calendar-order>
        <displayname>calendar</displayname>
        <calendar-color xmlns="http://apple.com/ns/ical/">#F64F00FF</calendar-color>
        <getctag xmlns="http://calendarserver.org/ns/">2012-07-26 00:09:32.361284</getctag>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
          <calendar xmlns="urn:ietf:params:xml:ns:caldav" />
        </resourcetype>
        <getcontentlength />
        <schedule-calendar-transp xmlns="urn:ietf:params:xml:ns:caldav">
          <opaque />
        </schedule-calendar-transp>
        <getlastmodified>Thu, 26 Jul 2012 00:09:32 GMT</getlastmodified>
        <creationdate>2012-07-26T00:09:32Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">CalDAVFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
  <response>
    <href>/calendars/users/test/8C1F393E-04E8-428A-819A-933C3A9338AD/</href>
    <propstat>
      <prop>
        <getetag>"43AA-1000-50079D1C"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <calendar-order xmlns="http://apple.com/ns/ical/">0</calendar-order>
        <displayname>Jon Wood Calendar</displayname>
        <calendar-color xmlns="http://apple.com/ns/ical/">#711a76</calendar-color>
        <getctag xmlns="http://calendarserver.org/ns/">2012-07-19 05:37:32.673835</getctag>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
          <calendar xmlns="urn:ietf:params:xml:ns:caldav" />
        </resourcetype>
        <getcontentlength />
        <schedule-calendar-transp xmlns="urn:ietf:params:xml:ns:caldav">
          <transparent />
        </schedule-calendar-transp>
        <getlastmodified>Thu, 19 Jul 2012 05:37:32 GMT</getlastmodified>
        <creationdate>2012-07-19T05:37:32Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">CalDAVFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
  <response>
    <href>/calendars/users/test/outbox/</href>
    <propstat>
      <prop>
        <getetag>"D4E-1000-4FFB15AF"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <displayname>outbox</displayname>
        <getctag xmlns="http://calendarserver.org/ns/">2012-07-09 17:32:31.950308</getctag>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
          <schedule-outbox xmlns="urn:ietf:params:xml:ns:caldav" />
        </resourcetype>
        <getcontentlength />
        <getlastmodified>Mon, 09 Jul 2012 17:32:31 GMT</getlastmodified>
        <creationdate>2012-07-09T17:32:31Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">ScheduleOutboxFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
  <response>
    <href>/calendars/users/test/freebusy</href>
    <propstat>
      <prop>
        <getetag>"D7D-0-4FFC3F7C"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <displayname>freebusy</displayname>
        <getcontenttype>text/plain</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <free-busy-url xmlns="http://calendarserver.org/ns/" />
        </resourcetype>
        <getcontentlength>0</getcontentlength>
        <getlastmodified>Tue, 10 Jul 2012 14:43:08 GMT</getlastmodified>
        <creationdate>2012-07-10T14:43:08Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">FreeBusyURLFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
  <response>
    <href>/calendars/users/test/inbox/</href>
    <propstat>
      <prop>
        <getetag>"42FB-1000-4FF21C60"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <displayname>inbox</displayname>
        <getctag xmlns="http://calendarserver.org/ns/">2012-07-02 22:10:40.527683</getctag>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
          <schedule-inbox xmlns="urn:ietf:params:xml:ns:caldav" />
        </resourcetype>
        <getcontentlength />
        <schedule-default-calendar-URL xmlns="urn:ietf:params:xml:ns:caldav">
          <href xmlns="DAV:">/calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/calendar</href>
        </schedule-default-calendar-URL>
        <getlastmodified>Mon, 02 Jul 2012 22:10:40 GMT</getlastmodified>
        <creationdate>2012-07-02T22:10:40Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">ScheduleInboxFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
</multistatus>

Next, I tried the same request except against a different URL. This time, I used a URL I obtained by querying the principal. Now the results still contain that initial collection, but they don't contain the calendars.

PROPFIND /calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/ (CalendarHomeSet)

<propfind xmlns="DAV:">
  <allprop />
</propfind>

Response:

<multistatus xmlns="DAV:">
  <response>
    <href>/calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
    <propstat>
      <prop>
        <getetag>"4293-1000-4FFC9A16"</getetag>
        <current-user-principal>
          <href>/principals/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9/</href>
        </current-user-principal>
        <displayname>Test User</displayname>
        <getcontenttype>httpd/unix-directory</getcontenttype>
        <supportedlock>
          <lockentry>
            <lockscope>
              <exclusive />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
          <lockentry>
            <lockscope>
              <shared />
            </lockscope>
            <locktype>
              <write />
            </locktype>
          </lockentry>
        </supportedlock>
        <resourcetype>
          <collection />
        </resourcetype>
        <getcontentlength />
        <getlastmodified>Tue, 10 Jul 2012 21:09:42 GMT</getlastmodified>
        <creationdate>2012-07-10T21:09:42Z</creationdate>
        <resource-class xmlns="http://twistedmatrix.com/xml_namespace/dav/">CalendarHomeFile</resource-class>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
</multistatus>

THIRD EDIT:

And here's the request and response I used to get the calendar home set:

REPORT /principals/users/test/

<D:principal-match xmlns:D="DAV:">
  <D:self />
  <D:prop>
    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav" />
  </D:prop>
</D:principal-match>

Response:

<multistatus xmlns="DAV:">
  <response>
    <href>/principals/users/test/</href>
    <propstat>
      <prop>
        <calendar-home-set xmlns="urn:ietf:params:xml:ns:caldav">
          <href xmlns="DAV:">/calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9</href>
        </calendar-home-set>
      </prop>
      <status>HTTP/1.1 200 OK</status>
    </propstat>
  </response>
</multistatus>

Answer

Evert picture Evert · Jul 26, 2012

Two things I can think of right now:

  1. Are you specifying the Depth: 1 header?
  2. Do the new users actually have calendars? The list may simply be empty for new users.

If those pointers don't help, you should show the full requests and responses.

EDIT

This is how you should typically do discovery in CalDAV.

  1. Do a PROPFIND on the url the user supplied, requesting {DAV:}current-user-principal.
  2. Using this url, you do a PROPFIND to find out more information about the user. Here, you should typically request for the calendar-home-set property in the caldav namespace.
  3. Then, using the calendar-home-set, do a PROPFIND (depth: 1) to find the calendars.

I have a feeling that because you do a principal-match, and don't use current-user-principal; this is going a bit wrong. But I'm not fully sure. My hunch is simply that your detected calendar-home-set is wrong.