CalDAV Connection Pooling, PROPFIND Discovery & Capabilities #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implement the foundational CalDAV client adapter using
reqwestandrustls, performing authenticated discovery via namespace-aware WebDAVPROPFINDqueries, validating collection capabilities (VEVENTandVTODOcomponent sets, supported reports), enforcing strict security boundaries, and implementing the external calendar synchronization engine (POST /v1/sync/life sync) with RFC 6578sync-collection, fallback enumeration, transactional cursor and projection commits, and observation gap handling (Scenarios R10, R11).Background & Technical Rationale
The
life-servicesystem relies on an upstream Radicale CalDAV server as the authoritative backend for live calendar events and task obligations. Establishing a reliable, secure connection requires robust WebDAV/CalDAV protocol negotiation:Protocol Negotiation & Multi-Stage Discovery:
CalDAV (RFC 4791) builds upon WebDAV (RFC 4918) and WebDAV Current Principal (RFC 5397). Clients cannot assume arbitrary hard-coded paths for calendars. Discovery must execute in sequential stages:
base_urlforDAV:current-user-principal.urn:ietf:params:xml:ns:caldav:calendar-home-set.DAV:resourcetype,DAV:displayname,urn:ietf:params:xml:ns:caldav:supported-calendar-component-set, andDAV:supported-report-set.Collection Validation & Explicit Logical ID Mapping:
Never implicitly bind all discovered collections. Service configuration defines logical collection IDs (e.g.
personal,work) mapped to expected URLs or paths. The discovery engine must verify that each mapped collection actually exists, has<cal:calendar/>in itsresourcetype, and explicitly advertises support for bothVEVENTandVTODOcomponents. If a configured collection is missing or lacks required component types, startup must fail fast withconfiguration_invalidorcalendar_unsupported.Strict Security Boundaries & Defense-in-Depth:
http://user:pass@host/).multistatus). Untrusted or malformed XML exposes the service to XML Entity Expansion (Billion Laughs / XXE) attacks. Usingquick-xml, external DTDs and entity expansion must be disabled, and strict limits on XML body size (e.g. 10 MB) and nesting depth (e.g. 32 levels) must be enforced.D:,d:,DAV:,C:,cal:). XML parsing must never match on raw tag string prefixes; it must resolve XML namespaces by URI (DAV:andurn:ietf:params:xml:ns:caldav) and match local element names.External Sync & Observation Semantics (Scenarios R10 & R11):
External changes made directly to Radicale (or by other CalDAV clients) must be observed and ingested into Life's SQLite store:
sync-collectionREPORT (RFC 6578) when advertised and conformance-tested; otherwise fall back to full href/ETag enumeration with bounded GETs.life_eventswithevidence_class: 'observed_external'), projection changes initem_projection, and cursor advancement insync_statewithin a single local transaction via the database worker thread.Exact Technical References & Specifications
Normative Quotations from Technical Architecture
From
docs/technical/calendar.md(Discovery and conditional writes, lines 5–14):From
docs/technical/operations-security.md(Deployment topology & Configuration contract, lines 11, 20):From
docs/technical/operations-security.md(Authentication and untrusted inputs, lines 41–42):From
docs/technical/verification.md(Deployment scenarios, Scenario D05, line 138):Protocol Specifications & XML Envelopes
1. Bootstrap Discovery: Current User Principal PROPFIND (Depth: 0)
Request:
Expected Response (
207 Multi-Status):2. Principal Discovery: Calendar Home Set PROPFIND (Depth: 0)
Request:
Expected Response (
207 Multi-Status):3. Collection Discovery & Capability Check PROPFIND (Depth: 1)
Request:
Expected Response (
207 Multi-Status):What to Build (Step-by-Step)
1. HTTP Client & Security Boundary (
src/calendar/client.rs)CalDavClientwrappingreqwest::Clientconfigured with:rustls(native root store).connect_timeout(5s),read_timeout/ overallrequest_timeout(20s) sourced from[radicale]config.reqwest::redirect::Policy:target.origin() != initial.origin(), returnPolicy::stop()and flag a cross-origin security violation.target.username() != ""ortarget.password().is_some()), immediately reject.https://tohttp://.username_file,password_file). Strip trailing newlines.Authorization: Basic <base64>header on all requests directed to the configured backend origin.Authorizationheader content.2. Secure WebDAV XML Parser (
src/calendar/xml.rs)quick-xml::Reader:XmlError::MaxDepthExceeded.D:,d:, default namespace, etc.).<multistatus>into structured Rust types:<propstat>'s HTTP status string matchesHTTP/1.1 20xbefore consuming its properties.3. CalDAV Discovery & Collection Capability Validator (
src/calendar/discovery.rs)CalDavDiscovery:discover_principal(&self) -> Result<String, CalDavError>: Executes Depth: 0 PROPFIND onradicale.base_url.discover_home_set(&self, principal_href: &str) -> Result<String, CalDavError>: Executes Depth: 0 PROPFIND on the principal href.discover_collections(&self, home_set_href: &str) -> Result<Vec<DiscoveredCollection>, CalDavError>: Executes Depth: 1 PROPFIND on the calendar-home-set href.<C:calendar/>inresourcetype.supported-calendar-component-set: ensureVEVENTandVTODOare explicitly listed.supported-report-set: ensurecalendar-queryandcalendar-multigetare present.base_urlorigin, enforcing that the path stays within the approved base path.[radicale.collections]in TOML) to the discovered collections:configuration_invalid.VEVENTorVTODO, fail startup withcalendar_unsupported.4. External Calendar Synchronization Engine (
src/calendar/sync.rs,src/api/sync.rs)POST /v1/sync(requiresadminscope) andlife sync --input - [--operation-id KEY] [--json]:{ "collection_ids": Option<Vec<String>>, "mode": "incremental" | "rebaseline" }.DAV:sync-collectioninsupported-report-set.sync-collectionREPORT with currentsync_state.cursor.DatabaseHandle:item_projection.life_eventswithevidence_class: 'observed_external'and dedupe keysync:<collection_id>:<generation>:<uid>.sync_statewith new cursor and generation.5. Diagnostics & Error Mapping
docs/technical/cli-api.md:dependency_unavailable(HTTP 503, CLI exit 5).forbidden/ security violation (HTTP 403, CLI exit 1).configuration_invalid/calendar_unsupported(HTTP 422, CLI exit 2).dependency_unavailablewith descriptive diagnostic logging (credentials redacted).Acceptance Criteria
reqwest+rustlsconnection pooling; idle connections are reused across sequential requests.username_file,password_file) with trailing newlines stripped; credentials and Authorization headers are excluded from trace logs.current-user-principal->calendar-home-set-> collection enumeration via depth-0 and depth-1PROPFINDrequests.D:,d:, empty prefix,C:,cal:) by matching namespace URIs (DAV:andurn:ietf:params:xml:ns:caldav).supported-calendar-component-setcontains bothVEVENTandVTODO; missing either rejects the collection withcalendar_unsupported.calendar-queryandcalendar-multigetare supported reports in the multistatus response.[radicale.collections]binds logical IDs to verified absolute collection URLs; unmapped collections are ignored, and missing configured collections fail startup.https://user:pass@host/) is rejected with a configuration validation error.dependency_unavailable(HTTP 503, exit code 5), clearly distinguishing backend unavailability from an empty calendar.POST /v1/synccommits observation events, projection updates, and sync cursor advancement in one atomic local SQLite transaction.Blocked by
Issue #007: CalDAV Connection Pooling, PROPFIND Discovery & Capabilitiesto CalDAV Connection Pooling, PROPFIND Discovery & Capabilities