From 11315704b75a197eab4fcdf4da3ab955147cae9e Mon Sep 17 00:00:00 2001 From: Hamza Date: Thu, 21 Aug 2025 18:07:47 +0100 Subject: [PATCH] fix(cardav): return correct card version on report Signed-off-by: Hamza --- apps/contactsinteraction/lib/Card.php | 4 ++ apps/dav/lib/CardDAV/Card.php | 5 ++ apps/dav/lib/CardDAV/Plugin.php | 85 +++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/apps/contactsinteraction/lib/Card.php b/apps/contactsinteraction/lib/Card.php index bf0acca7bd5..58c4aac5d0d 100644 --- a/apps/contactsinteraction/lib/Card.php +++ b/apps/contactsinteraction/lib/Card.php @@ -66,6 +66,10 @@ class Card implements ICard, IACL { return 'text/vcard; charset=utf-8'; } + public function getVersion(): ?string { + return '3.0'; + } + /** * @inheritDoc */ diff --git a/apps/dav/lib/CardDAV/Card.php b/apps/dav/lib/CardDAV/Card.php index 8cd4fd7e5ee..a0c445cf4d1 100644 --- a/apps/dav/lib/CardDAV/Card.php +++ b/apps/dav/lib/CardDAV/Card.php @@ -29,6 +29,11 @@ class Card extends \Sabre\CardDAV\Card { return (int)$this->cardData['addressbookid']; } + public function getVersion(): ?string { + preg_match('/^VERSION:([34])\.0/mi', $this->cardData['carddata'], $matches); + return isset($matches[1]) ? $matches[1] . '.0' : null; + } + public function getPrincipalUri(): string { return $this->addressBookInfo['principaluri']; } diff --git a/apps/dav/lib/CardDAV/Plugin.php b/apps/dav/lib/CardDAV/Plugin.php index 0ec10306ceb..ba22328be26 100644 --- a/apps/dav/lib/CardDAV/Plugin.php +++ b/apps/dav/lib/CardDAV/Plugin.php @@ -8,6 +8,7 @@ namespace OCA\DAV\CardDAV; use OCA\DAV\CardDAV\Xml\Groups; +use Sabre\DAV\Exception\ReportNotSupported; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; @@ -55,4 +56,88 @@ class Plugin extends \Sabre\CardDAV\Plugin { }); } } + + /** + * This function handles the addressbook-query REPORT. + * + * This report is used by the client to filter an addressbook based on a + * complex query. + * + * @param \Sabre\CardDAV\Xml\Request\AddressBookQueryReport $report + */ + protected function addressbookQueryReport($report) { + $depth = $this->server->getHTTPDepth(0); + + if ($depth == 0) { + $candidateNodes = [ + $this->server->tree->getNodeForPath($this->server->getRequestUri()), + ]; + if (!$candidateNodes[0] instanceof Card) { + throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0'); + } + } else { + $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); + } + + $validNodes = []; + foreach ($candidateNodes as $node) { + if (!$node instanceof Card) { + continue; + } + + $blob = $node->get(); + if (is_resource($blob)) { + $blob = stream_get_contents($blob); + } + + if (!$this->validateFilters($blob, $report->filters, $report->test)) { + continue; + } + + $validNodes[] = $node; + + if ($report->limit && $report->limit <= count($validNodes)) { + // We hit the maximum number of items, we can stop now. + break; + } + } + + $result = []; + foreach ($validNodes as $validNode) { + $contentType = $report->contentType; + // we theoretically support versions 3.0 and 4.0 so $vcardType should be dyncamic depending on the node + if ($validNode->getVersion()) { + $contentType .= '; version=' . $validNode->getVersion(); + } elseif ($report->version) { + $contentType .= '; version=' . $report->version; + } + $vcardType = $this->negotiateVCard( + $contentType + ); + if ($depth == 0) { + $href = $this->server->getRequestUri(); + } else { + $href = $this->server->getRequestUri() . '/' . $validNode->getName(); + } + + /** @psalm-suppress DeprecatedMethod */ + [$props] = $this->server->getPropertiesForPath($href, $report->properties, 0); + + if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) { + $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard( + $props[200]['{' . self::NS_CARDDAV . '}address-data'], + $vcardType, + $report->addressDataProperties + ); + } + $result[] = $props; + } + + $prefer = $this->server->getHTTPPrefer(); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); + $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); + } }