diff --git a/CHANGELOG.md b/CHANGELOG.md index 260fe17e..1b52985d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - Fix: Internal server error with PHP8 when searching address fields of contacts (Fixes: #410) - Fix: Assertion failure in DelayedPhotoLoader (Fixes: #404) +- Fixes for better handling of incoming vCard4 (Fixes: #411) + - Handle data-URI-style inline PHOTO as used in vCard4 + - Use VCard conversion to handle v4 properties such as KIND=group for which extensions are used in v3 vCards ## Version 5.0.0-beta1 (to 4.4.4) diff --git a/src/DataConversion.php b/src/DataConversion.php index d4359bfc..dd364940 100644 --- a/src/DataConversion.php +++ b/src/DataConversion.php @@ -319,6 +319,11 @@ public function isMultivalueProperty(string $attrname): bool */ public function toRoundcube(VCard $vcard, AddressbookCollection $davAbook): array { + // in case our input is not a v3 vcard, first convert it to one + if ($vcard->getDocumentType() != VObject\Document::VCARD30) { + $vcard = $vcard->convert(VObject\Document::VCARD30); + } + $save_data = [ // DEFAULTS 'kind' => 'individual', @@ -494,9 +499,16 @@ public function fromRoundcube(array $save_data, ?VCard $vcard = null): VCard $save_data["name"] = $this->composeDisplayname($save_data); } - if (!isset($vcard)) { + if (isset($vcard)) { + $vcardVersion = $vcard->getDocumentType(); + if ($vcardVersion != VObject\Document::VCARD30) { + $vcard4 = $vcard; + $vcard = $vcard->convert(VObject\Document::VCARD30); + } + } else { // create fresh minimal vcard $vcard = new VObject\Component\VCard(['VERSION' => '3.0']); + $vcardVersion = VObject\Document::VCARD30; } // set product @@ -522,6 +534,21 @@ public function fromRoundcube(array $save_data, ?VCard $vcard = null): VCard $this->setSingleValueProperties($save_data, $vcard); $this->setMultiValueProperties($save_data, $vcard); + // if the original vcard was version 4, convert it back to that version + if ($vcardVersion == VObject\Document::VCARD40) { + // XXX Temporary workarounds for sabre-io/vobject#602 BEGIN + // 1) If the photo was unchanged, preserve the original vcard's property to not lose the mimetype + $vcard = $vcard->convert(VObject\Document::VCARD40); + if (isset($vcard4->PHOTO) && !isset($save_data['photo'])) { + $vcard->PHOTO = $vcard4->PHOTO; + } + + // 2) Drop X-ADDRESSBOOKSERVER-KIND property; for KIND=group, it has been converted, for KIND=individual it + // it has been retained, but since it is the default we can simply drop it. + unset($vcard->{'X-ADDRESSBOOKSERVER-KIND'}); + // XXX Temporary workarounds for sabre-io/vobject#602 END + } + return $vcard; } @@ -608,6 +635,20 @@ private static function setPhotoProperty(VCard $vcard, string $photoData): void if (isset($vcard->PHOTO)) { $vcard->PHOTO['ENCODING'] = 'b'; $vcard->PHOTO['VALUE'] = 'binary'; + + if (function_exists('getimagesizefromstring')) { + $typemap = [ + IMAGETYPE_JPEG => 'JPEG', + IMAGETYPE_GIF => 'GIF', + IMAGETYPE_PNG => 'PNG', + ]; + $imginfo = getimagesizefromstring($photoData); + if ($imginfo !== false && isset($imginfo[2]) && is_int($imginfo[2])) { + if (key_exists($imginfo[2], $typemap)) { + $vcard->PHOTO['TYPE'] = $typemap[$imginfo[2]]; + } + } + } } } diff --git a/tests/Unit/Utils.php b/tests/Unit/Utils.php index 07199536..d91eb9f3 100644 --- a/tests/Unit/Utils.php +++ b/tests/Unit/Utils.php @@ -119,7 +119,7 @@ public static function comparePhoto(string $pExpStr, string $pRcStr): void TestCase::assertTrue(function_exists('gd_info'), "php-gd required"); // shortcut that also covers URI - if identical strings, save the comparison - if (empty($pExpStr) || empty($pRcStr) || str_contains($pExpStr, "http")) { + if (empty($pExpStr) || empty($pRcStr) || str_contains($pExpStr, "http") || str_contains($pExpStr, "data:")) { TestCase::assertSame($pExpStr, $pRcStr, "PHOTO comparison on URI value failed"); return; } diff --git a/tests/Unit/data/vcardCreate/InlinePhoto.vcf b/tests/Unit/data/vcardCreate/InlinePhoto.vcf index cc89c4ca..265896e9 100644 --- a/tests/Unit/data/vcardCreate/InlinePhoto.vcf +++ b/tests/Unit/data/vcardCreate/InlinePhoto.vcf @@ -6,7 +6,7 @@ FN:Max Mustermann EMAIL:special@maxmu.de PRODID:-//Apple Inc.//iCloud Web Address Book 2018B71//EN REV:2020-10-03T10:30:20Z -PHOTO;ENCODING=b;VALUE=binary: +PHOTO;ENCODING=b;VALUE=binary;TYPE=JPEG: /9j/4AAQSkZJRgABAQEASABIAAD/4RrgRXhpZgAATU0AKgAAAAgACQEPAAIAAAAGAAAAegEQAAI AAAAKAAAAgAEaAAUAAAABAAAAigEbAAUAAAABAAAAkgEoAAMAAAABAAIAAAExAAIAAAALAAAAmg EyAAIAAAAUAAAApgITAAMAAAABAAEAAIdpAAQAAAABAAAAugAAA/5BcHBsZQBpUGhvbmUgNnMAA diff --git a/tests/Unit/data/vcardExport/PhotoInlined.vcf.exported b/tests/Unit/data/vcardExport/PhotoInlined.vcf.exported index 671d0eeb..91097637 100644 --- a/tests/Unit/data/vcardExport/PhotoInlined.vcf.exported +++ b/tests/Unit/data/vcardExport/PhotoInlined.vcf.exported @@ -6,7 +6,7 @@ FN:Max Mustermann EMAIL;TYPE=internet:special@maxmu.de PRODID:-//Apple Inc.//iCloud Web Address Book 2018B71//EN REV:2020-10-03T10:30:20Z -PHOTO;ENCODING=b;VALUE=binary: +PHOTO;ENCODING=b;VALUE=binary;TYPE=PNG: iVBORw0KGgoAAAANSUhEUgAAALUAAAC1CAIAAACWMSn+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAA gAElEQVR4nGS8WW8sS5ImZpu7R0QuJJPkIc+5W+0109XV3dMttTB6EiAJ/TjST5UAQdACvUyjAU HSTC/q9VbV3c/OLTNjc3cz00PwsA5G8UAkMzwiyTBzs+/7zCzxlxenjoaIrLYSOm+6XWhPsfmkO diff --git a/tests/Unit/data/vcardExport/PhotoInlinedCropped.vcf.exported b/tests/Unit/data/vcardExport/PhotoInlinedCropped.vcf.exported index 671d0eeb..91097637 100644 --- a/tests/Unit/data/vcardExport/PhotoInlinedCropped.vcf.exported +++ b/tests/Unit/data/vcardExport/PhotoInlinedCropped.vcf.exported @@ -6,7 +6,7 @@ FN:Max Mustermann EMAIL;TYPE=internet:special@maxmu.de PRODID:-//Apple Inc.//iCloud Web Address Book 2018B71//EN REV:2020-10-03T10:30:20Z -PHOTO;ENCODING=b;VALUE=binary: +PHOTO;ENCODING=b;VALUE=binary;TYPE=PNG: iVBORw0KGgoAAAANSUhEUgAAALUAAAC1CAIAAACWMSn+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAA gAElEQVR4nGS8WW8sS5ImZpu7R0QuJJPkIc+5W+0109XV3dMttTB6EiAJ/TjST5UAQdACvUyjAU HSTC/q9VbV3c/OLTNjc3cz00PwsA5G8UAkMzwiyTBzs+/7zCzxlxenjoaIrLYSOm+6XWhPsfmkO diff --git a/tests/Unit/data/vcardImport/README.md b/tests/Unit/data/vcardImport/README.md index 7fa0965d..de8d7bb5 100644 --- a/tests/Unit/data/vcardImport/README.md +++ b/tests/Unit/data/vcardImport/README.md @@ -54,6 +54,7 @@ There are a few specifics to be tested during the import: - IM-KAddressbook: A VCard containing instant messaging attributes produced by KAddressbook - IM-Nextcloud: A VCard containing instant messaging attributes produced by nextcloud - IM-Owncloud: A VCard containing instant messaging attributes produced by owncloud +- VCard4-DataUriPhoto: A v4 VCard containing a PHOTO in data URI format. - ZeroStrings: Tests "0" strings in various places of the VCard, which are considered "empty" by php's empty function. This must not cause these properties to be discarded during the import like properties with no value. diff --git a/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.json b/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.json new file mode 100644 index 00000000..e89c174b --- /dev/null +++ b/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.json @@ -0,0 +1,9 @@ +{ + "cuid": "5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2", + "name": "vCard 4 Contact with image", + "kind": "individual", + "email:other": [ + "bbb@example.org" + ], + "photo": "@../srv/pixel.jpg" +} diff --git a/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.vcf b/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.vcf new file mode 100644 index 00000000..65e46eb1 --- /dev/null +++ b/tests/Unit/data/vcardImport/VCard4-DataUriPhoto.vcf @@ -0,0 +1,12 @@ +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V83.6//EN-GB +UID:5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2 +FN:vCard 4 Contact with image +EMAIL:bbb@example.org +REV:20221222T213003Z +PHOTO:data:image/JPEG\;base64\,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHB + wcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/ + wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD + /2gAIAQEAAD8AL//Z +END:VCARD diff --git a/tests/Unit/data/vcardUpdate/PhotoUpdated.vcf.new b/tests/Unit/data/vcardUpdate/PhotoUpdated.vcf.new index efac2703..4b1d398b 100644 --- a/tests/Unit/data/vcardUpdate/PhotoUpdated.vcf.new +++ b/tests/Unit/data/vcardUpdate/PhotoUpdated.vcf.new @@ -6,7 +6,7 @@ FN:Max Mustermann EMAIL:special@maxmu.de PRODID:-//Apple Inc.//iCloud Web Address Book 2018B71//EN REV:2020-10-03T10:30:20Z -PHOTO;ENCODING=b;VALUE=binary: +PHOTO;ENCODING=b;VALUE=binary;TYPE=PNG: iVBORw0KGgoAAAANSUhEUgAAALUAAAC1CAIAAACWMSn+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAA gAElEQVR4nGS8WW8sS5ImZpu7R0QuJJPkIc+5W+0109XV3dMttTB6EiAJ/TjST5UAQdACvUyjAU HSTC/q9VbV3c/OLTNjc3cz00PwsA5G8UAkMzwiyTBzs+/7zCzxlxenjoaIrLYSOm+6XWhPsfmkO diff --git a/tests/Unit/data/vcardUpdate/README.md b/tests/Unit/data/vcardUpdate/README.md index 1b41ae48..445588dd 100644 --- a/tests/Unit/data/vcardUpdate/README.md +++ b/tests/Unit/data/vcardUpdate/README.md @@ -17,5 +17,7 @@ preserved. - XABLabel: Two EMAIL properties have a custom label given with X-ABLabel in the original VCard. One of the addresses gets a new custom attribute, while the other one retains the original custom label. - Group: Update a KIND=group VCard to have a new group name +- VCard4-DataUriPhotoUnchanged: Preserve inline photo including mime-type in v4 vcard +- VCard4-DataUriPhotoUpdated: Update an inline photo in a v4 vcard - ZeroStrings: save data contains "0" values from some data fields, which are considered empty() by PHPs empty() function. These properties must be properly set to 0 in the updated VCard, not omitted. diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.json b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.json new file mode 100644 index 00000000..a85e71a4 --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.json @@ -0,0 +1,8 @@ +{ + "cuid": "5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2", + "name": "vCard 4 Contact with image", + "kind": "individual", + "email:other": [ + "bbb@example.org" + ] +} diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf new file mode 100644 index 00000000..ce379011 --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf @@ -0,0 +1,13 @@ +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V83.6//EN-GB +UID:5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2 +FN:vCard 4 Contact with image +N:;;;; +EMAIL:bbb@example.org +REV:20221222T213003Z +PHOTO:data:image/JPEG\;base64\,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHB + wcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/ + wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD + /2gAIAQEAAD8AL//Z +END:VCARD diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf.new b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf.new new file mode 100644 index 00000000..ce379011 --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUnchanged.vcf.new @@ -0,0 +1,13 @@ +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V83.6//EN-GB +UID:5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2 +FN:vCard 4 Contact with image +N:;;;; +EMAIL:bbb@example.org +REV:20221222T213003Z +PHOTO:data:image/JPEG\;base64\,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHB + wcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/ + wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD + /2gAIAQEAAD8AL//Z +END:VCARD diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.json b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.json new file mode 100644 index 00000000..e89c174b --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.json @@ -0,0 +1,9 @@ +{ + "cuid": "5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2", + "name": "vCard 4 Contact with image", + "kind": "individual", + "email:other": [ + "bbb@example.org" + ], + "photo": "@../srv/pixel.jpg" +} diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf new file mode 100644 index 00000000..ce379011 --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf @@ -0,0 +1,13 @@ +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V83.6//EN-GB +UID:5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2 +FN:vCard 4 Contact with image +N:;;;; +EMAIL:bbb@example.org +REV:20221222T213003Z +PHOTO:data:image/JPEG\;base64\,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHB + wcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/ + wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD + /2gAIAQEAAD8AL//Z +END:VCARD diff --git a/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf.new b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf.new new file mode 100644 index 00000000..d6b0cf70 --- /dev/null +++ b/tests/Unit/data/vcardUpdate/VCard4-DataUriPhotoUpdated.vcf.new @@ -0,0 +1,13 @@ +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V83.6//EN-GB +UID:5cdac90f-cb4c-4c1c-ad66-fe8591bdf3a2 +FN:vCard 4 Contact with image +N:;;;; +EMAIL:bbb@example.org +REV:20221222T213003Z +PHOTO:data:image/jpeg;base64\,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHB + wcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/ + wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD + /2gAIAQEAAD8AL//Z +END:VCARD