From 7ee1f8bc951d8ca05a7ec7ad7ddfbf38dd709101 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 28 Jul 2026 21:50:30 -0400 Subject: [PATCH 1/4] fix(caldav): limit getCalendarObjectByUID query to calendar-objects Signed-off-by: Josh --- apps/dav/lib/CalDAV/CalDavBackend.php | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 6b2d64b4d03e0..b659b51b68ae1 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -2636,33 +2636,42 @@ public function searchPrincipalUri(string $principalUri, } /** - * Searches through all of a users calendars and calendar objects to find - * an object with a specific UID. + * Find the path of the calendar object with a given UID in calendars owned by + * a particular principal (user). * - * This method should return the path to this object, relative to the - * calendar home, so this path usually only contains two parts: + * When $calendarUri is provided, the lookup is restricted to that calendar. + * When it is null, all of the principal's calendars are searched. In that + * case, callers must use both path components returned by this method: the + * matching object may belong to a different calendar than one the caller + * currently has selected. * - * calendarpath/objectpath.ics + * Subscription and federated cached objects, deleted objects, and objects in + * deleted calendars are not considered. * - * If the uid is not found, return null. - * - * This method should only consider * objects that the principal owns, so - * any calendars owned by other principals that also appear in this - * collection should be ignored. + * The returned value is a path relative to the calendar home: + * "/". It is null when no matching object exists. + * UID uniqueness is guaranteed only within a calendar collection. Therefore, + * an unrestricted lookup can be ambiguous if multiple owned calendars contain + * the same UID. * * @param string $principalUri * @param string $uid + * @param string|null $calendarUri Calendar URI to restrict the lookup to. * @return string|null */ #[\Override] public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null) { $query = $this->db->getQueryBuilder(); - $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi') + $query->selectAlias('c.uri', 'calendaruri') + ->selectAlias('co.uri', 'objecturi') ->from('calendarobjects', 'co') - ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id')) + ->join('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id')) ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid))) - ->andWhere($query->expr()->isNull('co.deleted_at')); + ->andWhere($query->expr()->eq('co.calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))) + ->andWhere($query->expr()->isNull('co.deleted_at')) + ->andWhere($query->expr()->isNull('c.deleted_at')) + ->setMaxResults(1); if ($calendarUri !== null) { $query->andWhere($query->expr()->eq('c.uri', $query->createNamedParameter($calendarUri))); @@ -2671,6 +2680,7 @@ public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null) $stmt = $query->executeQuery(); $row = $stmt->fetchAssociative(); $stmt->closeCursor(); + if ($row) { return $row['calendaruri'] . '/' . $row['objecturi']; } From 657bffe8dd2703606f062de8770bcf753fabdc22 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 28 Jul 2026 23:22:16 -0400 Subject: [PATCH 2/4] chore(dav): tidy getCalendarObjectByUID for logical clarity Signed-off-by: Josh --- apps/dav/lib/CalDAV/CalDavBackend.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index b659b51b68ae1..ec57290400d1e 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -2670,13 +2670,14 @@ public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null) ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid))) ->andWhere($query->expr()->eq('co.calendartype', $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))) ->andWhere($query->expr()->isNull('co.deleted_at')) - ->andWhere($query->expr()->isNull('c.deleted_at')) - ->setMaxResults(1); + ->andWhere($query->expr()->isNull('c.deleted_at')); if ($calendarUri !== null) { $query->andWhere($query->expr()->eq('c.uri', $query->createNamedParameter($calendarUri))); } + $query->->setMaxResults(1); + $stmt = $query->executeQuery(); $row = $stmt->fetchAssociative(); $stmt->closeCursor(); From 9808efb08e43cfd9fb05ef14f5707e0dc1f963a2 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 29 Jul 2026 10:56:14 -0400 Subject: [PATCH 3/4] chore(dav): fix typo in CalDavBackend Signed-off-by: Josh --- 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 ec57290400d1e..94c4ded44a1b3 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -2676,7 +2676,7 @@ public function getCalendarObjectByUID($principalUri, $uid, $calendarUri = null) $query->andWhere($query->expr()->eq('c.uri', $query->createNamedParameter($calendarUri))); } - $query->->setMaxResults(1); + $query->setMaxResults(1); $stmt = $query->executeQuery(); $row = $stmt->fetchAssociative(); From 1afdbe46686844acad61415bee977e34724ec3a0 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 29 Jul 2026 11:17:27 -0400 Subject: [PATCH 4/4] docs(dav): improve CalDavBackend::getCalendarObjectByUID doc Signed-off-by: Josh --- apps/dav/lib/CalDAV/CalDavBackend.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 94c4ded44a1b3..42bf5b669c58c 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -2636,14 +2636,17 @@ public function searchPrincipalUri(string $principalUri, } /** - * Find the path of the calendar object with a given UID in calendars owned by - * a particular principal (user). + * Finds the path to the calendar object matching a given UID, restricted to + * calendars owned by the specified principal. Calendars owned by other + * principals, even if visible in the principal's calendar home (e.g. via + * sharing), are ignored. * - * When $calendarUri is provided, the lookup is restricted to that calendar. - * When it is null, all of the principal's calendars are searched. In that - * case, callers must use both path components returned by this method: the - * matching object may belong to a different calendar than one the caller - * currently has selected. + * When $calendarUri is provided, the lookup is further restricted to that + * calendar. It must be owned by $principalUri, otherwise no match is returned. + * When $calendarUri is null, all calendars owned by the principal are searched. + * In that case, callers must use both path components returned by this method: + * the matching object may belong to a different calendar than the one the + * caller currently has selected. * * Subscription and federated cached objects, deleted objects, and objects in * deleted calendars are not considered. @@ -2652,11 +2655,12 @@ public function searchPrincipalUri(string $principalUri, * "/". It is null when no matching object exists. * UID uniqueness is guaranteed only within a calendar collection. Therefore, * an unrestricted lookup can be ambiguous if multiple owned calendars contain - * the same UID. + * the same UID; in that case, one database-dependent matching result is + * returned and no calendar is preferred. * * @param string $principalUri * @param string $uid - * @param string|null $calendarUri Calendar URI to restrict the lookup to. + * @param string|null $calendarUri Calendar URI to restrict the lookup to; must be owned by $principalUri. * @return string|null */ #[\Override]