diff --git a/lib/Document.php b/lib/Document.php index 8319b49b..3fdb4f98 100644 --- a/lib/Document.php +++ b/lib/Document.php @@ -194,6 +194,13 @@ public function createProperty(string $name, $value = null, ?array $parameters = $class = $this->getClassNameForPropertyValue($valueType); } + // A base64 encoded value is binary, whatever the default value type of + // the property: vCard 2.1 and 3.0 embed keys and sounds this way too, + // not only photos and logos. + if (!$class && $this->isBase64Encoded($parameters['ENCODING'] ?? null)) { + $class = Property\Binary::class; + } + // If the value parameter is not set or set to something we do not recognize // we do not attempt to interpret or parse the datass value as specified in // https://datatracker.ietf.org/doc/html/rfc5545#section-3.2.20 @@ -205,6 +212,18 @@ public function createProperty(string $name, $value = null, ?array $parameters = return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString); } + /** + * Whether an ENCODING parameter value says the value is base64 encoded. + */ + private function isBase64Encoded(mixed $encoding): bool + { + if (is_array($encoding)) { + $encoding = reset($encoding); + } + + return is_string($encoding) && in_array(strtoupper($encoding), ['B', 'BASE64'], true); + } + /** * This method returns a full class-name for a value parameter. * diff --git a/tests/VObject/Base64EncodingTest.php b/tests/VObject/Base64EncodingTest.php new file mode 100644 index 00000000..bf7a6b8b --- /dev/null +++ b/tests/VObject/Base64EncodingTest.php @@ -0,0 +1,43 @@ +KEY); + self::assertEquals('-----BEGIN', $vcard->KEY->getValue()); + self::assertEquals($input, $vcard->serialize()); + } + + public function testSoundInVCard21(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:2.1\r\nSOUND;ENCODING=BASE64;TYPE=WAVE:UklGRg==\r\nEND:VCARD\r\n"); + + self::assertInstanceOf(Property\Binary::class, $vcard->SOUND); + self::assertEquals('RIFF', $vcard->SOUND->getValue()); + } + + public function testConversionToVCard40GivesADataUri(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:3.0\r\nKEY;ENCODING=b;TYPE=PGP:LS0tLS1CRUdJTg==\r\nEND:VCARD\r\n"); + + $converted = $vcard->convert(Document::VCARD40); + + self::assertStringStartsWith('data:', (string) $converted->KEY); + self::assertStringEndsWith(';base64,LS0tLS1CRUdJTg==', (string) $converted->KEY); + } + + public function testValueParameterComesFirst(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:3.0\r\nKEY;ENCODING=b;VALUE=text:LS0tLS1CRUdJTg==\r\nEND:VCARD\r\n"); + + self::assertInstanceOf(Property\Text::class, $vcard->KEY); + } +}