From 2da2c93ac069f32bd517bab1b7c81b7660b82181 Mon Sep 17 00:00:00 2001 From: OttoDev Date: Fri, 10 Jul 2026 23:11:16 +1000 Subject: [PATCH] fix(caldav): url-encode owner/user in activity event links Activity links break when a uid has a space, since owner/affectedUser weren't url-encoded the way calendar_uri already is. Wrapped both in rawurlencode(), same fix joshtrichards okayed on the issue. Added two test cases for uids with spaces and confirmed they fail without the fix. Fixes #61738 Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: OttoDev --- .../lib/CalDAV/Activity/Provider/Event.php | 4 ++-- .../CalDAV/Activity/Provider/EventTest.php | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php index dec14b8e5e4a5..93e7ed889ffb8 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php @@ -72,13 +72,13 @@ protected function generateObjectParameter(array $eventData, string $affectedUse $linkData = $eventData['link']; $calendarUri = $this->urlencodeLowerHex($linkData['calendar_uri']); if ($affectedUser === $linkData['owner']) { - $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $linkData['owner'] . '/' . $calendarUri . '/' . $linkData['object_uri']); + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . rawurlencode($linkData['owner']) . '/' . $calendarUri . '/' . $linkData['object_uri']); } else { // Can't use the "real" owner and calendar names here because we create a custom // calendar for incoming shares with the name "_shared_by_". // Hack: Fix the link by generating it for the incoming shared calendar instead, // as seen from the affected user. - $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $affectedUser . '/' . $calendarUri . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . rawurlencode($affectedUser) . '/' . $calendarUri . '_shared_by_' . rawurlencode($linkData['owner']) . '/' . $linkData['object_uri']); } $params['link'] = $this->url->linkToRouteAbsolute('calendar.view.indexdirect.edit', [ 'objectId' => $objectId, diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php index 287bbf6f96573..3f58ed0f60354 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php @@ -146,11 +146,29 @@ public static function generateObjectParameterLinkEncodingDataProvider(): array ], base64_encode('/remote.php/dav/calendars/sharee/Umlaut_%c3%a4%c3%bc%c3%b6%c3%9f/someuuid.ics'), ], + [ // Owned calendar, uid contains a space + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'personal', + 'owner' => 'Elisa Ciria' + ], + base64_encode('/remote.php/dav/calendars/Elisa%20Ciria/personal/someuuid.ics'), + 'Elisa Ciria', + ], + [ // Shared calendar, owner and affected user uids both contain a space + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'personal', + 'owner' => 'Elisa Ciria' + ], + base64_encode('/remote.php/dav/calendars/John%20Doe/personal_shared_by_Elisa%20Ciria/someuuid.ics'), + 'John Doe', + ], ]; } #[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'generateObjectParameterLinkEncodingDataProvider')] - public function testGenerateObjectParameterLinkEncoding(array $link, string $objectId): void { + public function testGenerateObjectParameterLinkEncoding(array $link, string $objectId, string $affectedUser = 'sharee'): void { $generatedLink = [ 'objectId' => $objectId, ]; @@ -171,7 +189,7 @@ public function testGenerateObjectParameterLinkEncoding(array $link, string $obj 'name' => 'calendar', 'link' => 'fullLink', ]; - $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, 'sharee'])); + $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, $affectedUser])); } public static function dataGenerateObjectParameterThrows(): array {