Skip to content

fix(dav): handle Thunderbird accept-before-sync UID collisions#61207

Open
ndo84bw wants to merge 2 commits into
nextcloud:masterfrom
ndo84bw:fix/54471-thunderbird-put-uid-collision
Open

fix(dav): handle Thunderbird accept-before-sync UID collisions#61207
ndo84bw wants to merge 2 commits into
nextcloud:masterfrom
ndo84bw:fix/54471-thunderbird-put-uid-collision

Conversation

@ndo84bw

@ndo84bw ndo84bw commented Jun 11, 2026

Copy link
Copy Markdown

Summary

This takes over and completes #54471 with agreement (#54471 (comment)).

The problem

When someone is invited to an event, the server's scheduling code immediately writes a copy of that event into the invitee's personal calendar (under a server-generated object name). If the invitee opens the invitation email in Thunderbird and clicks Accept/Decline before their calendar has synced, Thunderbird does not yet know that object name. It therefore sends the PUT to a name it guesses itself. Because the event UID already exists in the calendar, the server rejects the PUT and Thunderbird shows error 80004005; the acceptance is lost.

What this PR does

Commit 1 (original work by @st3iny, rebased onto master, fixup commits squashed): a Sabre plugin that, for a Thunderbird PUT of a calendar object whose UID already exists in the same target calendar, rewrites the request URI to the existing object so the PUT updates it instead of failing on the collision.

Commit 2 (this take-over): completes and hardens that plugin.

  1. Deliver the organizer's reply. Thunderbird's accept-before-sync body stamps the organizer with SCHEDULE-AGENT=CLIENT. That parameter tells the server not to perform scheduling, so no iTip REPLY is generated and the organizer never learns of the acceptance (they stay NEEDS-ACTION, no email). An attendee is not allowed to change the ORGANIZER property (RFC 5546), so the plugin restores SCHEDULE-AGENT to the value of the stored event. This is a no-op when the organizer was genuinely client-scheduled (the stored copy already carries CLIENT) and re-enables the reply in the common server-scheduled case.

  2. Let the rewritten PUT through. Thunderbird sends If-None-Match: * because it believes it is creating a new object. After the URI has been rewritten to an existing object that precondition would fail with 412, so the header is dropped on the rewrite path.

  3. Only act on a genuine accept-before-sync. The plugin now returns early when Thunderbird already targets the object's real URI (i.e. the calendar was already synced). This avoids touching the body on a normal update - in particular it never strips a SCHEDULE-AGENT that the organizer set on their own copy.

  4. Authorization for the rewrite target. The plugin now runs before the ACL plugin (event priority 19 vs. the ACL plugin's 20; the current user principal is provided by the auth plugin at priority 10 and is available either way). The ACL plugin only checks PUT privileges when the target node exists; the original (guessed) URI never exists, so without this change the privilege check was skipped and not repeated after the rewrite. Running first means the ACL plugin evaluates {DAV:}write-content against the rewritten, existing object.

  5. Scope the collision lookup. The lookup is restricted to the calendar the PUT targets (calendars/{principal}/{calendar}/...) and excludes subscription/federated cache rows (calendartype) and trashed objects/calendars (deleted_at). This prevents rewriting onto an object in a different calendar of the same user, onto a read-only subscription cache, or onto a deleted object.

Testing

Manually verified on a live Nextcloud 33.0.3 instance (MariaDB, Apache/mod_php) with this patch applied, using Thunderbird as the invitee/organizer client:

  • Accept before the calendar syncs: no error (previously 80004005), the PUT succeeds
  • Organizer receives the iTip REPLY: the attendee shows as accepted in the organizer's web calendar and the notification email is delivered
  • A subsequent manual sync in Thunderbird reconciles to a single event (matched by UID), still accepted, no duplicate
  • Accept after the calendar has synced: unchanged behavior, the plugin does not interfere, organizer is notified
  • Decline before the calendar syncs: works the same way, organizer sees the declined state and is notified
  • Organizer edits their own already-synced event in Thunderbird: the change is saved, attendees receive exactly one update (no duplicate), confirming the plugin leaves a known object untouched

Unit tests (apps/dav/tests/unit/Connector/Sabre/ThunderbirdPutInvitationQuirkPluginTest.php) cover the URI rewrite, the If-None-Match removal, the early return when the object URI already matches, and the SCHEDULE-AGENT restore (restored from the stored organizer, left untouched when already equal, dropped when the stored organizer has none, and skipped when the stored data is unparsable). 17 tests pass locally with the flags CI uses (--fail-on-warning --fail-on-risky).

The authorization change (item 4) is not reproduced as a live exploit here; it is established from the Sabre ACL plugin source (the privilege check is bound to beforeMethod and skipped for a non-existent node, with no second check on the update path) and covered by the unit test asserting the registration priority.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

st3iny and others added 2 commits June 11, 2026 12:33
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
Signed-off-by: Nico Donath <ndo84bw@gmx.de>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Nico Donath <ndo84bw@gmx.de>
@ndo84bw ndo84bw requested a review from a team as a code owner June 11, 2026 12:42
@ndo84bw ndo84bw requested review from Altahrim, come-nc, provokateurin and salmart-dev and removed request for a team June 11, 2026 12:43
@ndo84bw ndo84bw changed the title Fix/54471 thunderbird put uid collision fix(dav): handle Thunderbird accept-before-sync UID collisions Jun 11, 2026
@susnux susnux added the community pull requests from community label Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community pull requests from community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error when accepting a calendar invite on Thunderbird: Processing message failed. Status: 80004005.

3 participants