From 466425a804c15bbdc6015baca22597e2719278bf Mon Sep 17 00:00:00 2001 From: Olivier Dolbeau Date: Fri, 2 Oct 2026 22:58:24 +0200 Subject: [PATCH] fix: do not split flat text values on unescaped commas FlatText inherited Text::setRawMimeDirValue(), which splits values on unescaped commas, although its documentation says they are not delimiters. getValue() then returned the value escaped again (newlines as a literal \n), and jCard got several values for one. LineFoldingIssueTest gets back the escaped comma its data had before bedefa13, and expects the line folded since it is one octet longer. Co-Authored-By: Claude Opus 5.5 --- lib/Property/FlatText.php | 12 +++++++ tests/VObject/LineFoldingIssueTest.php | 15 +++++++-- tests/VObject/Property/FlatTextTest.php | 45 +++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 tests/VObject/Property/FlatTextTest.php diff --git a/lib/Property/FlatText.php b/lib/Property/FlatText.php index eee1316e2..a92e95f98 100644 --- a/lib/Property/FlatText.php +++ b/lib/Property/FlatText.php @@ -3,6 +3,7 @@ namespace Sabre\VObject\Property; use Sabre\VObject\InvalidDataException; +use Sabre\VObject\Parser\MimeDir; /** * FlatText property. @@ -31,6 +32,17 @@ class FlatText extends Text */ public string $delimiter = ','; + /** + * Sets a raw value coming from a mimedir (iCalendar/vCard) file. + * + * A flat text holds a single value: as said above, unescaped commas are + * not delimiters, so the value is not split. + */ + public function setRawMimeDirValue(string $val): void + { + $this->setValue(MimeDir::unescapeValue($val, '')); + } + /** * Sets the value as a quoted-printable encoded string. * diff --git a/tests/VObject/LineFoldingIssueTest.php b/tests/VObject/LineFoldingIssueTest.php index 5e636d36c..81b3dd990 100644 --- a/tests/VObject/LineFoldingIssueTest.php +++ b/tests/VObject/LineFoldingIssueTest.php @@ -11,13 +11,24 @@ public function testRead(): void $event = <<serialize()); + self::assertEquals("TEST\n\n \n\nTEST\n\n \n\nTEST\n\n \n\nTEST\n\nTEST\nTEST, TEST", $obj->VEVENT->DESCRIPTION->getValue()); + self::assertEquals($expected, $obj->serialize()); } } diff --git a/tests/VObject/Property/FlatTextTest.php b/tests/VObject/Property/FlatTextTest.php new file mode 100644 index 000000000..e83d0fb23 --- /dev/null +++ b/tests/VObject/Property/FlatTextTest.php @@ -0,0 +1,45 @@ +NOTE->getParts()); + self::assertEquals('Line 1'."\n".'Line 2, and more', $vcard->NOTE->getValue()); + } + + public function testEscapedCommaIsUnescaped(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:4.0\r\nNOTE:Line 1\\nLine 2\\, and more\r\nEND:VCARD\r\n"); + + self::assertEquals('Line 1'."\n".'Line 2, and more', $vcard->NOTE->getValue()); + } + + public function testICalendarText(): void + { + $vcal = Reader::read("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDESCRIPTION:Agenda:\\n1. Budget, staffing\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + self::assertEquals('Agenda:'."\n".'1. Budget, staffing', $vcal->VEVENT->DESCRIPTION->getValue()); + } + + public function testJCardHasASingleValue(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Doe, Jane\r\nEND:VCARD\r\n"); + + self::assertEquals(['fn', new \stdClass(), 'text', 'Doe, Jane'], $vcard->FN->jsonSerialize()); + } + + public function testUnescapedCommaIsEscapedOnWrite(): void + { + $vcard = Reader::read("BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Doe, Jane\r\nEND:VCARD\r\n"); + + self::assertEquals("BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Doe\\, Jane\r\nEND:VCARD\r\n", $vcard->serialize()); + } +}