Skip to content

One object that does not parse makes a post-filtered search() raise for the whole calendar #815

Open

Description

@tobixen

Disclaimer: AI-generated text below the line, generated by Claude Opus 5.5.
Found while working on PyCalendar/calendaring#13


When Calendar.search() filters the results on the client side, one stored object that icalendar cannot parse makes the whole search raise a plain ValueError. None of the other objects come back.

_filter_search_results() (caldav/search.py, around line 80) calls searcher.check_component(o, ...), and on ValueError falls back to o.icalendar_instance.subcomponents. That parses the same data again, so it raises again:

try:
    filtered = searcher.check_component(o, expand_only=not post_filter)
except ValueError:
    filtered = [
        x for x in o.icalendar_instance.subcomponents if not isinstance(x, Timezone)
    ]

The post-filter is on by default in many cases (search.py, "Setting default value for post_filter"). Among them: a time range that caldav widened itself, todo=True without include_completed, and any server without search.text.case-sensitive (per the hints: Radicale, SOGo, CCS, Stalwart), where it is on for every search. get_object_by_uid() always post-filters. So on those servers, one malformed object written by some other client makes every search in that calendar fail.

Reproduction against caldav 3.6.0, without a server:

from caldav import DAVClient
from caldav.calendarobjectresource import Todo
from caldav.search import CalDAVSearcher, _filter_search_results

BAD = ("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//some client//EN\r\nBEGIN:VTODO\r\nUID:from-phone\r\n"
       "DTSTAMP:20261001T000000Z\r\nSUMMARY:buy milk\r\nDUE:2026-10-20\r\nEND:VTODO\r\nEND:VCALENDAR\r\n")
GOOD = BAD.replace("UID:from-phone", "UID:good").replace("DUE:2026-10-20", "DUE;VALUE=DATE:20261020")
client = DAVClient(url="https://caldav.example.com/")
objects = [Todo(client=client, url=f"https://caldav.example.com/cal/{n}.ics", data=d)
           for n, d in (("good", GOOD), ("bad", BAD))]
searcher = CalDAVSearcher(todo=True)
print(len(_filter_search_results(objects, searcher, post_filter=False)))  # 2
_filter_search_results(objects, searcher, post_filter=True)
# ValueError: Expected datetime, date, or time. Got: '2026-10-20'

(DUE:2026-10-20 is not a valid DATE value; such data is out there.)

Possible fix: skip an object that does not parse, with a log warning, or collect such objects and hand them back to the caller, instead of letting the exception escape. Skipping silently would hide data, so some way for the caller to learn about the skipped objects would be better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions