Skip to content

Commit d75d3c1

Browse files
tobixenclaude
andcommitted
fix: UnboundLocalError in _resolve_properties when PROPFIND response paths don't match
In production mode, error.assert_() only logs rather than raising, so the else-branch of _resolve_properties() would fall through to self.props.update(rc) with rc unbound, causing UnboundLocalError. Adding return {} avoids the crash. Fixes pycalendar/calendar-cli#114 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c39d478 commit d75d3c1

3 files changed

Lines changed: 25 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
1717
### Fixed
1818

1919
* Reusing a `CalDAVSearcher` across multiple `search()` calls could yield inconsistent results: the first call would return only pending tasks (correct), but subsequent calls would change behaviour because `icalendar_searcher.Searcher.check_component()` mutated the `include_completed` field from `None` to `False` as a side-effect. Fixed by passing a copy with `include_completed` already resolved to `filter_search_results()`, leaving the original searcher object unchanged. Fixes https://github.com/python-caldav/caldav/issues/650
20+
* `_resolve_properties()` would crash with `UnboundLocalError` in production mode when a server returned an empty or unrecognisable PROPFIND response (the response paths did not match the request URI and there was more than one or zero paths returned). Fixed by returning `{}` instead of falling through to an unbound variable. Related: https://github.com/pycalendar/calendar-cli/issues/114
2021

2122
## [3.1.0] - 2026-03-19
2223

caldav/davobject.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ def _resolve_properties(self, properties: dict) -> dict:
356356
f"paths found: {list(properties.keys())}"
357357
)
358358
error.assert_(False)
359+
return {}
359360
self.props.update(rc)
360361
return rc
361362

tests/test_caldav_unit.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2713,3 +2713,26 @@ def test_meta_section_returns_multiple_dicts(self, tmp_path):
27132713
"https://work.example.com/dav/",
27142714
"https://personal.example.com/dav/",
27152715
}
2716+
2717+
2718+
class TestResolveProperties:
2719+
"""Tests for _resolve_properties unbound variable bug (issue #647 / calendar-cli #114)."""
2720+
2721+
def _make_calendar(self, path="/calendar/"):
2722+
client = DAVClient(url="https://example.com")
2723+
return Calendar(client=client, url=f"https://example.com{path}")
2724+
2725+
def test_resolve_properties_empty_dict_production_mode(self):
2726+
"""In PRODUCTION mode, error.assert_ only logs; _resolve_properties must
2727+
not crash with UnboundLocalError when properties dict is empty."""
2728+
cal = self._make_calendar()
2729+
with mock.patch.object(error, "debugmode", "PRODUCTION"):
2730+
result = cal._resolve_properties({})
2731+
assert result == {}
2732+
2733+
def test_resolve_properties_unmatched_paths_production_mode(self):
2734+
"""Same but with a non-empty properties dict where path does not match."""
2735+
cal = self._make_calendar("/calendar/")
2736+
with mock.patch.object(error, "debugmode", "PRODUCTION"):
2737+
result = cal._resolve_properties({"/other/path/": {"foo": "bar"}, "/yet/another/": {"baz": "qux"}})
2738+
assert result == {}

0 commit comments

Comments
 (0)