⚠️ This issue respects the following points: ⚠️
Bug description
Calendar objects exposed through OCP\Calendar\ICalendarProvider are wrapped for CalDAV by the AppCalendar DAV implementation but the resulting calendar objects don't provide an ETag. This breaks CalDAV clients which expect DAV:getetag to be present in the response to calendar-multiget.
DAVx5, for example, aborts synchronization with:
Received multi-get response without ETag
The problem can be traced to:
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php
where CalendarObject::getETag() returns null.
This is specifically reproducible with calendars supplied through OCP\Calendar\ICalendarProvider.
Possibly related to #29875, but that issue concerns an older Nextcloud version and an ETag missing after creating an event. However, this report concerns the current AppCalendar wrapper and calendar-multiget responses.
This appears related to #14103 ("Server Provides OC-ETag but not ETag"), which also caused DAVx⁵ synchronization to fail when a standard ETag was missing. However, this is a different code path: the problem occurs specifically for calendars exposed through OCP\Calendar\ICalendarProvider and wrapped as an AppCalendar. On Nextcloud 34.0.2, a calendar-multiget response for such calendar objects does not contain DAV:getetag. A normal GET of the same generated .ics object works correctly.
Cause
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php currently does not provide an ETag for the generated calendar object:
https://github.com/nextcloud/server/blob/v34.0.2/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php#L92-L94
Tested workaround
As a proof of concept I changed the AppCalendar CalendarObject to generate an ETag from the serialized calendar object instead of 'null':
public function getETag(): ?string {
return '"' . md5($this->get()) . '"';
}
After this change:
- PROPFIND returns an ETag
- calendar-multiget returns the ETag
- DAVx5 accepts the response
- synchronization succeeds
I don't suggest that MD5 of the serialized representation necessarily has to be the final implementation. The test only demonstrates that providing a stable content-dependent ETag fixes the interoperability problem.
Environment
Nextcloud Server: 34.0.2
PHP: 8.4
Web server: nginx
CalDAV client: DAVx5
Calendar components tested: VJOURNAL and VEVENT
Provider API: OCP\Calendar\ICalendarProvider
Steps to reproduce
- Create/register an
OCP\Calendar\ICalendarProvider.
- Have the provider return one or more calendar components.
- Perform a
calendar-multiget REPORT requesting both DAV:getetag and CALDAV:calendar-data.
Example request:
curl -u ''
-X REPORT
-H 'Depth: 1'
-H 'Content-Type: application/xml; charset=utf-8'
--data '
<c:calendar-multiget
xmlns:d="DAV:"
xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:prop>
<d:getetag/>
<c:calendar-data/>
</d:prop>
<d:href>/remote.php/dav/calendars//app-generated--dav-wrapper--/.ics</d:href>
</c:calendar-multiget>'
'https:///remote.php/dav/calendars//app-generated--dav-wrapper--/'
Expected behavior
The response for every calendar object must contain a stable ETag which should change when the representation of the generated calendar object changes according to RFC4791. This allows CalDAV clients to identify and synchronize changed objects.
https://datatracker.ietf.org/doc/html/rfc4791#section-5.3.4
Actual behavior
calendar-data is returned, but no usable ETag is returned for the calendar object. DAVx5 consequently aborts synchronization with:
Received multi-get response without ETag
Nextcloud Server version
34.0.2
Operating system
Debian 12 Bookworm
PHP engine version
8.4.24
Web server
nginx version: nginx/1.22.1
Database engine version
'dbtype' => 'mysql',
'dbname' => 'nextcloud',
'dbhost' => 'localhost',
Is this bug present after an update or on a fresh install?
Fresh
Are you using the Nextcloud Server Encryption module?
None
What user-backends are you using?
Configuration report
{
"system": {
"passwordsalt": "REMOVED SENSITIVE VALUE",
"secret": "REMOVED SENSITIVE VALUE",
"trusted_domains": [
"localhost",
""
],
"datadirectory": "REMOVED SENSITIVE VALUE",
"dbtype": "mysql",
"version": "34.0.2.1",
"overwrite.cli.url": "https:///drive",
"instanceid": "REMOVED SENSITIVE VALUE",
"dbname": "REMOVED SENSITIVE VALUE",
"dbhost": "REMOVED SENSITIVE VALUE",
"dbtableprefix": "oc_",
"mysql.utf8mb4": true,
"dbuser": "REMOVED SENSITIVE VALUE",
"dbpassword": "REMOVED SENSITIVE VALUE",
"installed": true,
"logfile": "/var/log/nextcloud/nextcloud.log",
"trashbin_retention_obligation": "auto, 30",
"versions_retention_obligation": "auto, 30",
"updatechecker": false,
"memcache.local": "\OC\Memcache\APCu",
"integrity.check.disabled": true,
"filelocking.enabled": true,
"memcache.locking": "\OC\Memcache\Redis",
"redis": {
"host": "REMOVED SENSITIVE VALUE",
"port": "6379",
"timeout": "0.0",
"password": "REMOVED SENSITIVE VALUE"
},
"maintenance": false
}
}
List of activated Apps
=== ENABLED APPS ===
Enabled:
- activity: 7.0.0
- app_api: 34.0.0
- appstore: 1.0.0
- bruteforcesettings: 7.0.0
- calendar: 6.5.4
- circles: 34.0.0
- cloud_federation_api: 1.18.0
- comments: 1.24.0
- contacts: 8.7.6
- contactsinteraction: 1.15.0
- dashboard: 7.14.0
- dav: 1.39.0
- federatedfilesharing: 1.24.0
- federation: 1.24.0
- files: 2.6.0
- files_downloadlimit: 5.2.0
- files_external: 1.26.0
- files_lock: 34.0.1
- files_pdfviewer: 7.0.0-dev.0
- files_reminders: 1.7.0
- files_scripts: 5.0.1
- files_sharing: 1.26.0
- files_trashbin: 1.24.0
- files_versions: 1.27.0
- files_zip: 3.0.1
- firstrunwizard: 7.0.0-dev.0
- gpoddersync: 3.17.0
- journal_caldav: 0.1.0
- journalnotes: 0.2.0
- logreader: 7.0.0
- lookup_server_connector: 1.22.0
- mail: 5.10.13
- nextcloud_announcements: 6.0.0
- notes: 6.0.2
- notifications: 7.0.0-dev.1
- oauth2: 1.22.0
- office: 1.0.0
- password_policy: 6.0.0-dev.0
- photos: 7.0.0
- privacy: 6.0.0-dev.1
- profile: 1.3.0
- provisioning_api: 1.24.0
- recommendations: 7.0.0
- related_resources: 5.0.0-dev.0
- repod: 4.2.1
- richdocuments: 11.1.0
- serverinfo: 6.0.0
- settings: 1.17.0
- sharebymail: 1.24.0
- sociallogin: 6.5.4
- support: 6.0.0
- survey_client: 6.0.0-dev.0
- systemtags: 1.24.0
- tasks: 0.18.1
- text: 8.0.0
- theming: 2.9.0
- twofactor_backupcodes: 1.23.0
- twofactor_totp: 16.0.0
- user_ldap: 1.25.0
- user_status: 1.14.0
- viewer: 7.0.0-dev.0
- weather_status: 1.14.0
- webhook_listeners: 1.6.0
- workflowengine: 2.16.0
Nextcloud Signing status
- enabled: false
- defaultModule:
Nextcloud Logs
Additional info
No response
Bug description
Calendar objects exposed through
OCP\Calendar\ICalendarProviderare wrapped for CalDAV by the AppCalendar DAV implementation but the resulting calendar objects don't provide an ETag. This breaks CalDAV clients which expectDAV:getetagto be present in the response tocalendar-multiget.DAVx5, for example, aborts synchronization with:
The problem can be traced to:
where
CalendarObject::getETag()returnsnull.This is specifically reproducible with calendars supplied through
OCP\Calendar\ICalendarProvider.Possibly related to #29875, but that issue concerns an older Nextcloud version and an ETag missing after creating an event. However, this report concerns the current AppCalendar wrapper and calendar-multiget responses.
This appears related to #14103 ("Server Provides OC-ETag but not ETag"), which also caused DAVx⁵ synchronization to fail when a standard ETag was missing. However, this is a different code path: the problem occurs specifically for calendars exposed through OCP\Calendar\ICalendarProvider and wrapped as an AppCalendar. On Nextcloud 34.0.2, a calendar-multiget response for such calendar objects does not contain DAV:getetag. A normal GET of the same generated .ics object works correctly.
Cause
apps/dav/lib/CalDAV/AppCalendar/CalendarObject.phpcurrently does not provide an ETag for the generated calendar object:https://github.com/nextcloud/server/blob/v34.0.2/apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php#L92-L94
Tested workaround
As a proof of concept I changed the AppCalendar CalendarObject to generate an ETag from the serialized calendar object instead of 'null':
After this change:
I don't suggest that MD5 of the serialized representation necessarily has to be the final implementation. The test only demonstrates that providing a stable content-dependent ETag fixes the interoperability problem.
Environment
Nextcloud Server: 34.0.2
PHP: 8.4
Web server: nginx
CalDAV client: DAVx5
Calendar components tested: VJOURNAL and VEVENT
Provider API: OCP\Calendar\ICalendarProvider
Steps to reproduce
OCP\Calendar\ICalendarProvider.calendar-multigetREPORT requesting bothDAV:getetagandCALDAV:calendar-data.Example request:
curl -u ''
-X REPORT
-H 'Depth: 1'
-H 'Content-Type: application/xml; charset=utf-8'
--data '
<c:calendar-multiget
xmlns:d="DAV:"
xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:prop>
<d:getetag/>
<c:calendar-data/>
</d:prop>
<d:href>/remote.php/dav/calendars//app-generated--dav-wrapper--/.ics</d:href>
</c:calendar-multiget>'
'https:///remote.php/dav/calendars//app-generated--dav-wrapper--/'
Expected behavior
The response for every calendar object must contain a stable ETag which should change when the representation of the generated calendar object changes according to RFC4791. This allows CalDAV clients to identify and synchronize changed objects.
https://datatracker.ietf.org/doc/html/rfc4791#section-5.3.4
Actual behavior
calendar-datais returned, but no usable ETag is returned for the calendar object. DAVx5 consequently aborts synchronization with:Nextcloud Server version
34.0.2
Operating system
Debian 12 Bookworm
PHP engine version
8.4.24
Web server
nginx version: nginx/1.22.1
Database engine version
'dbtype' => 'mysql',
'dbname' => 'nextcloud',
'dbhost' => 'localhost',
Is this bug present after an update or on a fresh install?
Fresh
Are you using the Nextcloud Server Encryption module?
None
What user-backends are you using?
Configuration report
{
"system": {
"passwordsalt": "REMOVED SENSITIVE VALUE",
"secret": "REMOVED SENSITIVE VALUE",
"trusted_domains": [
"localhost",
""
],
"datadirectory": "REMOVED SENSITIVE VALUE",
"dbtype": "mysql",
"version": "34.0.2.1",
"overwrite.cli.url": "https:///drive",
"instanceid": "REMOVED SENSITIVE VALUE",
"dbname": "REMOVED SENSITIVE VALUE",
"dbhost": "REMOVED SENSITIVE VALUE",
"dbtableprefix": "oc_",
"mysql.utf8mb4": true,
"dbuser": "REMOVED SENSITIVE VALUE",
"dbpassword": "REMOVED SENSITIVE VALUE",
"installed": true,
"logfile": "/var/log/nextcloud/nextcloud.log",
"trashbin_retention_obligation": "auto, 30",
"versions_retention_obligation": "auto, 30",
"updatechecker": false,
"memcache.local": "\OC\Memcache\APCu",
"integrity.check.disabled": true,
"filelocking.enabled": true,
"memcache.locking": "\OC\Memcache\Redis",
"redis": {
"host": "REMOVED SENSITIVE VALUE",
"port": "6379",
"timeout": "0.0",
"password": "REMOVED SENSITIVE VALUE"
},
"maintenance": false
}
}
List of activated Apps
=== ENABLED APPS ===
Enabled:
Nextcloud Signing status
Nextcloud Logs
Additional info
No response