Add calendar (CalDAV) and contacts (CardDAV) support (#59) - #133
Open
a-schild wants to merge 6 commits into
Open
Add calendar (CalDAV) and contacts (CardDAV) support (#59)#133a-schild wants to merge 6 commits into
a-schild wants to merge 6 commits into
Conversation
Calendars and address books are reachable below remote.php/dav/, but the library had no way to talk to them: the path resolver carried CALDAV and VCARD types that nothing used. Both protocols are structurally identical - a per-user home collection of collections holding text resources - so the shared plumbing lives in ADavCollectionHandler and the two connectors only supply the namespaces and element names that differ. Entries are exchanged as raw iCalendar/vCard text. Parsing them would mean taking on ical4j and ez-vcard as compile dependencies and tying our public API to their model classes, so that is left to the caller. Sardine models plain WebDAV properties and drops the calendar-data and address-data payloads, so the REPORT responses are read with a small StAX reader, hardened against XXE like XMLAnswerParser. Listing an entire collection is a PROPFIND to enumerate it plus one multiget REPORT for the payloads, rather than one GET per entry. DAV paths use the internal user id from the provisioning API rather than the login name, which the 2021 discussion on the issue flagged as a problem when the two differ. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A PROPFIND naming specific properties returns only those, so resourcetype was never sent back, DavResource.getResourceTypes() was empty and the filter dropped every calendar and address book from the listing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getCalendarEntriesInRange gained a boolean overload that adds the CalDAV expand element to the requested calendar-data, so the server resolves a recurring event into one VEVENT per occurrence in the range instead of returning the stored event with its RRULE. The expanded form is a computed view of that range and not the stored resource, so the javadoc warns against writing it back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the long-standing request in #59 for calendar access, and the address book support asked for in its comments.
Try it without merging: this branch is published as
14.3.0-SNAPSHOTto the Central Portal snapshot repository — see Trying a pre-release snapshot in the README.API
All methods are exposed on
NextcloudConnector:listCalendars()listAddressBooks()getCalendarEntries(cal)getContacts(book)getCalendarEntriesInRange(cal, from, to)getCalendarEntriesInRange(cal, from, to, expand)getCalendarEntry(cal, name)getContact(book, name)putCalendarEntry(cal, name, ics[, etag])putContact(book, name, vcf[, etag])deleteCalendarEntry(cal, name)deleteContact(book, name)createCalendar(name, displayName, colour)createAddressBook(name, displayName, description)deleteCalendar(name)deleteAddressBook(name)Design notes
CalendarEntry.getData()/Contact.getData()return the iCalendar/vCard text verbatim. Parsing would have meant adding ical4j and ez-vcard as compile-scope dependencies for every consumer and tying the public API to their model classes.pom.xmlgains no dependency.ADavCollectionHandler;CalendarsandAddressBooksonly supply the namespaces, element names and content types that differ.getCalendarEntriesInRange(..., true)adds the CalDAV<C:expand>element, so the server resolves a recurring event into oneVEVENTper occurrence in the range (noRRULE, each with its ownRECURRENCE-ID, overrides applied, times in UTC). The javadoc warns that an expanded result is a computed view of that range and must not be written back. The 3-argument overload is unchanged and returns events as stored.calendar-data/address-datapayloads, so REPORT responses are read by a small StAXMultistatusParser, hardened against XXE the same wayXMLAnswerParseris.MKCALENDARand extendedMKCOLuse aDavMethodon raw HttpClient, following theSystemTagsprecedent.put*overloads taking an etag sendIf-Match, so a change made in the meantime fails loudly instead of being overwritten.requireValidPathSegmentand are percent-encoded, consistent with the 14.2.0 hardening.Incidental changes
authorizationHeader(),buildSyncClient()andshutdownSardine()moved fromSystemTagsup intoAWebdavHandlerrather than being duplicated by the new connectors.NextcloudApiExceptiongained a(String message, Throwable cause)constructor, so failures can carry both context and cause.Tests
152 tests pass in CI against the Testcontainers Nextcloud server, 24 of them new:
MultistatusParserTest— 8 unit tests, no server needed (payload extraction, weak etags, 404 propstats, XXE, malformed XML).TestCalendars— 9 integration tests: create calendar → put → get → list → range query → recurrence expansion (aFREQ=DAILY;COUNT=3event asserted as 1VEVENTwithRRULEunexpanded, and 3VEVENTs withRECURRENCE-IDand noRRULEexpanded) → etag precondition → delete → delete calendar.TestAddressBooks— 7 integration tests over the same lifecycle.Two bugs were caught by these tests rather than by users: an empty
<calendar-data/>in a 404 propstat created phantom entries, and collection listing returned nothing because a targeted PROPFIND does not returnresourcetypeunless it is explicitly requested.Version bumped to
14.3.0-SNAPSHOT(new feature, backwards compatible).Closes #59
🤖 Generated with Claude Code