diff --git a/lib/CalDAV/Backend/PDO.php b/lib/CalDAV/Backend/PDO.php index 91b5da4411..2767896434 100644 --- a/lib/CalDAV/Backend/PDO.php +++ b/lib/CalDAV/Backend/PDO.php @@ -544,19 +544,19 @@ public function createCalendarObject($calendarId, $objectUri, $calendarData) $extraData = $this->getDenormalizedData($calendarData); - $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)'); - $stmt->execute([ - $calendarId, - $objectUri, - $calendarData, - time(), - $extraData['etag'], - $extraData['size'], - $extraData['componentType'], - $extraData['firstOccurence'], - $extraData['lastOccurence'], - $extraData['uid'], - ]); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->calendarObjectTableName.' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (:calendarid, :uri, :calendardata, :lastmodified, :etag, :size, :componenttype, :firstoccurence, :lastoccurence, :uid)'); + $lastmodified = time(); + $stmt->bindParam('calendarid', $calendarId, \PDO::PARAM_INT); + $stmt->bindParam('uri', $objectUri, \PDO::PARAM_STR); + $stmt->bindParam('calendardata', $calendarData, \PDO::PARAM_LOB); + $stmt->bindParam('lastmodified', $lastmodified, \PDO::PARAM_INT); + $stmt->bindParam('etag', $extraData['etag'], \PDO::PARAM_STR); + $stmt->bindParam('size', $extraData['size'], \PDO::PARAM_INT); + $stmt->bindParam('componenttype', $extraData['componentType'], \PDO::PARAM_STR); + $stmt->bindParam('firstoccurence', $extraData['firstOccurence'], \PDO::PARAM_INT); + $stmt->bindParam('lastoccurence', $extraData['lastOccurence'], \PDO::PARAM_INT); + $stmt->bindParam('uid', $extraData['uid'], \PDO::PARAM_STR); + $stmt->execute(); $this->addChange($calendarId, $objectUri, 1); return '"'.$extraData['etag'].'"'; @@ -590,8 +590,19 @@ public function updateCalendarObject($calendarId, $objectUri, $calendarData) $extraData = $this->getDenormalizedData($calendarData); - $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?'); - $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]); + $stmt = $this->pdo->prepare('UPDATE '.$this->calendarObjectTableName.' SET calendardata = :calendardata, lastmodified = :lastmodified, etag = :etag, size = :size, componenttype = :componenttype, firstoccurence = :firstoccurence, lastoccurence = :lastoccurence, uid = :uid WHERE calendarid = :calendarid AND uri = :uri'); + $lastmodified = time(); + $stmt->bindParam('calendardata', $calendarData, \PDO::PARAM_LOB); + $stmt->bindParam('lastmodified', $lastmodified, \PDO::PARAM_INT); + $stmt->bindParam('etag', $extraData['etag'], \PDO::PARAM_STR); + $stmt->bindParam('size', $extraData['size'], \PDO::PARAM_INT); + $stmt->bindParam('componenttype', $extraData['componentType'], \PDO::PARAM_STR); + $stmt->bindParam('firstoccurence', $extraData['firstOccurence'], \PDO::PARAM_INT); + $stmt->bindParam('lastoccurence', $extraData['lastOccurence'], \PDO::PARAM_INT); + $stmt->bindParam('uid', $extraData['uid'], \PDO::PARAM_STR); + $stmt->bindParam('calendarid', $calendarId, \PDO::PARAM_INT); + $stmt->bindParam('uri', $objectUri, \PDO::PARAM_STR); + $stmt->execute(); $this->addChange($calendarId, $objectUri, 2); @@ -1311,13 +1322,22 @@ public function deleteSchedulingObject($principalUri, $objectUri) */ public function createSchedulingObject($principalUri, $objectUri, $objectData) { - $stmt = $this->pdo->prepare('INSERT INTO '.$this->schedulingObjectTableName.' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)'); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->schedulingObjectTableName.' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (:principaluri, :calendardata, :uri, :lastmodified, :etag, :size)'); if (is_resource($objectData)) { $objectData = stream_get_contents($objectData); } - $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]); + $lastmodified = time(); + $etag = md5($objectData); + $size = strlen($objectData); + $stmt->bindParam('principaluri', $principalUri, \PDO::PARAM_STR); + $stmt->bindParam('calendardata', $objectData, \PDO::PARAM_LOB); + $stmt->bindParam('uri', $objectUri, \PDO::PARAM_STR); + $stmt->bindParam('lastmodified', $lastmodified, \PDO::PARAM_INT); + $stmt->bindParam('etag', $etag, \PDO::PARAM_STR); + $stmt->bindParam('size', $size, \PDO::PARAM_INT); + $stmt->execute(); } /** diff --git a/lib/CardDAV/Backend/PDO.php b/lib/CardDAV/Backend/PDO.php index e47545b7a5..bf5103fb17 100644 --- a/lib/CardDAV/Backend/PDO.php +++ b/lib/CardDAV/Backend/PDO.php @@ -316,18 +316,18 @@ public function getMultipleCards($addressBookId, array $uris) */ public function createCard($addressBookId, $cardUri, $cardData) { - $stmt = $this->pdo->prepare('INSERT INTO '.$this->cardsTableName.' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)'); + $stmt = $this->pdo->prepare('INSERT INTO '.$this->cardsTableName.' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (:carddata, :uri, :lastmodified, :addressbookid, :size, :etag)'); $etag = md5($cardData); - - $stmt->execute([ - $cardData, - $cardUri, - time(), - $addressBookId, - strlen($cardData), - $etag, - ]); + $lastmodified = time(); + $size = strlen($cardData); + $stmt->bindParam('carddata', $cardData, \PDO::PARAM_LOB); + $stmt->bindParam('uri', $cardUri, \PDO::PARAM_STR); + $stmt->bindParam('lastmodified', $lastmodified, \PDO::PARAM_INT); + $stmt->bindParam('addressbookid', $addressBookId, \PDO::PARAM_INT); + $stmt->bindParam('size', $size, \PDO::PARAM_INT); + $stmt->bindParam('etag', $etag, \PDO::PARAM_STR); + $stmt->execute(); $this->addChange($addressBookId, $cardUri, 1); @@ -362,17 +362,18 @@ public function createCard($addressBookId, $cardUri, $cardData) */ public function updateCard($addressBookId, $cardUri, $cardData) { - $stmt = $this->pdo->prepare('UPDATE '.$this->cardsTableName.' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?'); + $stmt = $this->pdo->prepare('UPDATE '.$this->cardsTableName.' SET carddata = :carddata, lastmodified = :lastmodified, size = :size, etag = :etag WHERE uri = :uri AND addressbookid = :addressbookid'); $etag = md5($cardData); - $stmt->execute([ - $cardData, - time(), - strlen($cardData), - $etag, - $cardUri, - $addressBookId, - ]); + $lastmodified = time(); + $size = strlen($cardData); + $stmt->bindParam('carddata', $cardData, \PDO::PARAM_LOB); + $stmt->bindParam('lastmodified', $lastmodified, \PDO::PARAM_INT); + $stmt->bindParam('size', $size, \PDO::PARAM_INT); + $stmt->bindParam('etag', $etag, \PDO::PARAM_STR); + $stmt->bindParam('uri', $cardUri, \PDO::PARAM_STR); + $stmt->bindParam('addressbookid', $addressBookId, \PDO::PARAM_INT); + $stmt->execute(); $this->addChange($addressBookId, $cardUri, 2); diff --git a/tests/Sabre/CalDAV/Backend/AbstractPDOTestCase.php b/tests/Sabre/CalDAV/Backend/AbstractPDOTestCase.php index 8b6de11539..3ea1a6cb23 100644 --- a/tests/Sabre/CalDAV/Backend/AbstractPDOTestCase.php +++ b/tests/Sabre/CalDAV/Backend/AbstractPDOTestCase.php @@ -243,6 +243,30 @@ public function testCreateCalendarObject() ], $row); } + /** + * @see https://github.com/sabre-io/dav/issues/1587 + */ + public function testCreateCalendarObjectWithIcsEscapes() + { + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2', 'somerandomid', []); + + // ICS data with escaped comma (\,) and newline (\n) in DESCRIPTION. + // These are standard RFC 5545 TEXT escapes but trigger PostgreSQL + // "invalid input syntax for type bytea" when calendardata is BYTEA + // and the parameter is bound as PARAM_STR instead of PARAM_LOB. + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nDESCRIPTION:Hello\\, world\\nSecond line\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'ics-escapes-id', $object); + + // Verify round-trip: read it back and compare + $result = $backend->getCalendarObject($returnedId, 'ics-escapes-id'); + if (is_resource($result['calendardata'])) { + $result['calendardata'] = stream_get_contents($result['calendardata']); + } + self::assertEquals($object, $result['calendardata']); + } + public function testGetMultipleObjects() { $backend = new PDO($this->pdo);