Let’s have a look at what is happening:
First, DAVdroid 0.8.1 requests a list of all members from the server (OwnCloud 8.1.0):
http-outgoing-0 >> "PROPFIND /owncloud/remote.php/carddav/addressbooks/test/kontakte/ HTTP/1.1[\r][\n]"
http-outgoing-0 << "<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:response><d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/</d:href><d:propstat><d:prop><x3:getctag xmlns:x3="http://calendarserver.org/ns/">1437219316</x3:getctag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat><d:propstat><d:prop><d:getetag/></d:prop><d:status>HTTP/1.1 404 Not Found</d:status></d:propstat></d:response><d:response><d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf%2540foo.bar.vcf.vcf</d:href><d:propstat><d:prop><d:getetag>"28c05d52626ce5084a1f991b438ac41d"</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat><d:propstat><d:prop><x3:getctag xmlns:x3="http://calendarserver.org/ns/"/></d:prop><d:status>HTTP/1.1 404 Not Found</d:status></d:propstat></d:response><d:response><d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed%2540foo.bar.vcf.vcf</d:href><d:propstat><d:prop><d:getetag>"f144eaeea375650281aeeff2929788bd"</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat><d:propstat><d:prop><x3:getctag xmlns:x3="http://calendarserver.org/ns/"/></d:prop><d:status>HTTP/1.1 404 Not Found</d:status></d:propstat></d:response></d:multistatus>[\n]"
The pretty-printed response is:
<d:response>
<d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf%2540foo.bar.vcf.vcf</d:href>
<d:propstat>
<d:prop>
<d:getetag>"28c05d52626ce5084a1f991b438ac41d"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
<d:propstat>
<d:prop>
<x3:getctag xmlns:x3="http://calendarserver.org/ns/"/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
<d:response>
<d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed%2540foo.bar.vcf.vcf</d:href>
<d:propstat>
<d:prop>
<d:getetag>"f144eaeea375650281aeeff2929788bd"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
<d:propstat>
<d:prop>
<x3:getctag xmlns:x3="http://calendarserver.org/ns/"/>
</d:prop>
<d:status>HTTP/1.1 404 Not Found</d:status>
</d:propstat>
</d:response>
</d:multistatus>
So, there are two (new) members:
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf%2540foo.bar.vcf.vcf
(URL-encoded), i.e. member name 20140623T46782.acdf%40foo.bar.vcf.vcf
(unencoded)
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed%2540foo.bar.vcf.vcf
(URL-encoded), i.e. member name 20140623T00123.086fed%40foo.bar.vcf.vcf
(unencoded)
DAVdroid requests those two resources using REPORT addressbook-multiget
:
http-outgoing-0 >> "REPORT /owncloud/remote.php/carddav/addressbooks/test/kontakte/ HTTP/1.1[\r][\n]"
…
http-outgoing-0 >> "<CD:addressbook-multiget xmlns:CD="urn:ietf:params:xml:ns:carddav" xmlns="DAV:">[\n]"
http-outgoing-0 >> " <prop>[\n]"
http-outgoing-0 >> " <CD:address-data content-type="text/vcard" version="4.0"/>[\n]"
http-outgoing-0 >> " <getetag/>[\n]"
http-outgoing-0 >> " </prop>[\n]"
http-outgoing-0 >> " <href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed%2540foo.bar.vcf.vcf</href>[\n]"
http-outgoing-0 >> " <href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf%2540foo.bar.vcf.vcf</href>[\n]"
http-outgoing-0 >> "</CD:addressbook-multiget>"
OwnCloud returns:
http-outgoing-0 << "HTTP/1.1 207 Multi-Status[\r][\n]"
…
http-outgoing-0 << "<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav"><d:response><d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed@foo.bar.vcf.vcf</d:href><d:propstat><d:prop><card:address-data>BEGIN:VCARD [\n]"
http-outgoing-0 << "VERSION:4.0 [\n]"
http-outgoing-0 << "PRODID:-//Sabre//Sabre VObject 3.4.2//EN [\n]"
http-outgoing-0 << "FN:BAR [\n]"
http-outgoing-0 << "N:;BAR;;; [\n]"
http-outgoing-0 << "UID:20140623T00123.086fed@foo.bar.vcf [\n]"
http-outgoing-0 << "REV:2015-07-18T11:35:15+00:00 [\n]"
http-outgoing-0 << "END:VCARD [\n]"
http-outgoing-0 << "</card:address-data><d:getetag>"f144eaeea375650281aeeff2929788bd"</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response><d:response><d:href>/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T4678"
http-outgoing-0 << "2.acdf@foo.bar.vcf.vcf</d:href><d:propstat><d:prop><card:address-data>BEGIN:VCARD [\n]"
http-outgoing-0 << "VERSION:4.0 [\n]"
http-outgoing-0 << "PRODID:-//Sabre//Sabre VObject 3.4.2//EN [\n]"
http-outgoing-0 << "FN:FOO [\n]"
http-outgoing-0 << "N:;FOO;;; [\n]"
http-outgoing-0 << "UID:20140623T46782.acdf@foo.bar.vcf [\n]"
http-outgoing-0 << "REV:2015-07-18T11:35:15+00:00 [\n]"
http-outgoing-0 << "END:VCARD [\n]"
http-outgoing-0 << "</card:address-data><d:getetag>"28c05d52626ce5084a1f991b438ac41d"</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>[\n]"
So, if you have a look, you’ll see that DAVdroid requested the resources
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed%2540foo.bar.vcf.vcf
(URL-encoded), i.e. the member named 20140623T00123.086fed%40foo.bar.vcf.vcf
(unencoded [%25 decodes to “%”]) and
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf%2540foo.bar.vcf.vcf
(URL-encoded) i.e. the member named 20140623T46782.acdf%40foo.bar.vcf.vcf
(unencoded)
However, OwnCloud returned
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T00123.086fed@foo.bar.vcf.vcf
and
/owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf@foo.bar.vcf.vcf
This is clearly not what was requested and OwnCloud does one additional (and erroneous) level of encoding (as DAVdroid did earlier, by the way).
So, the sync process happens like this:
- In response to PROPFIND, OwnCloud tells DAVdroid that there are two members:
20140623T00123.086fed%40foo.bar.vcf.vcf
(unencoded) and
20140623T46782.acdf%40foo.bar.vcf.vcf
(unencoded)
- DAVdroid requests these two (new) members from OwnCloud and adds all resources from the response. The response however
does not contain the requested members, but 20140623T00123.086fed@foo.bar.vcf.vcf
and /owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf@foo.bar.vcf.vcf
.
These resources are added nevertheless (because DAVdroid expects them to be the requested ones, and it has to trust the server-given resource names to distinguish between the resources).
- In the last step (“delete every local non-dirty contacts that are not on the server anymore”), DAVdroid deletes everything except
20140623T00123.086fed%40foo.bar.vcf.vcf
(unencoded) and 20140623T46782.acdf%40foo.bar.vcf.vcf
, so the recently fetched resources are deleted immediately (because their names are 20140623T00123.086fed@foo.bar.vcf.vcf
and /owncloud/remote.php/carddav/addressbooks/test/kontakte/20140623T46782.acdf@foo.bar.vcf.vcf
).
Why does it work when there’s only one new record? In this case, DAVdroid uses a GET
request instead of REPORT addressbook-multiget
. The response is directly the VCard and the member name is used from the requested URI, so there’s no possibility for OwnCloud to return an erroneous file name.
Why did it work with previous DAVdroid versions? Well, DAVdroid did the same thing (double-decode URLs) earlier, too. This has been fixed to close #482. I guess it was a coincidence that this DAVdroid bug was just the the opposite of the corresponding OwnCloud bug so that it seemed to work (while it was bugged on both sides).
Do you agree?