From 76f216ee9e0fa162d0a2148cc1a335069e76ccb8 Mon Sep 17 00:00:00 2001 From: Olivier Dolbeau Date: Fri, 2 Oct 2026 23:23:34 +0200 Subject: [PATCH] fix: KEY and SOUND are URIs in vCard 4.0 RFC 6350 makes KEY and SOUND URIs by default in vCard 4.0, but they mapped to FlatText: jCard and xCard gave them the text type, and the 4.0 to 3.0 conversion never reached its SOUND branch. They now map to Property\Uri in vCard 4.0, as PHOTO and LOGO already do. The xCard tests of the RFC 6350 SOUND and KEY examples now expect , the type of those examples. Co-Authored-By: Claude Opus 5.5 --- lib/Component/VCard.php | 6 +++ tests/VObject/Component/VCardKeySoundTest.php | 44 +++++++++++++++++++ tests/VObject/Parser/XmlTest.php | 4 +- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tests/VObject/Component/VCardKeySoundTest.php diff --git a/lib/Component/VCard.php b/lib/Component/VCard.php index 0b8696fd4..a31350084 100644 --- a/lib/Component/VCard.php +++ b/lib/Component/VCard.php @@ -531,6 +531,12 @@ public function getClassNameForPropertyName(string $propertyName): string return VObject\Property\Uri::class; } + // In vCard 4, KEY and SOUND are URIs by default too (RFC 6350, sections + // 6.7.5 and 6.8.1), not text. + if (in_array($propertyName, ['KEY', 'SOUND'], true) && self::VCARD40 === $this->getDocumentType()) { + return VObject\Property\Uri::class; + } + return $className; } } diff --git a/tests/VObject/Component/VCardKeySoundTest.php b/tests/VObject/Component/VCardKeySoundTest.php new file mode 100644 index 000000000..ea8d6e32e --- /dev/null +++ b/tests/VObject/Component/VCardKeySoundTest.php @@ -0,0 +1,44 @@ +KEY); + self::assertInstanceOf(Property\Uri::class, $vcard->SOUND); + self::assertEquals(['key', new \stdClass(), 'uri', 'https://example.com/key.asc'], $vcard->KEY->jsonSerialize()); + } + + public function testATextKeyStaysText(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:4.0\r\nKEY;VALUE=text:-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nEND:VCARD\r\n"); + + self::assertInstanceOf(Property\Text::class, $vcard->KEY); + } + + public function testVCard30IsUnchanged(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:3.0\r\nKEY:https://example.com/key.asc\r\nEND:VCARD\r\n"); + + self::assertInstanceOf(Property\FlatText::class, $vcard->KEY); + } + + public function testSoundDataUriConvertsToBinaryInVCard30(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:4.0\r\nSOUND:data:audio/wav;base64,UklGRg==\r\nEND:VCARD\r\n"); + + $converted = $vcard->convert(Document::VCARD30); + + self::assertInstanceOf(Property\Binary::class, $converted->SOUND); + self::assertEquals('RIFF', $converted->SOUND->getValue()); + } +} diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index 223b0f105..6ab8c4753 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -2455,7 +2455,7 @@ public function testRFC6350Section6Part7Part5(): void - CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com + CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com @@ -2571,7 +2571,7 @@ public function testRFC6350Section6Part8Part1(): void application/pgp-keys - ftp://example.com/keys/jdoe + ftp://example.com/keys/jdoe