diff --git a/radicale/app/report.py b/radicale/app/report.py
index 44e41d1e..8cb1e9b2 100644
--- a/radicale/app/report.py
+++ b/radicale/app/report.py
@@ -233,10 +233,17 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
for filter_ in filters:
# extract time-range filter for processing after main filters
# for expand request
- time_range_element = filter_.find(".//" + xmlutils.make_clark("C:time-range"))
+ filter_copy = copy.deepcopy(filter_)
- if expand is None or time_range_element is None:
- main_filters.append(filter_)
+ if expand is not None:
+ for comp_filter in filter_copy.findall(".//" + xmlutils.make_clark("C:comp-filter")):
+ if comp_filter.get("name", "").upper() == "VCALENDAR":
+ continue
+ time_range_element = comp_filter.find(xmlutils.make_clark("C:time-range"))
+ if time_range_element is not None:
+ comp_filter.remove(time_range_element)
+
+ main_filters.append(filter_copy)
# Retrieve everything required for finishing the request.
retrieved_items = list(retrieve_items(
@@ -306,6 +313,11 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
time_range_start=time_range_start, time_range_end=time_range_end,
max_occurrence=max_occurrence,
)
+
+ if n_vev == 0:
+ logger.debug("No VEVENTs found after expansion for %r, skipping", item.href)
+ continue
+
n_vevents += n_vev
found_props.append(expanded_element)
else:
@@ -322,9 +334,11 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
assert item.href
uri = pathutils.unstrip_path(
posixpath.join(collection.path, item.href))
- multistatus.append(xml_item_response(
- base_prefix, uri, found_props=found_props,
- not_found_props=not_found_props, found_item=True))
+
+ if found_props or not_found_props:
+ multistatus.append(xml_item_response(
+ base_prefix, uri, found_props=found_props,
+ not_found_props=not_found_props, found_item=True))
return client.MULTI_STATUS, multistatus
@@ -340,6 +354,8 @@ def _expand(
) -> Tuple[ET.Element, int]:
vevent_component: vobject.base.Component = copy.copy(item.vobject_item)
logger.info("Expanding event %s", item.href)
+ logger.debug(f"Expand range: {start} to {end}")
+ logger.debug(f"Time range: {time_range_start} to {time_range_end}")
# Split the vevents included in the component into one that contains the
# recurrence information and others that contain a recurrence id to
diff --git a/radicale/tests/test_expand.py b/radicale/tests/test_expand.py
index bedc9d12..2b10110d 100644
--- a/radicale/tests/test_expand.py
+++ b/radicale/tests/test_expand.py
@@ -347,3 +347,123 @@ permissions: RrWw""")
check=check)
assert len(responses) == 0
assert status == check
+
+ def test_report_vcalendar_all_components(self) -> None:
+ """Test calendar-query with comp-filter VCALENDAR, returns all components."""
+ self.mkcalendar("/test/")
+ self.put("/test/calendar.ics", get_file_content("event_daily_rrule.ics"))
+ self.put("/test/todo.ics", get_file_content("todo1.ics"))
+
+ request = """
+
+
+
+
+
+
+
+
+ """
+ status, responses = self.report("/test", request)
+ assert status == 207
+ assert len(responses) == 2
+ assert "/test/calendar.ics" in responses
+ assert "/test/todo.ics" in responses
+
+ def test_report_vevent_only(self) -> None:
+ """Test calendar-query with comp-filter VEVENT, returns only VEVENT."""
+ self.mkcalendar("/test/")
+ self.put("/test/calendar.ics", get_file_content("event_daily_rrule.ics"))
+ self.put("/test/todo.ics", get_file_content("todo1.ics"))
+
+ start = "20060101T000000Z"
+ end = "20060104T000000Z"
+
+ request = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ status, responses = self.report("/test", request)
+ assert status == 207
+ assert len(responses) == 1
+ assert "/test/calendar.ics" in responses
+ vevent_response = responses["/test/calendar.ics"]
+ assert type(vevent_response) is dict
+ status, element = vevent_response["C:calendar-data"]
+ assert status == 200 and element.text
+ assert "BEGIN:VEVENT" in element.text
+ assert "UID:" in element.text
+ assert "BEGIN:VTODO" not in element.text
+
+ def test_report_time_range_no_vevent(self) -> None:
+ """Test calendar-query with time-range filter, no matching VEVENT."""
+ self.mkcalendar("/test/")
+ self.put("/test/calendar.ics/", get_file_content("event_daily_rrule.ics"))
+
+ request = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ status, responses = self.report("/test", request)
+ assert status == 207
+ assert len(responses) == 0
+
+ def test_report_time_range_one_vevent(self) -> None:
+ """Test calendar-query with time-range filter, matches one VEVENT."""
+ self.mkcalendar("/test/")
+ self.put("/test/calendar1.ics/", get_file_content("event_daily_rrule.ics"))
+ self.put("/test/calendar2.ics/", get_file_content("event1.ics"))
+
+ start = "20060101T000000Z"
+ end = "20060104T000000Z"
+
+ request = f"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ status, responses = self.report("/test", request)
+ assert status == 207
+ assert len(responses) == 1
+ response = responses["/test/calendar1.ics"]
+ assert type(response) is dict
+ status, element = response["C:calendar-data"]
+ assert status == 200 and element.text
+ assert "BEGIN:VEVENT" in element.text
+ assert "RECURRENCE-ID:20060103T170000Z" in element.text
+ assert "DTSTART:20060103T170000Z" in element.text