From fcc45a3f44eea677708da328763e29ce95a43877 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Sat, 9 May 2026 02:40:47 +0800 Subject: [PATCH] fix(server): caldav compatibility (#14930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #14411 fix #14909 Some CalDAV servers do not implement standard responses; add compatibility for these servers. #### PR Dependency Tree * **PR #14930** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **Bug Fixes** * Improved CalDAV discovery error handling to gracefully fall back when the server returns certain error statuses. * **New Features** * CalDAV account linking now returns the number of discovered calendars associated with the account. --- .../plugins/calendar/__tests__/caldav.spec.ts | 25 ++++++++++++++++++- .../src/plugins/calendar/providers/caldav.ts | 2 +- .../server/src/plugins/calendar/service.ts | 5 +++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/backend/server/src/plugins/calendar/__tests__/caldav.spec.ts b/packages/backend/server/src/plugins/calendar/__tests__/caldav.spec.ts index 8e2e636143..ed9d5a2614 100644 --- a/packages/backend/server/src/plugins/calendar/__tests__/caldav.spec.ts +++ b/packages/backend/server/src/plugins/calendar/__tests__/caldav.spec.ts @@ -147,6 +147,7 @@ const calendarQueryResponse = ` `; const createCalDAVServer = async (options?: { + discoveryGetStatus?: number; syncCollectionStatus?: number; }) => { const requests: Array<{ method: string; url: string; body: string }> = []; @@ -169,6 +170,16 @@ const createCalDAVServer = async (options?: { } if (req.url === '/.well-known/caldav') { + if (options?.discoveryGetStatus) { + res.writeHead(options.discoveryGetStatus, { + 'Content-Type': 'application/xml', + }); + res.end(` + + Sabre\\DAV\\Exception\\NotImplemented +`); + return; + } res.writeHead(302, { Location: '/caldav/' }); res.end(); return; @@ -275,7 +286,7 @@ const createCalendarModule = async (caldavConfig: Record) => { }; test('linkCalDAVAccount discovers calendars and parses events', async t => { - const server = await createCalDAVServer(); + const server = await createCalDAVServer({ discoveryGetStatus: 501 }); t.teardown(() => server.server.close()); const module = await createCalendarModule({ @@ -311,6 +322,18 @@ test('linkCalDAVAccount discovers calendars and parses events', async t => { account.id ); t.is(subscriptions.length, 1); + t.is(account.calendarsCount, 1); + t.true( + server.requests.some( + request => + request.method === 'GET' && request.url === '/.well-known/caldav' + ) + ); + t.true( + server.requests.some( + request => request.method === 'PROPFIND' && request.url === '/caldav/' + ) + ); const events = await models.calendarEvent.listBySubscriptionsInRange( [subscriptions[0].id], diff --git a/packages/backend/server/src/plugins/calendar/providers/caldav.ts b/packages/backend/server/src/plugins/calendar/providers/caldav.ts index a3e9c27ee0..1a31a1b447 100644 --- a/packages/backend/server/src/plugins/calendar/providers/caldav.ts +++ b/packages/backend/server/src/plugins/calendar/providers/caldav.ts @@ -1014,7 +1014,7 @@ export class CalDAVProvider extends CalendarProvider { if (response.ok) { return response.url; } - if ([400, 404, 405].includes(response.status)) { + if ([400, 404, 405, 501].includes(response.status)) { return serverUrl; } const text = await response.text(); diff --git a/packages/backend/server/src/plugins/calendar/service.ts b/packages/backend/server/src/plugins/calendar/service.ts index dc988d8b8a..8367ff0dca 100644 --- a/packages/backend/server/src/plugins/calendar/service.ts +++ b/packages/backend/server/src/plugins/calendar/service.ts @@ -272,7 +272,10 @@ export class CalendarService { throw error; } - return account; + const calendars = await this.models.calendarSubscription.listByAccount( + account.id + ); + return { ...account, calendarsCount: calendars.length }; } async syncAccountCalendars(accountId: string) {