max_vevent_rrule_occurrence: improve pre-check

This commit is contained in:
Peter Bieringer
2026-08-02 16:03:25 +02:00
parent 7cbf418d34
commit 490fbbf69d

View File

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