mirror of
https://github.com/Kozea/Radicale.git
synced 2025-08-01 18:18:31 +00:00
support max_occurence anti-DoS in xml_report for vevent expansion
Signed-off-by: David Greaves <david@dgreaves.com>
This commit is contained in:
parent
a26bfaa08a
commit
fbc3abc48d
1 changed files with 24 additions and 9 deletions
|
@ -145,7 +145,8 @@ def free_busy_report(base_prefix: str, path: str, xml_request: Optional[ET.Eleme
|
||||||
|
|
||||||
def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
||||||
collection: storage.BaseCollection, encoding: str,
|
collection: storage.BaseCollection, encoding: str,
|
||||||
unlock_storage_fn: Callable[[], None]
|
unlock_storage_fn: Callable[[], None],
|
||||||
|
max_occurrence: int = 0,
|
||||||
) -> Tuple[int, ET.Element]:
|
) -> Tuple[int, ET.Element]:
|
||||||
"""Read and answer REPORT requests that return XML.
|
"""Read and answer REPORT requests that return XML.
|
||||||
|
|
||||||
|
@ -244,6 +245,7 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
||||||
# !!! Don't access storage after this !!!
|
# !!! Don't access storage after this !!!
|
||||||
unlock_storage_fn()
|
unlock_storage_fn()
|
||||||
|
|
||||||
|
n_vevents = 0
|
||||||
while retrieved_items:
|
while retrieved_items:
|
||||||
# ``item.vobject_item`` might be accessed during filtering.
|
# ``item.vobject_item`` might be accessed during filtering.
|
||||||
# Don't keep reference to ``item``, because VObject requires a lot of
|
# Don't keep reference to ``item``, because VObject requires a lot of
|
||||||
|
@ -298,15 +300,21 @@ def xml_report(base_prefix: str, path: str, xml_request: Optional[ET.Element],
|
||||||
if time_range_element is not None:
|
if time_range_element is not None:
|
||||||
time_range_start, time_range_end = radicale_filter.parse_time_range(time_range_element)
|
time_range_start, time_range_end = radicale_filter.parse_time_range(time_range_element)
|
||||||
|
|
||||||
expanded_element = _expand(
|
(expanded_element, n_vev) = _expand(
|
||||||
element=element, item=copy.copy(item),
|
element=element, item=copy.copy(item),
|
||||||
start=start, end=end,
|
start=start, end=end,
|
||||||
time_range_start=time_range_start, time_range_end=time_range_end,
|
time_range_start=time_range_start, time_range_end=time_range_end,
|
||||||
|
max_occurrence=max_occurrence,
|
||||||
)
|
)
|
||||||
|
n_vevents += n_vev
|
||||||
found_props.append(expanded_element)
|
found_props.append(expanded_element)
|
||||||
else:
|
else:
|
||||||
found_props.append(element)
|
found_props.append(element)
|
||||||
|
n_vevents += 1
|
||||||
|
# Avoid DoS with too many events
|
||||||
|
if max_occurrence and n_vevents > max_occurrence:
|
||||||
|
raise ValueError("REPORT occurrences limit of {} hit"
|
||||||
|
.format(max_occurrence))
|
||||||
else:
|
else:
|
||||||
not_found_props.append(element)
|
not_found_props.append(element)
|
||||||
|
|
||||||
|
@ -327,7 +335,8 @@ def _expand(
|
||||||
end: datetime.datetime,
|
end: datetime.datetime,
|
||||||
time_range_start: Optional[datetime.datetime] = None,
|
time_range_start: Optional[datetime.datetime] = None,
|
||||||
time_range_end: Optional[datetime.datetime] = None,
|
time_range_end: Optional[datetime.datetime] = None,
|
||||||
) -> ET.Element:
|
max_occurrence: int = 0,
|
||||||
|
) -> Tuple[ET.Element, int]:
|
||||||
vevent_component: vobject.base.Component = copy.copy(item.vobject_item)
|
vevent_component: vobject.base.Component = copy.copy(item.vobject_item)
|
||||||
logger.info("Expanding event %s", item.href)
|
logger.info("Expanding event %s", item.href)
|
||||||
|
|
||||||
|
@ -401,7 +410,13 @@ def _expand(
|
||||||
# that event should be included as it is still ongoing. If no
|
# that event should be included as it is still ongoing. If no
|
||||||
# extra point is generated then it was a no-op.
|
# extra point is generated then it was a no-op.
|
||||||
rstart = start - duration if duration and duration.total_seconds() > 0 else start
|
rstart = start - duration if duration and duration.total_seconds() > 0 else start
|
||||||
recurrences = rruleset.between(rstart, end, inc=True)
|
recurrences = rruleset.between(rstart, end, inc=True, count=max_occurrence)
|
||||||
|
if max_occurrence and len(recurrences) >= max_occurrence:
|
||||||
|
# this shouldn't be > and if it's == then assume a limit
|
||||||
|
# was hit and ignore that maybe some would be filtered out
|
||||||
|
# by EXDATE etc. This is anti-DoS, not precise limits
|
||||||
|
raise ValueError("REPORT occurrences limit of {} hit"
|
||||||
|
.format(max_occurrence))
|
||||||
|
|
||||||
_strip_component(vevent_component)
|
_strip_component(vevent_component)
|
||||||
_strip_single_event(vevent_recurrence, dt_format)
|
_strip_single_event(vevent_recurrence, dt_format)
|
||||||
|
@ -500,14 +515,14 @@ def _expand(
|
||||||
|
|
||||||
if not filtered_vevents:
|
if not filtered_vevents:
|
||||||
element.text = ""
|
element.text = ""
|
||||||
return element
|
return element, 0
|
||||||
else:
|
else:
|
||||||
vevent_component.vevent_list = filtered_vevents
|
vevent_component.vevent_list = filtered_vevents
|
||||||
logger.debug("lbt: vevent_component %s", vevent_component)
|
logger.debug("lbt: vevent_component %s", vevent_component)
|
||||||
|
|
||||||
element.text = vevent_component.serialize()
|
element.text = vevent_component.serialize()
|
||||||
|
|
||||||
return element
|
return element, len(filtered_vevents)
|
||||||
|
|
||||||
|
|
||||||
def _convert_timezone(vevent: vobject.icalendar.RecurringComponent,
|
def _convert_timezone(vevent: vobject.icalendar.RecurringComponent,
|
||||||
|
@ -744,9 +759,9 @@ class ApplicationPartReport(ApplicationBase):
|
||||||
assert item.collection is not None
|
assert item.collection is not None
|
||||||
collection = item.collection
|
collection = item.collection
|
||||||
|
|
||||||
|
max_occurrence = self.configuration.get("reporting", "max_freebusy_occurrence")
|
||||||
if xml_content is not None and \
|
if xml_content is not None and \
|
||||||
xml_content.tag == xmlutils.make_clark("C:free-busy-query"):
|
xml_content.tag == xmlutils.make_clark("C:free-busy-query"):
|
||||||
max_occurrence = self.configuration.get("reporting", "max_freebusy_occurrence")
|
|
||||||
try:
|
try:
|
||||||
status, body = free_busy_report(
|
status, body = free_busy_report(
|
||||||
base_prefix, path, xml_content, collection, self._encoding,
|
base_prefix, path, xml_content, collection, self._encoding,
|
||||||
|
@ -761,7 +776,7 @@ class ApplicationPartReport(ApplicationBase):
|
||||||
try:
|
try:
|
||||||
status, xml_answer = xml_report(
|
status, xml_answer = xml_report(
|
||||||
base_prefix, path, xml_content, collection, self._encoding,
|
base_prefix, path, xml_content, collection, self._encoding,
|
||||||
lock_stack.close)
|
lock_stack.close, max_occurrence)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
"Bad REPORT request on %r: %s", path, e, exc_info=True)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue