From df787c4c5d51095c02b01921b366c41aa17385db Mon Sep 17 00:00:00 2001 From: mooons <10822203+mooons@users.noreply.github.com> Date: Wed, 6 May 2026 03:26:11 +0000 Subject: [PATCH] fix(dav): use absolute cutoff for sync token pruning Signed-off-by: mooons <10822203+mooons@users.noreply.github.com> --- apps/dav/lib/CalDAV/CalDavBackend.php | 4 +++- apps/dav/lib/CardDAV/CardDavBackend.php | 4 +++- apps/dav/tests/unit/CalDAV/CalDavBackendTest.php | 11 ++++++----- apps/dav/tests/unit/CardDAV/CardDavBackendTest.php | 11 ++++++----- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 2aac4264df830..c7a57cf57286d 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -3909,11 +3909,13 @@ public function pruneOutdatedSyncTokens(int $keep, int $retention): int { return 0; } + $cutoff = max(0, time() - $retention); + $query = $this->db->getQueryBuilder(); $query->delete('calendarchanges') ->where( $query->expr()->lte('id', $query->createNamedParameter($maxId - $keep, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), - $query->expr()->lte('created_at', $query->createNamedParameter($retention)), + $query->expr()->lte('created_at', $query->createNamedParameter($cutoff, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), ); return $query->executeStatement(); } diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 4768a863908a3..f5573e7306705 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -1528,11 +1528,13 @@ public function pruneOutdatedSyncTokens(int $keep, int $retention): int { return 0; } + $cutoff = max(0, time() - $retention); + $query = $this->db->getQueryBuilder(); $query->delete('addressbookchanges') ->where( $query->expr()->lte('id', $query->createNamedParameter($maxId - $keep, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), - $query->expr()->lte('created_at', $query->createNamedParameter($retention)), + $query->expr()->lte('created_at', $query->createNamedParameter($cutoff, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT), ); return $query->executeStatement(); } diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 80b7c7d8e0019..a34da86a6d3b6 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -1534,11 +1534,12 @@ public function testPruneOutdatedSyncTokens(): void { EOD; $this->backend->updateCalendarObject($calendarId, $uri, $calData); - // Keep everything - $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0); + // Keep everything by using a retention duration larger than the row age + $deleted = $this->backend->pruneOutdatedSyncTokens(0, time()); self::assertSame(0, $deleted); - $deleted = $this->backend->pruneOutdatedSyncTokens(0, time()); + // A retention duration of 0 means everything older than "now" is eligible + $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0); // At least one from the object creation and one from the object update $this->assertGreaterThanOrEqual(2, $deleted); $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1); @@ -1604,7 +1605,7 @@ public function testPruneOutdatedSyncTokens(): void { $this->assertEmpty($changes['deleted']); // Delete all but last change - $deleted = $this->backend->pruneOutdatedSyncTokens(1, time()); + $deleted = $this->backend->pruneOutdatedSyncTokens(1, 0); $this->assertEquals(1, $deleted); // We had two changes before, now one // Only update should remain @@ -1614,7 +1615,7 @@ public function testPruneOutdatedSyncTokens(): void { $this->assertEmpty($changes['deleted']); // Check that no crash occurs when prune is called without current changes - $deleted = $this->backend->pruneOutdatedSyncTokens(1, time()); + $deleted = $this->backend->pruneOutdatedSyncTokens(1, 0); self::assertSame(0, $deleted); } diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 9badcf36fae2f..c5ee414a1d6fd 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -923,11 +923,12 @@ public function testPruneOutdatedSyncTokens(): void { $this->backend->createCard($addressBookId, $uri, $this->vcardTest0); $this->backend->updateCard($addressBookId, $uri, $this->vcardTest1); - // Do not delete anything if week data as old as ts=0 - $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0); + // Keep everything by using a retention duration larger than the row age + $deleted = $this->backend->pruneOutdatedSyncTokens(0, time()); self::assertSame(0, $deleted); - $deleted = $this->backend->pruneOutdatedSyncTokens(0, time()); + // A retention duration of 0 means everything older than "now" is eligible + $deleted = $this->backend->pruneOutdatedSyncTokens(0, 0); // At least one from the object creation and one from the object update $this->assertGreaterThanOrEqual(2, $deleted); $changes = $this->backend->getChangesForAddressBook($addressBookId, $syncToken, 1); @@ -959,7 +960,7 @@ public function testPruneOutdatedSyncTokens(): void { $this->assertEmpty($changes['deleted']); // Delete all but last change - $deleted = $this->backend->pruneOutdatedSyncTokens(1, time()); + $deleted = $this->backend->pruneOutdatedSyncTokens(1, 0); $this->assertEquals(1, $deleted); // We had two changes before, now one // Only update should remain @@ -969,6 +970,6 @@ public function testPruneOutdatedSyncTokens(): void { $this->assertEmpty($changes['deleted']); // Check that no crash occurs when prune is called without current changes - $deleted = $this->backend->pruneOutdatedSyncTokens(1, time()); + $deleted = $this->backend->pruneOutdatedSyncTokens(1, 0); } }