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 diff --git a/radicale/app/put.py b/radicale/app/put.py index de11589b..daec6339 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 @@ -104,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 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) 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/")