fix: ICS export doesn't escape commas/semicolons in event fields (#1161)

* fix: escape SUMMARY/LOCATION per RFC 5545 in ICS export

* fix: escape commas/semicolons in ICS DESCRIPTION, not just newlines

* test: ICS export escapes commas, semicolons, backslashes, newlines
This commit is contained in:
Afonso Coutinho
2026-06-02 14:36:12 +01:00
committed by GitHub
parent 2e2da2aefe
commit 5b12bf3f55
2 changed files with 46 additions and 4 deletions
+21 -4
View File
@@ -65,6 +65,24 @@ def _get_or_404_event(db, uid: str, owner: str) -> CalendarEvent:
return ev
def _ics_escape(text: str) -> str:
"""Escape a value for an iCalendar TEXT field (RFC 5545 §3.3.11).
Backslash, semicolon and comma are structural in TEXT values and must be
escaped, and newlines become a literal ``\\n``. Backslash is escaped first
so the escapes we add aren't re-escaped.
"""
return (
(text or "")
.replace("\\", "\\\\")
.replace(";", "\\;")
.replace(",", "\\,")
.replace("\r\n", "\\n")
.replace("\n", "\\n")
.replace("\r", "\\n")
)
def _resolve_base_uid(uid: str) -> str:
"""Extract the base series UID from a compound occurrence UID.
@@ -1038,7 +1056,7 @@ def setup_calendar_routes() -> APIRouter:
for ev in events:
lines.append("BEGIN:VEVENT")
lines.append(f"UID:{ev.uid}")
lines.append(f"SUMMARY:{ev.summary or ''}")
lines.append(f"SUMMARY:{_ics_escape(ev.summary or '')}")
if ev.all_day:
lines.append(f"DTSTART;VALUE=DATE:{ev.dtstart.strftime('%Y%m%d')}")
lines.append(f"DTEND;VALUE=DATE:{ev.dtend.strftime('%Y%m%d')}")
@@ -1046,10 +1064,9 @@ def setup_calendar_routes() -> APIRouter:
lines.append(f"DTSTART:{ev.dtstart.strftime('%Y%m%dT%H%M%S')}")
lines.append(f"DTEND:{ev.dtend.strftime('%Y%m%dT%H%M%S')}")
if ev.description:
desc = ev.description.replace(chr(10), '\\n')
lines.append(f"DESCRIPTION:{desc}")
lines.append(f"DESCRIPTION:{_ics_escape(ev.description)}")
if ev.location:
lines.append(f"LOCATION:{ev.location}")
lines.append(f"LOCATION:{_ics_escape(ev.location)}")
if ev.rrule:
lines.append(f"RRULE:{ev.rrule}")
lines.append("END:VEVENT")