From f48dc47177d01d6b44b79f126e2087241033ca9b Mon Sep 17 00:00:00 2001 From: TowyTowy Date: Wed, 15 Jul 2026 11:26:45 +0200 Subject: [PATCH] Fix: text-match filter crashes on structured property (vCard N/ADR) A CardDAV addressbook-query REPORT with a text-match prop-filter on a structured property (e.g. N or ADR) returned HTTP 500. vobject parses these into Name/Address objects rather than plain strings, so text_match called .lower() on a non-string and raised AttributeError. Coerce non-string values to their text representation before matching. Co-Authored-By: Claude --- CHANGELOG.md | 1 + radicale/item/filter.py | 5 +++++ radicale/tests/test_base.py | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 940285ad..036c34bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.7.7.dev * Fix: time-range filter treated a VEVENT with a whole-day DURATION (e.g. P1D, P2D) as zero-length (timedelta.seconds instead of total_seconds), so such events were missing from calendar-query REPORT results * Fix: calendar-data expand (REPORT) left recurrence properties (e.g. RDATE) on the expanded single-occurrence VEVENTs; a single try/except around the sequential delattr() calls stopped at the first absent property (e.g. missing EXDATE), so later ones were never removed +* Fix: text-match filter on a structured property (e.g. vCard N or ADR) crashed with HTTP 500 (AttributeError: 'Name'/'Address' object has no attribute 'lower') because vobject parses these into non-string objects; their text representation is now used ## 3.7.6 * Extension: item verification on commandline diff --git a/radicale/item/filter.py b/radicale/item/filter.py index 46462487..74e571ca 100644 --- a/radicale/item/filter.py +++ b/radicale/item/filter.py @@ -578,6 +578,11 @@ def text_match(vobject_item: vobject.base.Component, match_type = filter_.get("match-type", match_type) def match(value: str) -> bool: + if not isinstance(value, str): + # Some properties (e.g. N and ADR in vCard) are parsed by vobject + # into structured objects instead of plain strings. Use their text + # representation so text-match doesn't crash with AttributeError. + value = str(value) value = value.lower() if match_type == "equals": return value == text diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 381d6bed..effa0d1e 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -1458,6 +1458,18 @@ permissions: RrWw""") test """], "contact", test="allof") + def test_addressbook_prop_filter_structured(self) -> None: + """text-match on a structured property (N) that vobject parses into a + non-string value must not crash the REPORT.""" + assert "/contacts.vcf/contact1.vcf" in self._test_filter(["""\ + + contact +"""], "contact") + assert "/contacts.vcf/contact1.vcf" not in self._test_filter(["""\ + + nonexistent +"""], "contact") + def test_calendar_empty_filter(self) -> None: self._test_filter([""])