mailrs-dav
Server-side CalDAV (RFC 4791) and CardDAV (RFC 6352) handlers for Rust mail / calendar / contacts servers — framework-agnostic, BYO data layer via the CalendarStore and AddressBookStore traits.
Extracted from mailrs so any project that wants to expose CalDAV / CardDAV can do so without re-implementing the multistatus / propstat builder, multiget UID extraction, iCalendar / vCard field scrapers, or the per-resource precondition handling (If-Match, If-None-Match: *).
This is, at the time of writing, the only standalone server-side CalDAV / CardDAV library on crates.io.
Highlights
- Framework-free — no axum / actix / tower / hyper. Each handler returns a
DavResponse { status, headers, body }your server-side adapter translates into your framework's response type. - Store-free — implement
CalendarStore(8 async methods) and / orAddressBookStore(8 async methods); every handler works. - Preconditions handled —
If-Match(etag must equal current) andIf-None-Match: *(resource must not exist) honored on PUT per RFC 4791 §5.3.2 / RFC 6352 §6.3.2. - Standard error envelope —
DavErrorenum with.to_dav_response()for a 4xx/5xx fallback your server can emit unchanged. - Pure helpers exposed —
xml_escape,multistatus,etag_of,options_response,extract_ical_field,extract_ical_datetime,extract_vcard_field,extract_multiget_uids,parse_depth. Use the handlers or grab the pieces.
Methods covered (1.0)
| HTTP verb | Resource scope | RFC section | Notes |
|---|---|---|---|
| OPTIONS | any | RFC 4918 §9.1 | Advertises DAV: 1, 2, 3, calendar-access, addressbook + verbs. |
| PROPFIND | /dav/ |
RFC 5397 | current-user-principal, *-home-set, principal-URL, supported-report-set. |
| PROPFIND | calendar home / calendar collection | RFC 4791 §4 | Auto-creates a default calendar on first hit. |
| PROPFIND | addressbook home / addressbook | RFC 6352 §5 | Auto-creates a default address book on first hit. |
| REPORT | calendar-multiget |
RFC 4791 §7.9 | UIDs extracted from <C:href> children. |
| REPORT | calendar-query |
RFC 4791 §7.8 | Returns all events; time-range filter is 1.x roadmap (see below). |
| REPORT | addressbook-multiget |
RFC 6352 §8.7 | UIDs extracted from <CR:href> children. |
| REPORT | addressbook-query |
RFC 6352 §8.6 | Returns all contacts. |
| GET | event / contact | RFC 4791 §5.3.4 / RFC 6352 §6.3.4 | Verbatim icalendar / vcard body + etag. |
| PUT | event / contact | RFC 4791 §5.3.2 / RFC 6352 §6.3.2 | 201 on create, 204 on update, 412 on precondition fail. |
| DELETE | event / contact | RFC 4918 §9.6 | 204 on delete, 404 when missing. |
Quick start
use async_trait;
use ;
;
# async
How it slots into axum
use Arc;
use ;
use ;
async : ,
State: ,
headers: HeaderMap,
body: String,
)
# use IntoResponse;
The store impl is yours. The mailrs server wraps its sqlx::PgPool in a thin DavAdapter that bridges the schema's row types into mailrs_dav::types — about 250 LOC, worth a read as a reference implementation.
Roadmap
1.0 is the minimum viable surface — enough to drive Apple Calendar / Contacts, Thunderbird, DAVx⁵, and other mainstream clients for read + write of events and contacts. Items planned for 1.x, in rough priority:
- Calendar-query
time-rangefiltering (RFC 4791 §9.7). Todaycalendar-queryreturns the full event list; clients then filter locally. Adding server-side time-range narrows the wire payload for clients that send the filter. - MKCALENDAR / MKCOL (RFC 4791 §5.3.1). Lets clients create new calendars from the UI (currently calendars are created server-side by
ensure_default_calendaror an admin path). - iTIP scheduling extensions (RFC 6638). Inbox / outbox collections for invite delivery. The raw iCalendar already round-trips intact, but the protocol-level scheduling collection wiring is out of
1.0. getctag(CalendarServer ctag extension). A cheap "did anything change in this collection" pre-PROPFIND check for clients that poll.
These will land as additive helpers / new pub fns. The existing trait signatures will not change incompatibly within 1.x.
What's intentionally out of scope
- HTTP auth. Basic / Bearer / OAuth is the wrapper's job — this crate takes a resolved
userstring. - Routing / URL parsing. Handlers take pre-resolved
calendar_id/book_id; the URL → id lookup is the wrapper's call (and trivial — the store trait gives youget_calendar/get_address_book). - Free/busy reports (RFC 4791 §7.10). Separate spec; rarely implemented; clients fall back to fetching events.
- ACL (RFC 3744). The handlers emit a fixed
<D:all/>privilege set for the authenticated owner; multi-owner / shared-calendar ACLs would need a real authorization model your wrapper owns.
Versioning
1.x follows semver. The public API surface is:
CalendarStore/AddressBookStoretrait method signaturesDavErrorenum variantsDavResponsefield shapes- Per-handler
pub fnsignatures incaldav::*,carddav::*,principal::* - Pure helper signatures in
parse::*andxml::*
The exact XML shape inside multistatus may evolve within a minor version as long as it stays compliant with the matching RFC.
License
Licensed under either Apache License, Version 2.0 or MIT license at your option.