From 415ffeb10a7e5eaaa6f1f4b96b25b4e9e974c2af Mon Sep 17 00:00:00 2001 From: Anushka Mukherjee Date: Tue, 11 Nov 2025 18:43:08 +0000 Subject: [PATCH 1/2] Fix infinite loop when INTERVAL=0 in recurrence rules Fixes #39 - Add server-side validation to reject RRULE strings with INTERVAL=0 or negative values - INTERVAL=0 is invalid per RFC 5545 and causes infinite loops in python-dateutil - Return HTTP 400 with translated 'no_repeat_every' error message - Prevents server hangs when users set repeat interval to 0 in the UI --- news/interval-validation.bugfix | 1 + .../recurrence/browser/json_recurrence.py | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 news/interval-validation.bugfix diff --git a/news/interval-validation.bugfix b/news/interval-validation.bugfix new file mode 100644 index 0000000..37943f9 --- /dev/null +++ b/news/interval-validation.bugfix @@ -0,0 +1 @@ +Prevent infinite loop when INTERVAL=0 is used in recurrence rules by adding server-side validation. INTERVAL=0 is invalid per RFC 5545 and now returns a proper error message instead of causing the server to hang. \ No newline at end of file diff --git a/src/plone/formwidget/recurrence/browser/json_recurrence.py b/src/plone/formwidget/recurrence/browser/json_recurrence.py index 7c81271..6e69855 100644 --- a/src/plone/formwidget/recurrence/browser/json_recurrence.py +++ b/src/plone/formwidget/recurrence/browser/json_recurrence.py @@ -1,4 +1,5 @@ from dateutil import rrule +from plone.formwidget.recurrence import _ from plone.base.i18nl10n import _interp_regex from plone.base.i18nl10n import datetime_formatvariables from plone.base.i18nl10n import monthname_msgid @@ -65,7 +66,24 @@ def json_string(self): start_date = datetime.datetime( int(data["year"]), int(data["month"]), int(data["day"]) ) - rule = rrule.rrulestr(data["rrule"], dtstart=start_date) + # Validate the RRULE string to prevent invalid INTERVAL values + # INTERVAL=0 is invalid per RFC5545 and causes an infinite loop in + # python-dateutil when iterating the rule. Reject it early with a + # user-friendly, translated error message. + rrule_str = data["rrule"] + m = re.search(r"(?i)(?:^|;)INTERVAL\s*=\s*([-+]?[0-9]+)(?:;|$)", rrule_str) + if m: + try: + interval_val = int(m.group(1)) + except Exception: + interval_val = None + if interval_val is not None and interval_val <= 0: + # Bad request: INTERVAL must be a positive integer + self.request.response.setStatus(400) + msg = translate(_("no_repeat_every"), context=self.request) + return {"error": msg} + + rule = rrule.rrulestr(rrule_str, dtstart=start_date) iterator = iter(rule) if "batch_size" in data: From 15d4b05427b8d6772882ddf01639568b1a17e35b Mon Sep 17 00:00:00 2001 From: Anushka Mukherjee Date: Tue, 11 Nov 2025 18:48:08 +0000 Subject: [PATCH 2/2] Fix pre-commit issues - Fix import order (isort) - Add newline at end of news fragment file --- news/interval-validation.bugfix | 2 +- src/plone/formwidget/recurrence/browser/json_recurrence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/news/interval-validation.bugfix b/news/interval-validation.bugfix index 37943f9..5bf7b91 100644 --- a/news/interval-validation.bugfix +++ b/news/interval-validation.bugfix @@ -1 +1 @@ -Prevent infinite loop when INTERVAL=0 is used in recurrence rules by adding server-side validation. INTERVAL=0 is invalid per RFC 5545 and now returns a proper error message instead of causing the server to hang. \ No newline at end of file +Prevent infinite loop when INTERVAL=0 is used in recurrence rules by adding server-side validation. INTERVAL=0 is invalid per RFC 5545 and now returns a proper error message instead of causing the server to hang. diff --git a/src/plone/formwidget/recurrence/browser/json_recurrence.py b/src/plone/formwidget/recurrence/browser/json_recurrence.py index 6e69855..a859a27 100644 --- a/src/plone/formwidget/recurrence/browser/json_recurrence.py +++ b/src/plone/formwidget/recurrence/browser/json_recurrence.py @@ -1,5 +1,4 @@ from dateutil import rrule -from plone.formwidget.recurrence import _ from plone.base.i18nl10n import _interp_regex from plone.base.i18nl10n import datetime_formatvariables from plone.base.i18nl10n import monthname_msgid @@ -7,6 +6,7 @@ from plone.base.i18nl10n import name_formatvariables from plone.base.i18nl10n import weekdayname_msgid from plone.base.i18nl10n import weekdayname_msgid_abbr +from plone.formwidget.recurrence import _ from Products.Five import BrowserView from zope.i18n import interpolate from zope.i18n import translate