Skip to content

fix(contacts): do not let one broken contact blank the whole app#5564

Open
MiMoHo wants to merge 1 commit into
nextcloud:mainfrom
MiMoHo:fix/broken-contact-must-not-blank-app
Open

fix(contacts): do not let one broken contact blank the whole app#5564
MiMoHo wants to merge 1 commit into
nextcloud:mainfrom
MiMoHo:fix/broken-contact-must-not-blank-app

Conversation

@MiMoHo

@MiMoHo MiMoHo commented Jul 12, 2026

Copy link
Copy Markdown

Summary

A single contact with one malformed property could take down the entire web UI, leaving the user with "no address books" and no way to recover from within the app. This PR fortifies the contact loading pipeline so that one broken contact can, at worst, only affect itself.

Failure mechanism (as diagnosed in #5149 and #5250)

  1. ical.js getters on Contact can throw for malformed/exotic property values. Two confirmed classes:
  2. The throw happens inside the sortContacts mutation, which iterates all contacts of all address books.
  3. The exception propagates into the .catch() of getContactsFromAddressBook, which was written for DAV fetch errors and responds by deleting the address book from the store.
  4. Because sortContacts is global, the same broken contact makes the commit fail for every address book being loaded → all address books get removed from the store → the app shows no address books and no contacts at all, and even the settings for deleting/disabling the offending address book become unreachable.

Changes

  • Contact.uid now reads the raw jCal value (always a plain string) instead of the parsed value object, so uid/key access never throws. Identity access must be infallible — key is used as store index everywhere.
  • The sortContacts mutation skips (and logs) contacts whose property getters throw, instead of failing the whole list.
  • getContactsFromAddressBook only removes the address book from the store when the DAV fetch itself fails. If processing the successfully fetched contacts fails, the address book stays visible and an error toast is shown — the data on the server is fine.

Relationship to other work

Complementary to #5265, which makes the last modified sort order actually work with malformed REV values (numeric sort keys read from raw jCal). This PR is about containment: no single contact may blank the app, whatever the sort order. Together they fully cover #5250.

Checklist

Resolve #5149
Helps with #5250 (together with #5265)

🤖 Generated with Claude Code

A single contact with a malformed property (e.g. a compact
REV:20230911 as written by Thunderbird CardBook or DAVx5, or a
value-typed UID) could take down the entire web UI: the throwing
ical.js getter crashed the sortContacts mutation for all contacts,
and the surrounding catch in getContactsFromAddressBook then removed
every address book from the store, leaving the user with 'no address
books' and no way to recover.

- read the raw jCal value in the uid getter so uid/key access never
  throws for value-typed UID properties
- skip contacts whose property getters throw while sorting instead of
  failing the whole contacts list
- only remove an address book from the store when the DAV fetch
  itself fails, not when processing the fetched contacts fails

Resolves nextcloud#5149
Helps with nextcloud#5250 (making the last-modified sort order work with
malformed REV values is handled separately in nextcloud#5265)

Assisted-by: Claude:claude-fable-5
Signed-off-by: MiMoHo <37556964+MiMoHo@users.noreply.github.com>
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.36842% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/models/contact.js 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parse errors in a contact make the entire app UI fail and show no contacts at all

1 participant