diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c45514..ab1afb9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## 3.7.7.dev +* Fix: free-busy REPORT always failed with HTTP 400 ("FREEBUSY occurrences limit of 0 hit") when [reporting] max_freebusy_occurrence is set to 0 (limit disabled), because the limit check did not honor the disabled limit * Fix: time-range filter treated a VEVENT with a whole-day DURATION (e.g. P1D, P2D) as zero-length (timedelta.seconds instead of total_seconds), so such events were missing from calendar-query REPORT results * Fix: calendar-data expand (REPORT) left recurrence properties (e.g. RDATE) on the expanded single-occurrence VEVENTs; a single try/except around the sequential delattr() calls stopped at the first absent property (e.g. missing EXDATE), so later ones were never removed * Fix: text-match filter on a structured property (e.g. vCard N or ADR) crashed with HTTP 500 (AttributeError: 'Name'/'Address' object has no attribute 'lower') because vobject parses these into non-string objects; their text representation is now used diff --git a/radicale/app/report.py b/radicale/app/report.py index dd41e8bb..fab2e445 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -133,7 +133,7 @@ def free_busy_report(base_prefix: str, path: str, xml_request: Optional[ET.Eleme time_range_element, "VEVENT", n=n_occurrences) - if len(occurrences) >= max_occurrence: + if max_occurrence > 0 and len(occurrences) >= max_occurrence: raise ValueError("FREEBUSY occurrences limit of {} hit" .format(max_occurrence)) diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index effa0d1e..47f4ee2d 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -2232,6 +2232,18 @@ permissions: RrWw""") """, 400, is_xml=False) + # Test max_freebusy_occurrence set to 0 (limit disabled) + self.configure({"reporting": {"max_freebusy_occurrence": 0}}) + code, responses = self.report(calendar_path, """\ + + + +""", 200, is_xml=False) + assert len(responses) == 1 + vcalendar = list(responses.values())[0] + assert isinstance(vcalendar, vobject.base.Component) + assert len(vcalendar.vfreebusy_list) == 3 + def _report_sync_token( self, calendar_path: str, sync_token: Optional[str] = None, **kwargs ) -> Tuple[str, RESPONSES]: