Skip to content

Commit 481aaf1

Browse files
kesselbhamza221
authored andcommitted
fix(carddav): return correct sync token for non-truncated requests
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
1 parent f5ddac1 commit 481aaf1

File tree

6 files changed

+44
-17
lines changed

6 files changed

+44
-17
lines changed

apps/dav/appinfo/v1/carddav.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
\OC::$server->getUserManager(),
5757
\OC::$server->get(IEventDispatcher::class),
5858
\OC::$server->get(\OCA\DAV\CardDAV\Sharing\Backend::class),
59+
\OC::$server->get(OCP\IConfig::class),
60+
5961
);
6062

6163
$debugging = \OC::$server->getConfig()->getSystemValue('debug', false);

apps/dav/lib/CardDAV/CardDavBackend.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,18 +920,20 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel,
920920

921921
// Fetching all changes
922922
$stmt = $qb->executeQuery();
923+
$rowCount = $stmt->rowCount();
923924

924925
$changes = [];
926+
$highestSyncToken = 0;
925927

926928
// This loop ensures that any duplicates are overwritten, only the
927929
// last change on a node is relevant.
928930
while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
929931
$changes[$row['uri']] = $row['operation'];
930-
// get the last synctoken, needed in case a limit was set
931-
$result['syncToken'] = $row['synctoken'];
932+
$highestSyncToken = $row['synctoken'];
932933
}
934+
933935
$stmt->closeCursor();
934-
936+
935937
// No changes found, use current token
936938
if (empty($changes)) {
937939
$result['syncToken'] = $currentToken;
@@ -950,6 +952,20 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel,
950952
break;
951953
}
952954
}
955+
956+
/*
957+
* The synctoken in oc_addressbooks is always the highest synctoken in oc_addressbookchanges for a given addressbook plus one (see addChange).
958+
*
959+
* For truncated results, it is expected that we return the highest token from the response, so the client can continue from the latest change.
960+
*
961+
* For non-truncated results, it is expected to return the currentToken. If we return the highest token, as with truncated results, the client will always think it is one change behind.
962+
*
963+
* Therefore, we differentiate between truncated and non-truncated results when returning the synctoken.
964+
*/
965+
if ($rowCount === $limit && $highestSyncToken < $currentToken) {
966+
$result['syncToken'] = $highestSyncToken;
967+
$result['result_truncated'] = true;
968+
}
953969
} else {
954970
$qb = $this->db->getQueryBuilder();
955971
$qb->select('id', 'uri')

apps/dav/tests/unit/CardDAV/CardDavBackendTest.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class CardDavBackendTest extends TestCase {
5656
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */
5757
private $groupManager;
5858

59+
/** @var IConfig|MockObject */
60+
private $config;
61+
5962
/** @var IEventDispatcher|MockObject */
6063
private $dispatcher;
6164
private Backend $sharingBackend;
@@ -239,7 +242,7 @@ public function testAddressBookSharing(): void {
239242
public function testCardOperations(): void {
240243
/** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backend */
241244
$backend = $this->getMockBuilder(CardDavBackend::class)
242-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
245+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
243246
->onlyMethods(['updateProperties', 'purgeProperties'])->getMock();
244247

245248
// create a new address book
@@ -294,7 +297,7 @@ public function testCardOperations(): void {
294297

295298
public function testMultiCard(): void {
296299
$this->backend = $this->getMockBuilder(CardDavBackend::class)
297-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
300+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
298301
->setMethods(['updateProperties'])->getMock();
299302

300303
// create a new address book
@@ -347,7 +350,7 @@ public function testMultiCard(): void {
347350

348351
public function testMultipleUIDOnDifferentAddressbooks(): void {
349352
$this->backend = $this->getMockBuilder(CardDavBackend::class)
350-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
353+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
351354
->onlyMethods(['updateProperties'])->getMock();
352355

353356
// create 2 new address books
@@ -369,7 +372,7 @@ public function testMultipleUIDOnDifferentAddressbooks(): void {
369372

370373
public function testMultipleUIDDenied(): void {
371374
$this->backend = $this->getMockBuilder(CardDavBackend::class)
372-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
375+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
373376
->setMethods(['updateProperties'])->getMock();
374377

375378
// create a new address book
@@ -390,7 +393,7 @@ public function testMultipleUIDDenied(): void {
390393

391394
public function testNoValidUID(): void {
392395
$this->backend = $this->getMockBuilder(CardDavBackend::class)
393-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
396+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
394397
->setMethods(['updateProperties'])->getMock();
395398

396399
// create a new address book
@@ -447,7 +450,7 @@ public function testDeleteWithoutCard(): void {
447450

448451
public function testSyncSupport(): void {
449452
$this->backend = $this->getMockBuilder(CardDavBackend::class)
450-
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
453+
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
451454
->setMethods(['updateProperties'])->getMock();
452455

453456
// create a new address book

apps/dav/tests/unit/CardDAV/SyncServiceTest.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public function testEmptySync(): void {
104104
'1',
105105
'principals/system/system',
106106
[]
107-
)['token'];
107+
)[0];
108108

109109
$this->assertEquals('http://sabre.io/ns/sync/1', $token);
110110
}
@@ -175,7 +175,7 @@ public function testSyncWithNewElement(): void {
175175
'1',
176176
'principals/system/system',
177177
[]
178-
)['token'];
178+
)[0];
179179

180180
$this->assertEquals('http://sabre.io/ns/sync/2', $token);
181181
}
@@ -246,7 +246,7 @@ public function testSyncWithUpdatedElement(): void {
246246
'1',
247247
'principals/system/system',
248248
[]
249-
)['token'];
249+
)[0];
250250

251251
$this->assertEquals('http://sabre.io/ns/sync/3', $token);
252252
}
@@ -287,7 +287,7 @@ public function testSyncWithDeletedElement(): void {
287287
'1',
288288
'principals/system/system',
289289
[]
290-
)['token'];
290+
)[0];
291291

292292
$this->assertEquals('http://sabre.io/ns/sync/4', $token);
293293
}
@@ -430,7 +430,7 @@ public function testDeleteAddressbookWhenAccessRevoked(): void {
430430
'1',
431431
'principals/system/system',
432432
[]
433-
)['token'];
433+
);
434434
}
435435

436436
/**
@@ -472,7 +472,7 @@ public function testUseAbsoluteUriReport(string $host, string $expected): void {
472472
'1',
473473
'principals/system/system',
474474
[]
475-
)['token'];
475+
);
476476
}
477477

478478
public function providerUseAbsoluteUriReport(): array {

apps/federation/tests/SyncFederationAddressbooksTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function testSync(): void {
5555
->disableOriginalConstructor()
5656
->getMock();
5757
$syncService->expects($this->once())->method('syncRemoteAddressBook')
58-
->willReturn('1');
58+
->willReturn(['1', false]);
5959

6060
/** @var SyncService $syncService */
6161
$s = new SyncFederationAddressBooks($dbHandler, $syncService, $this->discoveryService, $this->logger);
@@ -114,7 +114,7 @@ public function testSuccessfulSyncWithoutChangesAfterFailure(): void {
114114
->disableOriginalConstructor()
115115
->getMock();
116116
$syncService->expects($this->once())->method('syncRemoteAddressBook')
117-
->willReturn('0');
117+
->willReturn(['0', false]);
118118

119119
/** @var SyncService $syncService */
120120
$s = new SyncFederationAddressBooks($dbHandler, $syncService, $this->discoveryService, $this->logger);

config/config.sample.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,12 @@
346346
*/
347347
'carddav_sync_request_timeout' => 30,
348348

349+
/**
350+
* The limit applied to the synchronization report request, e.g. federated system address books (as run by `occ federation:sync-addressbooks`).
351+
*/
352+
'carddav_sync_request_truncation' => 2500,
353+
354+
349355
/**
350356
* `true` enabled a relaxed session timeout, where the session timeout would no longer be
351357
* handled by Nextcloud but by either the PHP garbage collection or the expiration of

0 commit comments

Comments
 (0)