diff --git a/radicale/item/__init__.py b/radicale/item/__init__.py index 26c93849..975e4e7a 100644 --- a/radicale/item/__init__.py +++ b/radicale/item/__init__.py @@ -247,51 +247,51 @@ def check_and_sanitize_items( if hasattr(component, "rrule"): # workaround for vobject < 1.0.0 as it has no limiter in "getrruleset" logger.trace("Recurrence rule found in %s in object %r: %r", component.name, component_uid, component.rrule.value) - # early check of maximum of COUNT to avoid DoS (semi-ugly workaround) - pattern = re.compile('.*;COUNT=(\\d+)(;.*)?$') - match = pattern.match(component.rrule.value) - if match: - rrule_count = int(match[1]) - if max_vevent_rrule_occurrence > 0 and rrule_count > max_vevent_rrule_occurrence: - logger.error("Recurrence rule %r count in %s in object %r: %d (REJECTED/limit: %d)", component.rrule.value, component.name, component_uid, rrule_count, max_vevent_rrule_occurrence) + if not hasattr(component, "dtstart"): + # e.g. VTODO + rrule = vobject.icalendar.rrule.rrulestr(component.rrule.value) + else: + dtstart = radicale_filter.date_to_datetime(component.dtstart.value) + ignoretz = ( + not isinstance(dtstart, datetime.datetime) + or dtstart.tzinfo is None + ) + rrule = vobject.icalendar.rrule.rrulestr(component.rrule.value, ignoretz=ignoretz) + # early check of maximum of COUNT to avoid DoS (workaround) + if hasattr(rrule, "_count") and rrule._count is not None: + logger.trace("Recurrence rule %r in %s in object %r contains: COUNT=%d", component.rrule.value, component.name, component_uid, rrule._count) + if max_vevent_rrule_occurrence > 0 and rrule._count > max_vevent_rrule_occurrence: + logger.error("Recurrence rule %r count in %s in object %r: %d (REJECTED/limit: %d)", component.rrule.value, component.name, component_uid, rrule._count, max_vevent_rrule_occurrence) raise ValueError("Too many recurrence rule entries in %s in object %r: %d (limit: %d)" - % (component.name, component_uid, rrule_count, max_vevent_rrule_occurrence)) + % (component.name, component_uid, rrule._count, max_vevent_rrule_occurrence)) else: - logger.debug("Recurrence rule count in %s in object %r: %d (PASSED/limit: %d)" % (component.name, component_uid, rrule_count, max_vevent_rrule_occurrence)) - if hasattr(component, "dtstart"): - # early check of maximum of (UNTIL-DTSTART)/interval(FREQ) to avoid DoS (ugly workaround with some guessing) - pattern = re.compile('FREQ=([A-Z]+)(;.*)?$') - match = pattern.match(component.rrule.value) - if match and match[1] in RRULE_FREQUENCIES_TO_INTERVAL: - # RRULE has known FREQ - freq = match[1] - pattern = re.compile('.*;UNTIL=([\\dTZ]+)(;.*)?$') - match = pattern.match(component.rrule.value) - dtstart = radicale_filter.date_to_datetime(component.dtstart.value) - if match: - # RRULE has UNTIL - ignoretz = ( - not isinstance(dtstart, datetime.datetime) - or dtstart.tzinfo is None - ) - until = vobject.icalendar.rrule.rrulestr(component.rrule.value, ignoretz=ignoretz)._until - if dtstart.tzinfo is not None: - until = until.astimezone(dtstart.tzinfo) - delta = until - dtstart - seconds = delta.total_seconds() - if seconds < 0: - # UNTIL < DTSTART - logger.error("Recurrence rule %r in %s in object %r REJECTED, UNTIL < DTSTART", component.rrule.value, component.name, component_uid) - raise ValueError("Recurrence rule in %s in object %r has UNTIL < DTSTART" - % (component.name, component_uid)) - rrule_entries = seconds / RRULE_FREQUENCIES_TO_INTERVAL[freq] - logger.trace("estimated rule entries: %d", rrule_entries) - if max_vevent_rrule_occurrence > 0 and rrule_entries > max_vevent_rrule_occurrence: - logger.warning("Recurrence rule %r entries in %s in object %r: %d (estimated/REJECTED/limit: %d)", component.rrule.value, component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence) - raise ValueError("Too many recurrence rule entries in %s in object %r: %d (limit: %d)" - % (component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence)) - else: - logger.debug("Recurrence rule %r entries in %s in object %r: %d (estimated/PASSED/limit: %d)" % (component.rrule.value, component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence)) + logger.debug("Recurrence rule count in %s in object %r: %d (PASSED/limit: %d)" % (component.name, component_uid, rrule._count, max_vevent_rrule_occurrence)) + else: + logger.trace("Recurrence rule %r in %s in object %r doesn't contain: COUNT", component.rrule.value, component.name, component_uid) + # early check of maximum of (UNTIL-DTSTART)/interval(FREQ) to avoid DoS (ugly workaround with some guessing) + if hasattr(rrule, "_freq") and rrule._freq is not None and hasattr(rrule, "_until") and rrule._until is not None and hasattr(component, "dtstart"): + if vobject.icalendar.FREQUENCIES[rrule._freq] not in RRULE_FREQUENCIES_TO_INTERVAL: + raise ValueError("Unsupported FREQ in recurrence rule in %s in object %r: %r" + % (component.name, component_uid, rrule._freq)) + # RRULE has known FREQ+UNTIL+DTSTART + if dtstart.tzinfo is not None: + rrule._until = rrule._until.astimezone(dtstart.tzinfo) + delta = rrule._until - dtstart + seconds = delta.total_seconds() + if seconds < 0: + # UNTIL < DTSTART + logger.error("Recurrence rule %r in %s in object %r REJECTED, UNTIL < DTSTART", component.rrule.value, component.name, component_uid) + raise ValueError("Recurrence rule in %s in object %r has UNTIL < DTSTART" + % (component.name, component_uid)) + rrule_entries = seconds / RRULE_FREQUENCIES_TO_INTERVAL[vobject.icalendar.FREQUENCIES[rrule._freq]] + if max_vevent_rrule_occurrence > 0 and rrule_entries > max_vevent_rrule_occurrence: + logger.warning("Recurrence rule %r entries in %s in object %r: %d (estimated/REJECTED/limit: %d)", component.rrule.value, component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence) + raise ValueError("Too many recurrence rule entries in %s in object %r: %d (limit: %d)" + % (component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence)) + else: + logger.debug("Recurrence rule %r entries in %s in object %r: %d (estimated/PASSED/limit: %d)" % (component.rrule.value, component.name, component_uid, rrule_entries, max_vevent_rrule_occurrence)) + else: + logger.trace("Recurrence rule %r in %s in object %r doesn't contain: FREQ+UNTIL", component.rrule.value, component.name, component_uid) # generic check by vobject try: rruleset = component.rruleset