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"):
# 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)
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))
if not hasattr(component, "dtstart"):
# e.g. VTODO
rrule = vobject.icalendar.rrule.rrulestr(component.rrule.value)
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
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))
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))
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:
until = until.astimezone(dtstart.tzinfo)
delta = until - dtstart
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[freq]
logger.trace("estimated rule entries: %d", rrule_entries)
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