From 5b2cb5c0bfe22398c48aec7d0941b26e90539d18 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 1 Dec 2025 18:50:46 +0100 Subject: [PATCH 1/5] add new test cases for broken items --- radicale/tests/static/broken-vcards.vcf | 16 +++++++ .../tests/static/broken-vcards2-no_uid.vcf | 16 +++++++ radicale/tests/static/broken-vcards2.vcf | 17 ++++++++ radicale/tests/static/broken-vevents.ics | 25 +++++++++++ radicale/tests/test_base.py | 42 +++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 radicale/tests/static/broken-vcards.vcf create mode 100644 radicale/tests/static/broken-vcards2-no_uid.vcf create mode 100644 radicale/tests/static/broken-vcards2.vcf create mode 100644 radicale/tests/static/broken-vevents.ics diff --git a/radicale/tests/static/broken-vcards.vcf b/radicale/tests/static/broken-vcards.vcf new file mode 100644 index 00000000..19bd670b --- /dev/null +++ b/radicale/tests/static/broken-vcards.vcf @@ -0,0 +1,16 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +UID:C68582D2-2E60-0001-C2C0-000000000000.vcf +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example.com +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000000.vcf +END:VCARD +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +UID:C68582D2-2E60-0001-C2C0-000000000001.vcf +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example1.com +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000001.vcf +END:VCARD diff --git a/radicale/tests/static/broken-vcards2-no_uid.vcf b/radicale/tests/static/broken-vcards2-no_uid.vcf new file mode 100644 index 00000000..76a3dfb6 --- /dev/null +++ b/radicale/tests/static/broken-vcards2-no_uid.vcf @@ -0,0 +1,16 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +UID:C68582D2-2E60-0001-C2C0-000000000000.vcf +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example.com +FN:Test Example +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000000.vcf +END:VCARD +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example1.com +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000001.vcf +END:VCARD diff --git a/radicale/tests/static/broken-vcards2.vcf b/radicale/tests/static/broken-vcards2.vcf new file mode 100644 index 00000000..ca5fa3ac --- /dev/null +++ b/radicale/tests/static/broken-vcards2.vcf @@ -0,0 +1,17 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +UID:C68582D2-2E60-0001-C2C0-000000000000.vcf +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example.com +FN:Test Example +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000000.vcf +END:VCARD +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Inverse inc.//SOGo Connector 1.0//EN +UID:C68582D2-2E60-0001-C2C0-000000000001.vcf +X-MOZILLA-HTML:FALSE +EMAIL;TYPE=work:test-misses-N-or-FN@example1.com +X-RADICALE-NAME:C68582D2-2E60-0001-C2C0-000000000001.vcf +END:VCARD diff --git a/radicale/tests/static/broken-vevents.ics b/radicale/tests/static/broken-vevents.ics new file mode 100644 index 00000000..bbfbcd0b --- /dev/null +++ b/radicale/tests/static/broken-vevents.ics @@ -0,0 +1,25 @@ +BEGIN:VCALENDAR +PRODID:-//Radicale//NONSGML Radicale Server//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20160725T060147Z +LAST-MODIFIED:20160727T193435Z +DTSTAMP:20160727T193435Z +UID:040000008200E00074C5B7101A82E00800000000 +SUMMARY:Good ICS +STATUS:CONFIRMED +X-MOZ-LASTACK:20160727T193435Z +DTSTART;TZID=Europe/Budapest:20160727T170000 +DTEND;TZID=Europe/Budapest:20160727T223000 +CLASS:PUBLIC +X-LIC-ERROR:No value for LOCATION property. Removing entire property: +END:VEVENT +BEGIN:VEVENT +CREATED:20160725T060147Z +LAST-MODIFIED:20160727T193435Z +DTSTAMP:20160727T193435Z +UID:040000008200E00074C5B7101A82E00800000001 +CLASS:PUBLIC +X-LIC-ERROR:No value for LOCATION property. Removing entire property: +END:VEVENT +END:VCALENDAR diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index 0f33a4fc..afcca1b2 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -142,6 +142,20 @@ permissions: RrWw""") assert "Event" in answer assert "UID:event" in answer + def test_add_event_broken(self) -> None: + """Add a broken event.""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("broken-vevent.ics") + path = "/calendar.ics/broken-vevent.ics" + self.put(path, event, check=400) + + def test_add_events_broken2(self) -> None: + """Add a broken event (2nd one is broken).""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("broken-vevents.ics") + path = "/calendar.ics/" + self.put(path, event, check=400) + def test_add_event_without_uid(self) -> None: """Add an event without UID.""" self.mkcalendar("/calendar.ics/") @@ -201,6 +215,34 @@ permissions: RrWw""") _, answer = self.get(path) assert "UID:contact1" in answer + def test_add_contact_broken(self) -> None: + """Add a broken contact.""" + self.create_addressbook("/contacts.vcf/") + contact = get_file_content("broken-vcard.vcf") + path = "/contacts.vcf/broken-vcards.vcf" + self.put(path, contact, check=400) + + def test_add_contacts_broken(self) -> None: + """Add broken contacts.""" + self.create_addressbook("/contacts.vcf/") + contact = get_file_content("broken-vcards.vcf") + path = "/contacts.vcf/" + self.put(path, contact, check=400) + + def test_add_contacts_broken2(self) -> None: + """Add broken contacts (only 2nd one is broken).""" + self.create_addressbook("/contacts.vcf/") + contact = get_file_content("broken-vcards2.vcf") + path = "/contacts.vcf/" + self.put(path, contact, check=400) + + def test_add_contacts_broken2_no_uid(self) -> None: + """Add broken contacts (only 2nd one is broken and has no UID).""" + self.create_addressbook("/contacts.vcf/") + contact = get_file_content("broken-vcards2-no_uid.vcf") + path = "/contacts.vcf/" + self.put(path, contact, check=400) + def test_add_contact_photo_with_data_uri(self) -> None: """Test workaround for broken PHOTO data from InfCloud""" self.create_addressbook("/contacts.vcf/") From 56e0362f59efcdaf4224542933e380c952d98636 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 1 Dec 2025 18:51:17 +0100 Subject: [PATCH 2/5] extend broken vcalendar item log --- radicale/app/put.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index de11589b..0a72d00a 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -93,9 +93,13 @@ def prepare(vobject_items: List[vobject.base.Component], path: str, logger.debug("Prepare item with UID '%s'", item.uid) try: item.prepare() - except ValueError as e: + except (RuntimeError, ValueError, AttributeError) as e: if logger.isEnabledFor(logging.DEBUG): - logger.warning("Problem during prepare item with UID '%s' (content below): %s\n%s", item.uid, e, item._text) + if item._text is None: + content = vobject_item + else: + content = item._text + logger.warning("Problem during prepare item with UID '%s' (content below): %s\n%s", item.uid, e, content) else: logger.warning("Problem during prepare item with UID '%s' (content suppressed in this loglevel): %s", item.uid, e) raise From da612cfccb57c3e4b5677c88f817dc02148ed723 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 1 Dec 2025 18:51:41 +0100 Subject: [PATCH 3/5] add broken vaddressbook logging similar to vcalendar --- radicale/app/put.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index 0a72d00a..daec6339 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -108,7 +108,19 @@ def prepare(vobject_items: List[vobject.base.Component], path: str, for vobject_item in vobject_items: item = radicale_item.Item(collection_path=collection_path, vobject_item=vobject_item) - item.prepare() + logger.debug("Prepare item with UID '%s'", item.uid) + try: + item.prepare() + except (RuntimeError, ValueError, AttributeError) as e: + if logger.isEnabledFor(logging.DEBUG): + if item._text is None: + content = vobject_item + else: + content = item._text + logger.warning("Problem during prepare item with UID '%s' (content below): %s\n%s", item.uid, e, content) + else: + logger.warning("Problem during prepare item with UID '%s' (content suppressed in this loglevel): %s", item.uid, e) + raise items.append(item) elif not write_whole_collection: vobject_item, = vobject_items From 9b43b8e3de4d310325ebffe5a7e0a8459dc7b520 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 1 Dec 2025 18:52:05 +0100 Subject: [PATCH 4/5] catch broken event --- radicale/item/filter.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/radicale/item/filter.py b/radicale/item/filter.py index 94cdc015..a1329988 100644 --- a/radicale/item/filter.py +++ b/radicale/item/filter.py @@ -354,7 +354,10 @@ def visit_time_ranges(vobject_item: vobject.base.Component, child_name: str, for child, is_recurrence, recurrences in get_children( vobject_item.vevent_list): # TODO: check if there's a timezone - dtstart = child.dtstart.value + try: + dtstart = child.dtstart.value + except AttributeError: + raise AttributeError("missing DTSTART") if child.rruleset: dtstarts, infinity = getrruleset(child, recurrences) From 5fa370fa1ced5e8b28377e2c52d7cd432de0137e Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 1 Dec 2025 18:59:20 +0100 Subject: [PATCH 5/5] extend changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be52c88..8d36b54d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## 3.5.10.dev +* Improve: logging of broken calendar items during PUT +* Add: logging of broken contact items during PUT ## 3.5.9 * Extend: [auth] add support for type http_remote_user