From 169e20b5a371e9ecd2c17cef4bd1568e05c7c206 Mon Sep 17 00:00:00 2001 From: Michael Stilkerich Date: Sun, 25 Dec 2022 08:48:36 +0100 Subject: [PATCH 1/3] Unescape also semicolon in URI Property This is required for VCard4 (see test sample), as comma and semicolon may appear in URIs with scheme data. --- lib/Property/Uri.php | 46 ++++++++++++++++++------------ tests/VObject/Property/UriTest.php | 25 ++++++++++++++++ 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/lib/Property/Uri.php b/lib/Property/Uri.php index 9816ffffd..e6b103acf 100644 --- a/lib/Property/Uri.php +++ b/lib/Property/Uri.php @@ -3,6 +3,7 @@ namespace Sabre\VObject\Property; use Sabre\VObject\Parameter; +use Sabre\VObject\Parser\MimeDir; use Sabre\VObject\Property; /** @@ -62,32 +63,41 @@ public function parameters(): array */ public function setRawMimeDirValue(string $val): void { - // Normally we don't need to do any type of unescaping for these - // properties, however, we've noticed that Google Contacts + // For VCard4, we need to unescape comma, backslash and semicolon (and newline). (RFC6350, 3.4) + // + // However, we've noticed that Google Contacts // specifically escapes the colon (:) with a backslash. While I have // no clue why they thought that was a good idea, I'm unescaping it // anyway. // // Good thing backslashes are not allowed in urls. Makes it easy to // assume that a backslash is always intended as an escape character. - if ('URL' === $this->name) { - $regex = '# (?: (\\\\ (?: \\\\ | : ) ) ) #x'; - $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); - $newVal = ''; - foreach ($matches as $match) { - switch ($match) { - case '\:': - $newVal .= ':'; - break; - default: - $newVal .= $match; - break; - } + $escapeColon = ('URL' === $this->name) ? '| : ' : ''; + + $regex = '# (?: (\\\\ (?: \\\\ ' . $escapeColon . '| N | n | ; | , ) ) ) #x'; + $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $newVal = ''; + foreach ($matches as $match) { + switch ($match) { + case '\\\\': + $newVal .= '\\'; + break; + case '\;': + $newVal .= ';'; + break; + case '\,': + $newVal .= ','; + break; + case '\:': + $newVal .= ':'; + break; + default: + $newVal .= $match; + break; } - $this->value = $newVal; - } else { - $this->value = strtr($val, ['\,' => ',']); } + + $this->value = $newVal; } /** diff --git a/tests/VObject/Property/UriTest.php b/tests/VObject/Property/UriTest.php index 80810104c..4f0d54bf6 100644 --- a/tests/VObject/Property/UriTest.php +++ b/tests/VObject/Property/UriTest.php @@ -23,4 +23,29 @@ public function testAlwaysEncodeUriVCalendar(): void $output = Reader::read($input)->serialize(); $this->assertStringContainsString('URL;VALUE=URI:http://example.org/', $output); } + + public function testUriUnescapedProperly(): void + { + // The colon should normally not be escaped in URL, but Google Contacts does it and + // vobject contains a workaround for it + $input = <<assertSame('http://www.example.com/hello?world', (string) $output->URL); + $this->assertSame('data:image/JPEG;base64,/9j/4AAQSkZJRgABAQAAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAAv/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AL//Z', (string) $output->PHOTO); + } } From 34e3ddd2f536c18479fea23e6b5e75ee8a340184 Mon Sep 17 00:00:00 2001 From: Michael Stilkerich Date: Sun, 25 Dec 2022 09:07:08 +0100 Subject: [PATCH 2/3] Consider image mime types case insensitive in VCardConverter Also added the mandatory escaping of commas in VCard4 prop values to the converter test samples. --- lib/VCardConverter.php | 2 +- tests/VObject/VCardConverterTest.php | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index 7de4501ba..83e83fbfb 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -336,7 +336,7 @@ protected function convertUriToBinary(Component\VCard $output, Property\Uri $new unset($value); $newProperty['ENCODING'] = 'b'; - switch ($mimeType) { + switch (strtolower($mimeType)) { case 'image/jpeg': $newProperty['TYPE'] = 'JPEG'; break; diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index d27e7ff14..1a838ec35 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -38,9 +38,9 @@ public function testConvert30to40(): void TEL;PREF=1;TYPE=HOME:+1 555 666 777 ITEM1.TEL:+1 444 555 666 ITEM1.X-ABLABEL:CustomLabel -PHOTO;TYPE=HOME:data:image/jpeg;base64,Zm9v -PHOTO:data:image/gif;base64,Zm9v -PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO;TYPE=HOME:data:image/jpeg;base64\\,Zm9v +PHOTO:data:image/gif;base64\\,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64\\,Zm9v PHOTO:http://example.org/foo.png KIND:ORG END:VCARD @@ -66,9 +66,9 @@ public function testConvert40to40(): void VERSION:4.0 FN:Steve TEL;PREF=1;TYPE=HOME:+1 555 666 777 -PHOTO:data:image/jpeg;base64,Zm9v -PHOTO:data:image/gif;base64,Zm9v -PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:data:image/jpeg;base64\\,Zm9v +PHOTO:data:image/gif;base64\\,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64\\,Zm9v PHOTO:http://example.org/foo.png END:VCARD @@ -79,9 +79,9 @@ public function testConvert40to40(): void VERSION:4.0 FN:Steve TEL;PREF=1;TYPE=HOME:+1 555 666 777 -PHOTO:data:image/jpeg;base64,Zm9v -PHOTO:data:image/gif;base64,Zm9v -PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:data:image/jpeg;base64\\,Zm9v +PHOTO:data:image/gif;base64\\,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64\\,Zm9v PHOTO:http://example.org/foo.png END:VCARD @@ -193,9 +193,9 @@ public function testConvert40to30(): void PRODID:foo FN:Steve TEL;PREF=1;TYPE=HOME:+1 555 666 777 -PHOTO:data:image/jpeg;base64,Zm9v -PHOTO:data:image/gif,foo -PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:data:image/JPEG\\;base64\\,Zm9v +PHOTO:data:image/gif\\,foo +PHOTO;X-PARAM=FOO:data:image/png;base64\\,Zm9v PHOTO:http://example.org/foo.png KIND:ORG END:VCARD From 4d89b2e3be731e441760390aaffa70e628958c90 Mon Sep 17 00:00:00 2001 From: Michael Stilkerich Date: Mon, 26 Dec 2022 10:20:25 +0100 Subject: [PATCH 3/3] Fix: Remove X-ADDRESSBOOKSERVER-KIND=individual property in convert When converting to VCard v4, only X-ADDRESSBOOKSERVER-KIND=GROUP is converted to KIND=group currently. X-ADDRESSBOOKSERVER-KIND=INDIVIDUAL is kept in the vcard, although KIND=individual would be correct. Since this is the default, the property can also be dropped, as is done by vobject in the opposite direction (converting v4 to v3). Therefore, this fix will also drop the X-ADDRESSBOOKSERVER-KIND=INDIVIDUAL property. Test existing test was broken because it used the output of the v4->v3 conversion as input for the v3->v4 conversion, but since the property is dropped during the first conversion there is no explicit X-ADDRESSBOOKSERVER-KIND property in the input of the v3->v4 conversion. Test fixed as well. --- lib/VCardConverter.php | 4 ++++ tests/VObject/VCardConverterTest.php | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/VCardConverter.php b/lib/VCardConverter.php index 83e83fbfb..d76977d8e 100644 --- a/lib/VCardConverter.php +++ b/lib/VCardConverter.php @@ -180,6 +180,10 @@ protected function convertProperty(Component\VCard $input, Component\VCard $outp if ('GROUP' === strtoupper($property->getValue())) { $newProperty = $output->createProperty('KIND', 'GROUP'); } + if ('INDIVIDUAL' === strtoupper($property->getValue())) { + // Individual is implicit, so we skip it. + return; + } break; case 'X-ADDRESSBOOKSERVER-MEMBER': $newProperty = $output->createProperty('MEMBER', $property->getValue()); diff --git a/tests/VObject/VCardConverterTest.php b/tests/VObject/VCardConverterTest.php index 1a838ec35..2a9ea62fd 100644 --- a/tests/VObject/VCardConverterTest.php +++ b/tests/VObject/VCardConverterTest.php @@ -409,7 +409,14 @@ public function testConvertIndividualCard(): void $vcard ); - $input = $output; + $input = <<