Merge pull request #2153 from pbiering/issue-2151
Improve: sanitize item add timezone to EXDATE or RDATE if missing but DTSTART has
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
* Add: [sharing] conversion_bday_categories (customize)
|
* Add: [sharing] conversion_bday_categories (customize)
|
||||||
* Add: [sharing] conversion_bday_age_max (limit in case of "age" placeholder is used which blocks using RRULE)
|
* Add: [sharing] conversion_bday_age_max (limit in case of "age" placeholder is used which blocks using RRULE)
|
||||||
* Extension: [sharing/bday conversion]: add STATUS + CLASS fields
|
* Extension: [sharing/bday conversion]: add STATUS + CLASS fields
|
||||||
|
* Improve: sanitize item align timezone (add/remove) of EXDATE or RDATE with DTSTART
|
||||||
|
|
||||||
## 3.7.4
|
## 3.7.4
|
||||||
* Fix: sharing: PROPFIND returns now empty owner element in case of a mapped share as clients try PROPFIND on this not accessable href
|
* Fix: sharing: PROPFIND returns now empty owner element in case of a mapped share as clients try PROPFIND on this not accessable href
|
||||||
|
|||||||
@@ -174,12 +174,26 @@ def check_and_sanitize_items(
|
|||||||
# EXDATE has value DATE even if DTSTART/DTEND is DATE-TIME.
|
# EXDATE has value DATE even if DTSTART/DTEND is DATE-TIME.
|
||||||
# The RFC is vaguely formulated on the issue.
|
# The RFC is vaguely formulated on the issue.
|
||||||
# To resolve the issue convert EXDATE and RDATE to
|
# To resolve the issue convert EXDATE and RDATE to
|
||||||
# the same type as DTDSTART
|
# the same type as DTSTART
|
||||||
if hasattr(component, "dtstart"):
|
if hasattr(component, "dtstart"):
|
||||||
ref_date = component.dtstart.value
|
ref_date = component.dtstart.value
|
||||||
ref_value_param = component.dtstart.params.get("VALUE")
|
ref_value_param = component.dtstart.params.get("VALUE")
|
||||||
for dates in chain(component.contents.get("exdate", []),
|
for dates in chain(component.contents.get("exdate", []),
|
||||||
component.contents.get("rdate", [])):
|
component.contents.get("rdate", [])):
|
||||||
|
for i, date in enumerate(dates.value):
|
||||||
|
if type(ref_date) is datetime.datetime and type(date) is datetime.datetime:
|
||||||
|
if hasattr(ref_date, 'tzinfo') and ref_date.tzinfo is not None:
|
||||||
|
logger.trace("ITEM/check_and_sanitize_item: dtstart has tzinfo: '%s'", ref_date)
|
||||||
|
if hasattr(date, 'tzinfo') and date.tzinfo is None:
|
||||||
|
# Ensure that datetime.datetime object has timezone set if dtstart has
|
||||||
|
dates.value[i] = dates.value[i].replace(tzinfo=ref_date.tzinfo)
|
||||||
|
logger.trace("ITEM/check_and_sanitize_item: overtake missing tzinfo from dtstart: '%s' -> '%s'", date, dates.value[i])
|
||||||
|
elif (hasattr(ref_date, 'tzinfo') and ref_date.tzinfo is None) or not hasattr(ref_date, 'tzinfo'):
|
||||||
|
logger.trace("ITEM/check_and_sanitize_item: dtstart has no tzinfo: '%s'", ref_date)
|
||||||
|
if hasattr(date, 'tzinfo') and date.tzinfo is not None:
|
||||||
|
# Ensure that datetime.datetime object has no timezone set if dtstart has none
|
||||||
|
dates.value[i] = dates.value[i].replace(tzinfo=None)
|
||||||
|
logger.trace("ITEM/check_and_sanitize_item: remove existing tzinfo (dtstart has none): '%s' -> '%s'", date, dates.value[i])
|
||||||
if all(type(d) is type(ref_date) for d in dates.value):
|
if all(type(d) is type(ref_date) for d in dates.value):
|
||||||
continue
|
continue
|
||||||
for i, date in enumerate(dates.value):
|
for i, date in enumerate(dates.value):
|
||||||
|
|||||||
116
radicale/tests/static/event_issue2151.ics
Normal file
116
radicale/tests/static/event_issue2151.ics
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
BEGIN:VCALENDAR
|
||||||
|
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:W. Europe Standard Time
|
||||||
|
BEGIN:STANDARD
|
||||||
|
DTSTART:16010101T030000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
END:STANDARD
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
DTSTART:16010101T020000
|
||||||
|
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
END:DAYLIGHT
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20260605T085814Z
|
||||||
|
LAST-MODIFIED:20260605T100722Z
|
||||||
|
DTSTAMP:20260605T100722Z
|
||||||
|
UID:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
SUMMARY:Billings-task-setting follow-up series
|
||||||
|
PRIORITY:5
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
ORGANIZER;CN=XXXXXXXX XXXXX:mailto:XXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XX;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:XX@XXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XXXXXXXX XXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XXXXXX XXXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
EXDATE:20260403T110000
|
||||||
|
RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20260731T090000Z
|
||||||
|
X-MOZ-LASTACK:20260605T100722Z
|
||||||
|
DTSTART;TZID=W. Europe Standard Time:20260123T110000
|
||||||
|
DTEND;TZID=W. Europe Standard Time:20260123T120000
|
||||||
|
CLASS:PUBLIC
|
||||||
|
DESCRIPTION;LANGUAGE=de-DE:Serie war letzte Woche ausgelaufen\n\nMicrosoft Teams meeting details redacted.\n
|
||||||
|
LOCATION;LANGUAGE=de-DE:Microsoft Teams-Besprechung
|
||||||
|
SEQUENCE:0
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
|
||||||
|
X-MICROSOFT-CDO-APPT-SEQUENCE:0
|
||||||
|
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
|
||||||
|
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||||
|
X-MICROSOFT-CDO-INSTTYPE:1
|
||||||
|
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
|
||||||
|
X-MICROSOFT-CDO-OWNERAPPTID:XXXXXXXXXX
|
||||||
|
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||||
|
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
|
||||||
|
X-MICROSOFT-ISRESPONSEREQUESTED:TRUE
|
||||||
|
X-MICROSOFT-LOCATIONDISPLAYNAME:Microsoft Teams-Besprechung
|
||||||
|
X-MICROSOFT-LOCATIONS:[{"DisplayName":"Microsoft Teams-Besprechung"\,"LocationAnnotation":""\,"LocationUri":""\,"LocationStreet":""\,"LocationCity":""\,"LocationState":""\,"LocationCountry":""\,"LocationPostalCode":""\,"LocationFullAddress":""}]
|
||||||
|
X-MICROSOFT-LOCATIONSOURCE:None
|
||||||
|
X-MICROSOFT-ONLINEMEETINGINFORMATION:{"OnlineMeetingChannelId":null\,"OnlineMeetingProvider":3}
|
||||||
|
X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
|
||||||
|
X-MICROSOFT-SCHEDULINGSERVICEUPDATEURL:REDACTED
|
||||||
|
X-MICROSOFT-SKYPETEAMSMEETINGURL:REDACTED
|
||||||
|
X-MICROSOFT-SKYPETEAMSPROPERTIES:REDACTED
|
||||||
|
X-MOZ-GENERATION:17
|
||||||
|
X-MOZ-INVITED-ATTENDEE:mailto:XX@XXXXXXX.XXX
|
||||||
|
X-MOZ-RECEIVED-DTSTAMP:20260123T100333Z
|
||||||
|
X-MOZ-RECEIVED-SEQUENCE:0
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER:-PT5M
|
||||||
|
DESCRIPTION:Mozilla Standardbeschreibung
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
CREATED:20260605T085814Z
|
||||||
|
LAST-MODIFIED:20260130T093740Z
|
||||||
|
DTSTAMP:20260130T093740Z
|
||||||
|
UID:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||||
|
SUMMARY:Billings-task-setting follow-up series
|
||||||
|
PRIORITY:5
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
RECURRENCE-ID;TZID=W. Europe Standard Time:20260130T110000
|
||||||
|
ORGANIZER;CN=XXXXXXXX XXXXX:mailto:XXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XXXXXXXX XXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XXXXXX XXXXXXXX;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT:mailto:XXXXXXXXXX@XXXXXXXXXXXXXX.XXX
|
||||||
|
ATTENDEE;RSVP=TRUE;CN=XX;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT:mailto:XX@XXXXXXX.XXX
|
||||||
|
DTSTART;TZID=W. Europe Standard Time:20260130T103000
|
||||||
|
DTEND;TZID=W. Europe Standard Time:20260130T113000
|
||||||
|
CLASS:PUBLIC
|
||||||
|
DESCRIPTION:Serie war letzte Woche ausgelaufen\n\nMicrosoft Teams meeting details redacted.\n
|
||||||
|
LOCATION:Microsoft Teams-Besprechung
|
||||||
|
SEQUENCE:1
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
|
||||||
|
X-MICROSOFT-CDO-APPT-SEQUENCE:1
|
||||||
|
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
|
||||||
|
X-MICROSOFT-CDO-IMPORTANCE:1
|
||||||
|
X-MICROSOFT-CDO-INSTTYPE:3
|
||||||
|
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
|
||||||
|
X-MICROSOFT-CDO-OWNERAPPTID:XXXXXXXXXX
|
||||||
|
X-MICROSOFT-DISALLOW-COUNTER:FALSE
|
||||||
|
X-MICROSOFT-DONOTFORWARDMEETING:FALSE
|
||||||
|
X-MICROSOFT-ISRESPONSEREQUESTED:TRUE
|
||||||
|
X-MICROSOFT-LOCATIONDISPLAYNAME:Microsoft Teams-Besprechung
|
||||||
|
X-MICROSOFT-LOCATIONS:[{"DisplayName":"Microsoft Teams-Besprechung"\,"LocationAnnotation":""\,"LocationUri":""\,"LocationStreet":""\,"LocationCity":""\,"LocationState":""\,"LocationCountry":""\,"LocationPostalCode":""\,"LocationFullAddress":""}]
|
||||||
|
X-MICROSOFT-LOCATIONSOURCE:None
|
||||||
|
X-MICROSOFT-ONLINEMEETINGINFORMATION:{"OnlineMeetingChannelId":null\,"OnlineMeetingProvider":3}
|
||||||
|
X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT
|
||||||
|
X-MICROSOFT-SCHEDULINGSERVICEUPDATEURL:REDACTED
|
||||||
|
X-MICROSOFT-SKYPETEAMSMEETINGURL:REDACTED
|
||||||
|
X-MICROSOFT-SKYPETEAMSPROPERTIES:REDACTED
|
||||||
|
X-MOZ-GENERATION:0
|
||||||
|
X-MOZ-RECEIVED-DTSTAMP:20260130T093205Z
|
||||||
|
X-MOZ-RECEIVED-SEQUENCE:1
|
||||||
|
BEGIN:VALARM
|
||||||
|
ACTION:DISPLAY
|
||||||
|
TRIGGER:-PT5M
|
||||||
|
DESCRIPTION:Mozilla Standardbeschreibung
|
||||||
|
END:VALARM
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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:event_mixed_datetime_and_date_exdate
|
||||||
|
SUMMARY:Event
|
||||||
|
DTSTART:20130901T180000
|
||||||
|
DTEND:20130901T190000
|
||||||
|
RRULE:FREQ=DAILY;COUNT=3
|
||||||
|
EXDATE:20130902T193000Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
@@ -273,6 +273,18 @@ permissions: RrWw""")
|
|||||||
event = get_file_content("event_exdate_without_rrule.ics")
|
event = get_file_content("event_exdate_without_rrule.ics")
|
||||||
self.put("/calendar.ics/event.ics", event)
|
self.put("/calendar.ics/event.ics", event)
|
||||||
|
|
||||||
|
def test_add_event_exdate_no_tz(self) -> None:
|
||||||
|
"""Test event where EXDATE has no tzinfo."""
|
||||||
|
self.mkcalendar("/calendar.ics/")
|
||||||
|
event = get_file_content("event_issue2151.ics")
|
||||||
|
self.put("/calendar.ics/event_issue2151.ics", event)
|
||||||
|
|
||||||
|
def test_add_event_dtstart_no_tz_exdate_tz(self) -> None:
|
||||||
|
"""Test event where DTSTART has no tzinfo but EXDATE."""
|
||||||
|
self.mkcalendar("/calendar.ics/")
|
||||||
|
event = get_file_content("event_mixed_datetime_and_date_exdate.ics")
|
||||||
|
self.put("/calendar.ics/event_mixed_datetime_and_date_exdate.ics", event)
|
||||||
|
|
||||||
def test_add_todo(self) -> None:
|
def test_add_todo(self) -> None:
|
||||||
"""Add a todo."""
|
"""Add a todo."""
|
||||||
self.mkcalendar("/calendar.ics/")
|
self.mkcalendar("/calendar.ics/")
|
||||||
|
|||||||
Reference in New Issue
Block a user