CardDAV addressbook-multiget REPORT returns malformed PHOTO field

Issue Description

When fetching a vCard via a plain GET request on the individual resource, the PHOTO property is returned correctly in valid vCard 3.0 syntax:

PHOTO;TYPE=JPEG;ENCODING=b:/9j/4AAQSkZJRgABAQEAkACQAAD/2wBD…

However, when the same contact is fetched via a CARDDAV:addressbook-multiget REPORT (the mechanism used by CardDAV clients like DAVx5 during normal sync), the PHOTO property comes back malformed:

PHOTO;TYPE=JPEG:data:base64,/9j/4AAQSkZJRgABAQEAkACQAAD/2wBD…

This is neither valid vCard 3.0 nor vCard 4.0 syntax — it mixes the old TYPE=JPEG parameter (v3 style) with a data:base64, URI-style prefix (v4 style), but without a proper MIME type in the data URI (should be data:image/jpeg;base64,… for v4.0) and with a stray escaped comma (,) that shouldn’t be there in either format.

Impact: CardDAV clients (confirmed with DAVx5/ez-vcard) cannot parse this malformed field and silently drop the photo — no error is raised on either the client or server side, making this hard to notice without inspecting raw HTTP traffic.

Expected Behavior

The addressbook-multiget REPORT should return the PHOTO property either unchanged (as the plain GET does), or correctly re-serialized in valid vCard 3.0 or 4.0 syntax — not a mix of both.

Actual Behavior

The PHOTO property comes back malformed:
PHOTO;TYPE=JPEG:data:base64,/9j/4AAQSkZJRgABAQEAkACQAAD/2wBD…

Reproduction Steps

  1. Store a contact with an embedded JPEG photo (PHOTO;ENCODING=b;TYPE=JPEG:) in a Stalwart address book (in my case, migrated from Nextcloud via vdirsyncer)
  2. Fetch the resource directly via GET /dav/card///.vcf → PHOTO is correct/valid
  3. Sync the same address book with a CardDAV client that uses addressbook-multiget (e.g. DAVx5 with verbose logging enabled) → PHOTO in the REPORT response is malformed as shown above
  4. Contact photo does not appear on the client device, despite no sync errors being reported

Relevant Log Output

DAVx5 verbose log snippet (see last line):
07-27 11:32:09.544 10901 12081 I .davdroid.sync.ContactsSyncManager: Processing CardDAV resource 82ecc683-5b72-472f-962f-0df1f39c254d.vcf
07-27 11:32:09.545 10901 12211 V .davdroid.network.HttpClientBuilder:
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: <D:multistatus xmlns:D=“DAV:” xmlns:B=“urn:ietf:params:xml:ns:carddav”><D:response><D:href>/dav/card/uli%40domain.de/kontakte/9222c463-057f-485d-b184-c445d0b9d6fc.vcf</D:href><D:propstat><D:prop><D:getcontenttype>text/vcard</D:getcontenttype><D:getetag>“2715294701”</D:getetag><B:address-data><![CDATA[BEGIN:VCARD
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: VERSION:4.0
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: N:Lawinenlage;Schweiz;;;
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: FN:Lawinenlage, Schweiz
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: CLASS:PUBLIC
07-27 11:32:09.546 10901 12211 V .davdroid.network.HttpClientBuilder: PHOTO;TYPE=JPEG:data:base64,/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAMCAgICAgMCAgI

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Fixed in v0.16.15 (yet to be released).

Works. Many thanks. :+1: