diff --git a/CHANGELOG.md b/CHANGELOG.md index 15706e57..763676ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.6.1.dev * Fix: MOVE failing with URL-encoded destination header +* Improve: add workaround to remove empty lines in item to avoid reject by vobject parser ## 3.6.0 diff --git a/radicale/item/__init__.py b/radicale/item/__init__.py index 1c9dcadb..48c0bdaa 100644 --- a/radicale/item/__init__.py +++ b/radicale/item/__init__.py @@ -4,7 +4,7 @@ # Copyright © 2014 Jean-Marc Martins # Copyright © 2008-2017 Guillaume Ayoub # Copyright © 2017-2022 Unrud -# Copyright © 2024-2025 Peter Bieringer +# Copyright © 2024-2026 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -56,6 +56,8 @@ def read_components(s: str) -> List[vobject.base.Component]: # * 0x0A Line Feed # * 0x0D Carriage Return s = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', s) + # Workaround delete all empty lines to avoid vobject parsing errors + s = re.sub(r'(?m)^[ \t]*\r?\n', '', s) return list(vobject.readComponents(s, allowQP=True)) diff --git a/radicale/tests/static/event_issue1970_ok.ics b/radicale/tests/static/event_issue1970_ok.ics new file mode 100644 index 00000000..89725608 --- /dev/null +++ b/radicale/tests/static/event_issue1970_ok.ics @@ -0,0 +1,38 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20130902T150157Z +LAST-MODIFIED:20130902T150158Z +DTSTAMP:20130902T150158Z +UID:event1 +SUMMARY:Event +CATEGORIES:some_category1,another_category2 +DESCRIPTION:Line1 + Line2 + Line3 +ORGANIZER:mailto:unclesam@example.com +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com +ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com +DTSTART;TZID=Europe/Paris:20130901T180000 +DTEND;TZID=Europe/Paris:20130901T190000 +END:VEVENT +END:VCALENDAR diff --git a/radicale/tests/static/event_issue1970_problem.ics b/radicale/tests/static/event_issue1970_problem.ics new file mode 100644 index 00000000..a1b75e6e --- /dev/null +++ b/radicale/tests/static/event_issue1970_problem.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20130902T150157Z +LAST-MODIFIED:20130902T150158Z +DTSTAMP:20130902T150158Z +UID:event1 +SUMMARY:Event having description with empty line +CATEGORIES:some_category1,another_category2 +DESCRIPTION:Line1 + Line2 + + Line4 +ORGANIZER:mailto:unclesam@example.com +ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Jane Doe:MAILTO:janedoe@example.com +ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com";PARTSTAT=ACCEPTED;CN=John Doe:MAILTO:johndoe@example.com +DTSTART;TZID=Europe/Paris:20130901T180000 +DTEND;TZID=Europe/Paris:20130901T190000 +END:VEVENT +END:VCALENDAR diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index ab7954bf..f50cf0be 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -144,6 +144,34 @@ permissions: RrWw""") assert "Event" in answer assert "UID:event" in answer + def test_add_event_with_desc_ok(self) -> None: + """Add an event.""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("event_issue1970_ok.ics") + path = "/calendar.ics/event_issue1970_ok.ics" + self.put(path, event) + _, headers, answer = self.request("GET", path, check=200) + assert "ETag" in headers + assert headers["Content-Type"] == "text/calendar; charset=utf-8" + assert "DESCRIPTION" in answer + assert "VEVENT" in answer + assert "Event" in answer + assert "UID:event" in answer + + def test_add_event_with_desc_problem(self) -> None: + """Add an event.""" + self.mkcalendar("/calendar.ics/") + event = get_file_content("event_issue1970_problem.ics") + path = "/calendar.ics/event_issue1970_problem.ics" + self.put(path, event) + _, headers, answer = self.request("GET", path, check=200) + assert "ETag" in headers + assert headers["Content-Type"] == "text/calendar; charset=utf-8" + assert "DESCRIPTION" in answer + assert "VEVENT" in answer + assert "Event" in answer + assert "UID:event" in answer + def test_add_event_exceed_size(self) -> None: """Add an event which is exceeding max-resource-size.""" self.configure({"server": {"max_resource_size": 20}})