Skip to content

caldav, carddav: support If-Match/If-None-Match on client PUT#209

Open
hstern wants to merge 1 commit into
emersion:masterfrom
hstern:mb720-client-ifmatch
Open

caldav, carddav: support If-Match/If-None-Match on client PUT#209
hstern wants to merge 1 commit into
emersion:masterfrom
hstern:mb720-client-ifmatch

Conversation

@hstern

@hstern hstern commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Motivation

caldav.Client.PutCalendarObject and carddav.Client.PutAddressObject send no conditional request headers, so a client cannot perform an optimistic-concurrency write — overwrite only if the resource still carries an expected ETag (If-Match), or create only if absent (If-None-Match). Both methods carried a // TODO: add support for If-None-Match and If-Match.

Changes

  • Add an options parameter to PutCalendarObject / PutAddressObject, reusing the existing server-side PutCalendarObjectOptions / PutAddressObjectOptions types so the client and Backend interface now mirror each other. A nil opts preserves the prior unconditional behaviour; a non-nil opts sets If-Match / If-None-Match.
  • Add webdav.HTTPErrorCode(err) (int, bool), so a client consumer can read the HTTP status off a returned error (e.g. distinguish 412 Precondition Failed from other failures) without reaching into the internal error type — there is currently no public way to do this.

This is a breaking change to the two client method signatures (go-webdav is pre-1.0). Also removed some dead commented-out pipe code in carddav/client.go while there.

Tests

Added caldav/client_ifmatch_test.go and carddav/client_ifmatch_test.go (httptest-backed): assert the If-Match header is sent, that a nil opts sends none, and that a 412 surfaces via webdav.HTTPErrorCode.

Signed-off-by: Henry Stern henry@stern.ca

PutCalendarObject and PutAddressObject sent no conditional request
headers, so a client could not perform an optimistic-concurrency write
(overwrite only if the resource still carries an expected ETag). Add an
options parameter to both, reusing the existing server-side
PutCalendarObjectOptions / PutAddressObjectOptions types so the client
and Backend interface mirror each other. A nil opts preserves the prior
unconditional behaviour; a non-nil opts sets If-Match / If-None-Match.

Also add webdav.HTTPErrorCode, so a client consumer can read the HTTP
status off a returned error (e.g. distinguish 412 Precondition Failed
from other failures) without reaching into the internal error type.

Signed-off-by: Henry Stern <henry@stern.ca>
Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
hstern added a commit to hstern/go-mailbox-720 that referenced this pull request Jun 26, 2026
Graph If-Match / @odata.etag optimistic concurrency on PATCH: faithful conditional PUT for events (CalDAV) + contacts (CardDAV); best-effort account-level ifInState for messages (JMAP). New calendar/contacts/mail ConditionalWriter capabilities; specsubset injects the If-Match param + @odata.etag property; precondition sentinels map to 412.

Requires the go-webdav fork (client If-Match + HTTPErrorCode) via a go.mod replace, pending upstream emersion/go-webdav#209.

Merged with --admin: the one red check (build-test) is the pre-existing flaky TestManagerDeliversForPrincipalOnChange in internal/notify, unrelated to this PR and tracked as MB720-58.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant