From 0d101135063982c74d4859024307f1775be4f498 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 29 Dec 2021 14:18:45 +0100 Subject: [PATCH 1/3] Fix column/property type of the CalDAV deleted_at time stamp The timestamp is an int, but we treated it as string. With this patch the property map is enriched with types and settype casts the value if necessary. Signed-off-by: Christoph Wurst --- apps/dav/lib/CalDAV/CalDavBackend.php | 92 +++++++++++++-------------- 1 file changed, 45 insertions(+), 47 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index ab1f878065114..074e2713c4bcc 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -138,20 +138,19 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription public const CLASSIFICATION_CONFIDENTIAL = 2; /** - * List of CalDAV properties, and how they map to database field names + * List of CalDAV properties, and how they map to database field names and their type * Add your own properties by simply adding on to this array. * - * Note that only string-based properties are supported here. - * * @var array + * @psalm-var array */ public $propertyMap = [ - '{DAV:}displayname' => 'displayname', - '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', - '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', - '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', - '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', - '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => 'deleted_at', + '{DAV:}displayname' => ['displayname', 'string'], + '{urn:ietf:params:xml:ns:caldav}calendar-description' => ['description', 'string'], + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => ['timezone', 'string'], + '{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'string'], + '{http://apple.com/ns/ical/}calendar-color' => ['calendarcolor', 'string'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => ['deleted_at', 'int'], ]; /** @@ -332,7 +331,7 @@ public function getDeletedCalendars(int $deletedBefore): array { public function getCalendarsForUser($principalUri) { $principalUriOriginal = $principalUri; $principalUri = $this->convertPrincipal($principalUri, true); - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; @@ -373,10 +372,7 @@ public function getCalendarsForUser($principalUri) { '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -392,7 +388,7 @@ public function getCalendarsForUser($principalUri) { $principals[] = $principalUri; - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; @@ -450,10 +446,7 @@ public function getCalendarsForUser($principalUri) { $readOnlyPropertyName => $readOnly, ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -470,7 +463,7 @@ public function getCalendarsForUser($principalUri) { */ public function getUsersOwnCalendars($principalUri) { $principalUri = $this->convertPrincipal($principalUri, true); - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; @@ -499,10 +492,8 @@ public function getUsersOwnCalendars($principalUri) { '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -537,7 +528,7 @@ private function getUserDisplayName($uid) { * @return array */ public function getPublicCalendars() { - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; @@ -576,10 +567,7 @@ public function getPublicCalendars() { '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -598,7 +586,7 @@ public function getPublicCalendars() { * @throws NotFound */ public function getPublicCalendar($uri) { - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; @@ -644,10 +632,7 @@ public function getPublicCalendar($uri) { '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -660,7 +645,7 @@ public function getPublicCalendar($uri) { * @return array|null */ public function getCalendarByUri($principal, $uri) { - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; @@ -698,10 +683,7 @@ public function getCalendarByUri($principal, $uri) { '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -713,7 +695,7 @@ public function getCalendarByUri($principal, $uri) { * @return array|null */ public function getCalendarById($calendarId) { - $fields = array_values($this->propertyMap); + $fields = array_column($this->propertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; @@ -750,10 +732,7 @@ public function getCalendarById($calendarId) { '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; - foreach ($this->propertyMap as $xmlName => $dbName) { - $calendar[$xmlName] = $row[$dbName]; - } - + $calendar = $this->rowToCalendar($row, $calendar); $calendar = $this->addOwnerPrincipalToCalendar($calendar); $calendar = $this->addResourceTypeToCalendar($row, $calendar); @@ -844,7 +823,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent'); } - foreach ($this->propertyMap as $xmlName => $dbName) { + foreach ($this->propertyMap as $xmlName => [$dbName, $type]) { if (isset($properties[$xmlName])) { $values[$dbName] = $properties[$xmlName]; } @@ -893,7 +872,7 @@ public function updateCalendar($calendarId, PropPatch $propPatch) { $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent'); break; default: - $fieldName = $this->propertyMap[$propertyName]; + $fieldName = $this->propertyMap[$propertyName][0]; $newValues[$fieldName] = $propertyValue; break; } @@ -1090,7 +1069,7 @@ public function getDeletedCalendarObjects(int $deletedBefore): array { 'size' => (int) $row['size'], 'component' => strtolower($row['componenttype']), 'classification' => (int) $row['classification'], - '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int) $row['deleted_at'], ]; } $stmt->closeCursor(); @@ -1129,7 +1108,7 @@ public function getDeletedCalendarObjectsByPrincipal(string $principalUri): arra 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), 'classification' => (int)$row['classification'], - '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => $row['deleted_at'] === null ? $row['deleted_at'] : (int) $row['deleted_at'], ]; } $stmt->closeCursor(); @@ -3161,4 +3140,23 @@ private function addResourceTypeToCalendar(array $row, array $calendar): array { } return $calendar; } + + /** + * Amend the calendar info with database row data + * + * @param array $row + * @param array $calendar + * + * @return array + */ + private function rowToCalendar($row, array $calendar): array { + foreach ($this->propertyMap as $xmlName => [$dbName, $type]) { + $value = $row[$dbName]; + if ($value !== null) { + settype($value, $type); + } + $calendar[$xmlName] = $value; + } + return $calendar; + } } From df0e93869c0eba88a53a1d6fa87da0f681a93452 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 29 Dec 2021 15:10:38 +0100 Subject: [PATCH 2/3] Fix column/property type of the calendar order Signed-off-by: Christoph Wurst --- apps/dav/lib/CalDAV/CalDavBackend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 074e2713c4bcc..82ec3c9b90055 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -148,7 +148,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription '{DAV:}displayname' => ['displayname', 'string'], '{urn:ietf:params:xml:ns:caldav}calendar-description' => ['description', 'string'], '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => ['timezone', 'string'], - '{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'string'], + '{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'int'], '{http://apple.com/ns/ical/}calendar-color' => ['calendarcolor', 'string'], '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}deleted-at' => ['deleted_at', 'int'], ]; From e858c6b9e2bd2f6ccfd17cb4988963b5a50cae15 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 29 Dec 2021 15:26:49 +0100 Subject: [PATCH 3/3] Fix CalDAV subscriptions calendarorder column/prop type Signed-off-by: Christoph Wurst --- apps/dav/lib/CalDAV/CalDavBackend.php | 58 +++++++++++++++------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 82ec3c9b90055..9f815b4f2d1e4 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -95,6 +95,7 @@ use Sabre\VObject\Recur\EventIterator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; +use function array_column; use function array_merge; use function array_values; use function explode; @@ -159,13 +160,13 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * @var array */ public $subscriptionPropertyMap = [ - '{DAV:}displayname' => 'displayname', - '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate', - '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', - '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', - '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos', - '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms', - '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', + '{DAV:}displayname' => ['displayname', 'string'], + '{http://apple.com/ns/ical/}refreshrate' => ['refreshrate', 'string'], + '{http://apple.com/ns/ical/}calendar-order' => ['calendarorder', 'int'], + '{http://apple.com/ns/ical/}calendar-color' => ['calendarcolor', 'string'], + '{http://calendarserver.org/ns/}subscribed-strip-todos' => ['striptodos', 'bool'], + '{http://calendarserver.org/ns/}subscribed-strip-alarms' => ['stripalarms', 'string'], + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => ['stripattachments', 'string'], ]; /** @var array properties to index */ @@ -743,7 +744,7 @@ public function getCalendarById($calendarId) { * @param $subscriptionId */ public function getSubscriptionById($subscriptionId) { - $fields = array_values($this->subscriptionPropertyMap); + $fields = array_column($this->subscriptionPropertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'source'; @@ -775,13 +776,7 @@ public function getSubscriptionById($subscriptionId) { '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; - foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { - if (!is_null($row[$dbName])) { - $subscription[$xmlName] = $row[$dbName]; - } - } - - return $subscription; + return $this->rowToSubscription($row, $subscription); } /** @@ -2329,7 +2324,7 @@ public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limi * @return array */ public function getSubscriptionsForUser($principalUri) { - $fields = array_values($this->subscriptionPropertyMap); + $fields = array_column($this->subscriptionPropertyMap, 0); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'source'; @@ -2357,13 +2352,7 @@ public function getSubscriptionsForUser($principalUri) { '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', ]; - foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { - if (!is_null($row[$dbName])) { - $subscription[$xmlName] = $row[$dbName]; - } - } - - $subscriptions[] = $subscription; + $subscriptions[] = $this->rowToSubscription($row, $subscription); } return $subscriptions; @@ -2394,7 +2383,7 @@ public function createSubscription($principalUri, $uri, array $properties) { $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments']; - foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) { + foreach ($this->subscriptionPropertyMap as $xmlName => [$dbName, $type]) { if (array_key_exists($xmlName, $properties)) { $values[$dbName] = $properties[$xmlName]; if (in_array($dbName, $propertiesBoolean)) { @@ -2456,7 +2445,7 @@ public function updateSubscription($subscriptionId, PropPatch $propPatch) { if ($propertyName === '{http://calendarserver.org/ns/}source') { $newValues['source'] = $propertyValue->getHref(); } else { - $fieldName = $this->subscriptionPropertyMap[$propertyName]; + $fieldName = $this->subscriptionPropertyMap[$propertyName][0]; $newValues[$fieldName] = $propertyValue; } } @@ -3159,4 +3148,23 @@ private function rowToCalendar($row, array $calendar): array { } return $calendar; } + + /** + * Amend the subscription info with database row data + * + * @param array $row + * @param array $subscription + * + * @return array + */ + private function rowToSubscription($row, array $subscription): array { + foreach ($this->subscriptionPropertyMap as $xmlName => [$dbName, $type]) { + $value = $row[$dbName]; + if ($value !== null) { + settype($value, $type); + } + $subscription[$xmlName] = $value; + } + return $subscription; + } }