Sören Kuklau
Sören Kuklau

Reputation: 19930

Custom CardDAV server: clients (especially iOS) don't seem to request actual contacts

I'm trying to implement a custom CardDAV server on top of NWebDAV and ASP.NET Core 3.1, using an existing database as the actual data source for contacts. (NWebDAV seems to have excellent support for basic WebDAV notions like responding to PROPFIND. Out of the box, running its sample project to serve your home directory seems to work great. OTOH, stuff like REPORT is apparently missing, and I'm not sure yet how much work that will be.)

I'm using iOS 13 as my main testing client.

I have successfully gotten it to:

So I know it's parsing my PROPFIND responses to a point. But afterwards, nothing. From what I understand, it should issue some kind of REPORT request against my server, either to first do a complete sync, or — when typing something into the search field — to query particular contacts. It never seems to try.

I give it something like:

<addressbook-home-set xmlns="urn:ietf:params:xml:ns:carddav">
  <D:href>/addressbooks/sample/108</D:href>
  <D:href>/addressbooks/sample/109</D:href>
</addressbook-home-set>

And it issues a PROPFIND request against each of those address books, like so:

<?xml version="1.0" encoding="utf-8"?>
<A:propfind xmlns:A="DAV:">
  <A:prop>
    <A:add-member />
    <E:bulk-requests xmlns:E="http://me.com/_namespace/" />
    <A:current-user-privilege-set />
    <A:displayname />
    <E:guardian-restricted xmlns:E="http://me.com/_namespace/" />
    <D:max-image-size xmlns:D="urn:ietf:params:xml:ns:carddav" />
    <D:max-resource-size xmlns:D="urn:ietf:params:xml:ns:carddav" />
    <C:me-card xmlns:C="http://calendarserver.org/ns/" />
    <A:owner />
    <C:push-transports xmlns:C="http://calendarserver.org/ns/" />
    <C:pushkey xmlns:C="http://calendarserver.org/ns/" />
    <A:quota-available-bytes />
    <A:quota-used-bytes />
    <A:resource-id />
    <A:resourcetype />
    <A:supported-report-set />
    <A:sync-token />
  </A:prop>
</A:propfind>

I respond to this as follows:

<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:">
  <D:response>
    <D:href>http://localhost:55946/addressbooks/sample/108</D:href>
    <D:propstat>
      <D:prop>
        <D:add-member />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}add-member is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <bulk-requests xmlns="http://me.com/_namespace/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://me.com/_namespace/}bulk-requests is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <guardian-restricted xmlns="http://me.com/_namespace/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://me.com/_namespace/}guardian-restricted is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <max-image-size xmlns="urn:ietf:params:xml:ns:carddav" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {urn:ietf:params:xml:ns:carddav}max-image-size is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <max-resource-size xmlns="urn:ietf:params:xml:ns:carddav" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {urn:ietf:params:xml:ns:carddav}max-resource-size is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:owner />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}owner is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <push-transports xmlns="http://calendarserver.org/ns/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://calendarserver.org/ns/}push-transports is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <pushkey xmlns="http://calendarserver.org/ns/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://calendarserver.org/ns/}pushkey is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:quota-available-bytes />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}quota-available-bytes is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:quota-used-bytes />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}quota-used-bytes is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:resource-id />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}resource-id is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:sync-token />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}sync-token is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:current-user-privilege-set>
          <D:privilege>
            <D:read />
          </D:privilege>
          <D:privilege>
            <D:read-acl />
          </D:privilege>
          <D:privilege>
            <D:read-current-user-privilege-set />
          </D:privilege>
          <D:privilege>
            <D:write />
          </D:privilege>
          <D:privilege>
            <D:bind />
          </D:privilege>
        </D:current-user-privilege-set>
        <D:displayname>SampleContacts108</D:displayname>
        <me-card xmlns="http://calendarserver.org/ns/">
          <D:href>/addressbooks/sample/108/contact-49191/</D:href>
        </me-card>
        <D:resourcetype>
          <D:collection />
          <addressbook xmlns="urn:ietf:params:xml:ns:carddav" />
        </D:resourcetype>
        <D:supported-report-set>
          <D:supported-report>
            <addressbook-query xmlns="urn:ietf:params:xml:ns:carddav" />
          </D:supported-report>
          <D:supported-report>
            <addressbook-multiget xmlns="urn:ietf:params:xml:ns:carddav" />
          </D:supported-report>
          <D:supported-report>
            <D:expand-property />
          </D:supported-report>
        </D:supported-report-set>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>http://localhost:55946/addressbooks/sample/108/Bar,Foo</D:href>
    <D:propstat>
      <D:prop>
        <D:add-member />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}add-member is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <bulk-requests xmlns="http://me.com/_namespace/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://me.com/_namespace/}bulk-requests is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:current-user-privilege-set />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}current-user-privilege-set is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <guardian-restricted xmlns="http://me.com/_namespace/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://me.com/_namespace/}guardian-restricted is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <max-image-size xmlns="urn:ietf:params:xml:ns:carddav" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {urn:ietf:params:xml:ns:carddav}max-image-size is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <max-resource-size xmlns="urn:ietf:params:xml:ns:carddav" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {urn:ietf:params:xml:ns:carddav}max-resource-size is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <me-card xmlns="http://calendarserver.org/ns/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://calendarserver.org/ns/}me-card is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:owner />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}owner is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <push-transports xmlns="http://calendarserver.org/ns/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://calendarserver.org/ns/}push-transports is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <pushkey xmlns="http://calendarserver.org/ns/" />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {http://calendarserver.org/ns/}pushkey is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:quota-available-bytes />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}quota-available-bytes is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:quota-used-bytes />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}quota-used-bytes is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:resource-id />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}resource-id is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:resourcetype />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}resourcetype is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:supported-report-set />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}supported-report-set is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:sync-token />
      </D:prop>
      <D:status>HTTP/1.1 404 Not Found</D:status>
      <D:responsedescription>Property {DAV:}sync-token is not supported.</D:responsedescription>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <D:displayname>Bar, Foo</D:displayname>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

(I've implemented me-card because someone said not doing so might crash iOS Contacts, and in hopes it would prompt iOS to at least try to fetch the me card. But alas.)

This response includes the metadata for both the address book itself, and a sample contact within. I'm not sure if that's correct, but leaving that contact out doesn't seem to change anything.

I'm supporting the reports addessbook-query and addressbook-multiget. My reading of 8. Address Book Reports in the spec is that this should be enough to comply with the standard.

What puzzles me is that there's just… nothing. There's no user-facing error, there's no further request, etc.

I did find something in Console:

com.apple.dataaccess    debug   10:52:13.523530+0200    dataaccessd <private>: There are no server side items to grab, so I'm outta here

But I don't know if that refers to this account (could be a different server). If it does, I'd love to know what brings iOS to this conclusion?

So, my main question is: do I have the basic assumption right that the CardDAV client should eventually issue a REPORT request to query contacts?

Upvotes: 0

Views: 421

Answers (1)

Evert
Evert

Reputation: 99485

There's several things missing if you're building a standard CardDAV server. If you're just looking to build the specific features that Addressbook supports, I'd probably get started with implementing supported-report-set, return all the relevant CardDAV reports and see if the client makes more requests.

If I recall correctly the client will at least want multiget if webdav-sync isn't supported, but the last time I've built a server is 10 years ago.

Upvotes: 0

Related Questions