4

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>
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466

1 Answers1

4

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.

Community
  • 1
  • 1
Evert
  • 93,428
  • 18
  • 118
  • 189
  • Thanks for responding. I do use Depth = 1 and was able to confirm that all users have a default calendar. I've edited my question to show more of my code. Thanks for any help. – Jonathan Wood Jul 26 '12 at 16:17
  • I would love to see *exactly* what you are sending to the server, and getting back.. in terms of xml. I'm the author of a CalDAV server (SabreDAV), so the request/response bodies will be much more helpful for me :) – Evert Jul 26 '12 at 16:38
  • Thanks for responding. I've added the exact contents at the end of my question. I was expecting the second response to be the same as the first. (Also, if we want to move from DCS to SabreDAV, what is that like? Any tools needed? – Jonathan Wood Jul 26 '12 at 17:14
  • I can tell it's DCS. The thing that stands out the most is the base url. The first starts with /calendars/users, the second /calendars/__uids__. This would make me wonder if you correctly obtained the calendar-home-set. The calendar-home-set can be a list of hrefs, so maybe you grabbed just the first? Also.. if you correctly implement the CalDAV protocol, switching to SabreDAV should be easily.. Data migration is harder though – Evert Jul 26 '12 at 18:04
  • And so far you are on the right path, but yea.. the last thing I'd ask is.. can you show the request and response you use for obtaining calendar-home-set. – Evert Jul 26 '12 at 18:04
  • Thanks again. Data migration is no issue since this project has not yet been deployed. I've added the request and response I used to get the home calendar set. This was worked out largely through guessing and trial and error. So I could easily have something wrong. – Jonathan Wood Jul 26 '12 at 18:16
  • I think I would suggest not using the principal-match report, but use the current-user-principal property instead. You can just do a propfind anywhere, and get the current principal url. Using that, do another propfind for calendar-home-set. This is how pretty much any CalDAV client does discovery. – Evert Jul 26 '12 at 19:59
  • I'm trying to figure out the correct syntax for this. Looks like the [CalDav specs](http://www.webdav.org/specs/rfc4791.html) don't mention anything about `current-user-principal`. – Jonathan Wood Jul 26 '12 at 20:42
  • It was added in a later rfc, because of issues with principal-match. Check the url I linked to in my answer. – Evert Jul 26 '12 at 20:55
  • Thanks for the additional help. I guess you just have to understand this stuff inside and out to know what's needed. For me, guessing at the correct syntax (I'm not seeing examples requests at the link you posted) seems very painful. At any rate, I finally seemed to have got this working. However, the result was "/calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9", which appears to be the same path I've was using originally. – Jonathan Wood Jul 26 '12 at 21:17
  • Was my hunch correct? How did you end up discovering this url? The best way (imho) to get a better feeling for the protocol, is to see how other (proper) clients do this. – Evert Jul 26 '12 at 23:42
  • Which URL? I figured out the `/calendars/users/test/` URL by trial and error with a browser. I had found the `/calendars/__uids__/d817aaec-7d24-5b38-bc2f-6369da72cdd9` URL using a `principal-match` request to obtain the current user's "calendar-home-set" path. The approach you suggested gives me the same URL. But I'm unable to get the list of calendars available for this URL. Yes, I would give my left leg to see some sample C or C# client code that actually works with DCS! All searches to find that have been unsuccessful. (I've found several C# examples that just weren't right.) – Jonathan Wood Jul 26 '12 at 23:57
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/14493/discussion-between-evert-and-jonathan-wood) – Evert Jul 27 '12 at 00:12