diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php index 085675b8680e2..beb654777831e 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php @@ -97,14 +97,15 @@ protected function generateObjectParameter(array $eventData, string $affectedUse // The calendar app needs to be manually loaded for the routes to be loaded OC_App::loadApp('calendar'); $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'] . '/' . $linkData['calendar_uri'] . '/' . $linkData['object_uri']); + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $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 . '/' . $linkData['calendar_uri'] . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $affectedUser . '/' . $calendarUri . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); } $link = [ 'view' => 'dayGridMonth', @@ -262,4 +263,16 @@ private function generateClassifiedObjectParameter(array $eventData, string $aff } return $parameter; } + + /** + * Return urlencoded string but with lower cased hex sequences. + * The remaining casing will be untouched. + */ + private function urlencodeLowerHex(string $raw): string { + return preg_replace_callback( + '/%[0-9A-F]{2}/', + static fn (array $matches) => strtolower($matches[0]), + urlencode($raw), + ); + } } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php index 33fbc7a7432a3..bfa324aa0e971 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php @@ -146,17 +146,58 @@ public function testGenerateObjectParameter(int $id, string $name, ?array $link, $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, $affectedUser])); } - public function testGenerateObjectParameterWithSharedCalendar(): void { - $link = [ - 'object_uri' => 'someuuid.ics', - 'calendar_uri' => 'personal', - 'owner' => 'sharer' + public static function generateObjectParameterLinkEncodingDataProvider(): array { + return [ + [ // Shared calendar + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'personal', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/personal_shared_by_sharer/someuuid.ics'), + ], + [ // Shared calendar with umlauts + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'umlaut_äüöß', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/umlaut_%c3%a4%c3%bc%c3%b6%c3%9f_shared_by_sharer/someuuid.ics'), + ], + [ // Shared calendar with umlauts and mixed casing + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'Umlaut_äüöß', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/Umlaut_%c3%a4%c3%bc%c3%b6%c3%9f_shared_by_sharer/someuuid.ics'), + ], + [ // Owned calendar with umlauts + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'umlaut_äüöß', + 'owner' => 'sharee' + ], + base64_encode('/remote.php/dav/calendars/sharee/umlaut_%c3%a4%c3%bc%c3%b6%c3%9f/someuuid.ics'), + ], + [ // Owned calendar with umlauts and mixed casing + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'Umlaut_äüöß', + 'owner' => 'sharee' + ], + base64_encode('/remote.php/dav/calendars/sharee/Umlaut_%c3%a4%c3%bc%c3%b6%c3%9f/someuuid.ics'), + ], ]; + } + + /** @dataProvider generateObjectParameterLinkEncodingDataProvider */ + public function testGenerateObjectParameterLinkEncoding(array $link, string $objectId): void { $generatedLink = [ 'view' => 'dayGridMonth', 'timeRange' => 'now', 'mode' => 'sidebar', - 'objectId' => base64_encode('/remote.php/dav/calendars/sharee/' . $link['calendar_uri'] . '_shared_by_sharer/' . $link['object_uri']), + 'objectId' => $objectId, 'recurrenceId' => 'next' ]; $this->appManager->expects($this->once())