Skip to content
This repository was archived by the owner on Aug 2, 2026. It is now read-only.

Add CalDAV read-only calendar backend - #355

Open
ssmurfgg04-gif wants to merge 1 commit into
emersion:masterfrom
ssmurfgg04-gif:feat/caldav-support
Open

Add CalDAV read-only calendar backend#355
ssmurfgg04-gif wants to merge 1 commit into
emersion:masterfrom
ssmurfgg04-gif:feat/caldav-support

Conversation

@ssmurfgg04-gif

Copy link
Copy Markdown

Summary

Implements a read-only CalDAV backend exposing Proton Calendar over RFC 4791,
mirroring the structure of the existing CardDAV backend. Closes #282.

This is the minimum viable foundation: clients can discover calendars and
sync existing events. Write operations (create/update/delete events) are
explicitly rejected with HTTP 403 for now, matching how CardDAV rejects
CreateAddressBook/DeleteAddressBook. Once event write support is added
to the protonmail package, the same backend can be extended.

What works

  • PROPFIND on root → returns current-user-principal and CalDAV calendar-home-set
  • PROPFIND on /calendar → lists all Proton calendars
  • PROPFIND on /calendar/<id> → returns calendar metadata (name, description, supported components)
  • PROPFIND on /calendar/<id>/ → lists all event resources in the calendar
  • GET on /calendar/<id>/<eventID>.ics → returns decrypted iCalendar object
  • REPORT (calendar-query) → filtered listings via caldav.Filter
  • REPORT (calendar-multiget) → batch object fetch

What is not yet supported

  • PUT (create/update event) → returns 403 Forbidden
  • DELETE (remove event) → returns 403 Forbidden
  • MKCALENDAR (create new calendar) → returns 403 Forbidden

Reason: the protonmail package only exposes ListCalendars and
ListCalendarEvents. Adding write methods requires implementing the
calendar event create/update/delete endpoints with their key-packet
encryption scheme, which I haven't done in this PR to keep scope reviewable.

Architecture

caldav/caldav.go implements the full caldav.Backend interface from
github.com/emersion/go-webdav/caldav. The structure mirrors
carddav/carddav.go:

  • backend struct holds the Proton client and user's PGP private keys
  • ListCalendars proxies to protonmail.Client.ListCalendars
  • ListCalendarObjects enumerates events via ListCalendarEvents over a
    10-year window centred on now, decrypts each card, and wraps in a
    caldav.CalendarObject
  • GetCalendarObject does the same but filters by event ID
  • QueryCalendarObjects delegates to caldav.Filter on the full listing

Decryption

Proton Calendar encrypts event data with a symmetric session key, which is
itself encrypted with the user's PGP key and shipped in either
CalendarKeyPacket (personal events) or SharedKeyPacket (shared events).
The encrypted event payload is base64-encoded in CalendarEventCard.Data.

decryptCalendarEventCard performs:

  1. Base64-decode the key packet
  2. openpgp.ReadMessage to decrypt the session key using the user's private keyring
  3. Base64-decode the card Data
  4. Re-armor and openpgp.ReadMessage again, supplying the session key via the symmetric prompt callback

This pattern mirrors ContactCard.Read in protonmail/contacts.go, adapted
for the calendar scheme where the key packet is detached from the data packet.

If Proton has changed their wire format, the function returns a clear error
describing which step failed — easy to debug and patch.

CLI

New subcommand and flags, matching the CardDAV pattern:

hydroxide caldav                              # run only the CalDAV server
hydroxide serve                               # runs all servers including CalDAV
hydroxide serve -disable-caldav               # opt out
hydroxide serve -caldav-port 8003             # custom port (default 8081)
hydroxide serve -caldav-host 0.0.0.0          # bind to all interfaces

Testing

  • caldav/caldav_test.go covers path parsing for calendar and object paths
    (the parts that can be unit-tested without real Proton API access)
  • All tests pass: go test ./caldav/ → ok
  • Full project builds clean: go build ./... → ok
  • go vet ./caldav/ → clean
  • Binary starts and shows the new caldav subcommand in help output

Caveat I want to be transparent about: I do not have access to a Proton
account in this environment, so I could not run an end-to-end test against
the real Proton Calendar API. The decryption code follows the documented
pattern from protonmail/contacts.go and the field types in
protonmail/calendar.go, but the maintainer (or any tester with a Proton
account) should verify event decryption works against live data before
merge. If the wire format has changed, the error returned will identify
which step failed.

Files changed

  • caldav/caldav.go (new, 428 lines) — full CalDAV backend implementation
  • caldav/caldav_test.go (new, 82 lines) — path parsing tests
  • cmd/hydroxide/main.go — added listenAndServeCalDAV, caldav subcommand, -caldav-host, -caldav-port, -disable-caldav flags, and serve integration
  • go.mod / go.sum — added github.com/emersion/go-ical dependency

Notes on existing PRs

I see other PRs in the bounty queue attempting this. I haven't read their
code to avoid contamination; this implementation is written from scratch
based on the protonmail package API and the CardDAV backend pattern. If
the maintainer prefers a different approach, I'm happy to rework.

License

MIT (matches hydroxide's LICENSE).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant