Skip to content

[Bug]: CalDAV PUT of a new VEVENT with ATTENDEE + EXDATE fails with TypeError in sabre/vobject ITip\Broker #62950

Description

@splendidinternet

⚠️ This issue respects the following points: ⚠️

Bug description

A CalDAV PUT that creates a new calendar object fails with HTTP 500 when the VEVENT contains both ATTENDEE and EXDATE properties, and the requesting user is an attendee whose PARTSTAT is already set to something other than NEEDS-ACTION.

Root cause: on creation ($isNew === true), OCA\DAV\CalDAV\Schedule\Plugin::calendarObjectChange() passes $currentObject = null into processICalendarChange(). Broker::parseEvent() then builds a default $oldEventInfo that is missing the exdate key:

$oldEventInfo = [
    'organizer' => null,
    'significantChangeHash' => '',
    'attendees' => [],
];

Broker::parseEventForAttendee() accesses it unconditionally at line 686:

if (!in_array($exDate, $oldEventInfo['exdate'])) {

Since PHP 8.1, passing null as the haystack to in_array() raises a fatal TypeError instead of emitting a warning, so the request aborts with a 500 and the event is never created.

sabre-io/vobject master guards this with $oldEventInfo['exdate'] ?? [], but that is on the 5.x line and was never backported to 4.5. The copy currently in nextcloud/3rdparty master still contains the unguarded access, so this is not fixed in any current or upcoming Nextcloud release as far as I can tell.

Vendored dependency: sabre/vobject 4.5.6.

Related, but different: #59695 and #50843 touch the same area, but concern $eventInfo['status'] leaking from a cancelled recurrence override. This report is about the missing exdate key in the default $oldEventInfo on object creation.

I checked #50843: it modifies only apps/dav/lib/CalDAV/TipBroker.php and its test, and does not touch 3rdparty/sabre/vobject/lib/ITip/Broker.php. parseEventForAttendee() is not overridden in TipBroker (no occurrence in the file), so the faulty code path in Sabre's parent Broker is still reachable on NC 34+ as well.

Suggested fix: either backport the null-coalescing guard to the 4.5 branch of sabre/vobject and bump the vendored version, or add 'exdate' => [] to the default $oldEventInfo array. The latter is arguably more correct, since exdate is documented as part of the structure returned by parseEventInfo().

Workaround in use here: setting SCHEDULE-AGENT=CLIENT on all ATTENDEE properties. Attendees carrying that parameter are skipped in parseEventInfo(), so the faulty code path is never entered.

Steps to reproduce

  1. Share a calendar from user A to user B with write access.
  2. As user B, PUT a new object into that calendar (not an update of an existing one), containing an ORGANIZER that is not user B, an ATTENDEE that is user B with PARTSTAT set to a value other than NEEDS-ACTION, and at least one EXDATE.
  3. The request fails with HTTP 500.

repro.ics:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//repro//EN
BEGIN:VEVENT
UID:repro-exdate-attendee-001
DTSTAMP:20260805T100000Z
DTSTART;TZID=Europe/Berlin:20260810T100000
DTEND;TZID=Europe/Berlin:20260810T110000
RRULE:FREQ=WEEKLY;COUNT=5
EXDATE;TZID=Europe/Berlin:20260817T100000
SUMMARY:Repro
ORGANIZER;CN=User A:mailto:usera@example.com
ATTENDEE;CN=User B;PARTSTAT=ACCEPTED:mailto:userb@example.com
END:VEVENT
END:VCALENDAR
curl -u <userB>:<app-password> -X PUT \
  -H "Content-Type: text/calendar; charset=utf-8" \
  --data-binary @repro.ics \
  https://cloud.example.com/remote.php/dav/calendars/<userB>/<calendar>/repro-exdate-attendee-001.ics

Note the PARTSTAT condition: for a new object, parseEvent() only calls parseEventForAttendee() if the attendee has a participation status other than NEEDS-ACTION. Without that, the faulty branch is never reached and the bug does not appear.

Expected behavior

The calendar object is created and the server responds with HTTP 201.

Nextcloud Server version

32

Operating system

Debian/Ubuntu

PHP engine version

PHP 8.3

Web server

Nginx

Database engine version

MariaDB

Is this bug present after an update or on a fresh install?

Upgraded to a MAJOR version (ex. 31 to 32)

Are you using the Nextcloud Server Encryption module?

Encryption is Disabled

What user-backends are you using?

  • Default user-backend (database)
  • LDAP/ Active Directory
  • SSO - SAML
  • Other

Configuration report

{
    "system": {
        "passwordsalt": "***REMOVED SENSITIVE VALUE***",
        "secret": "***REMOVED SENSITIVE VALUE***",
        "trusted_domains": [
            "nextcloud.splendid-internet.de"
        ],
        "datadirectory": "***REMOVED SENSITIVE VALUE***",
        "dbtype": "mysql",
        "version": "33.0.6.2",
        "overwrite.cli.url": "https:\/\/nextcloud.splendid-internet.de",
        "overwritehost": "nextcloud.splendid-internet.de",
        "overwriteprotocol": "https",
        "dbname": "***REMOVED SENSITIVE VALUE***",
        "dbhost": "***REMOVED SENSITIVE VALUE***",
        "dbtableprefix": "oc_",
        "mysql.utf8mb4": true,
        "dbuser": "***REMOVED SENSITIVE VALUE***",
        "dbpassword": "***REMOVED SENSITIVE VALUE***",
        "installed": true,
        "instanceid": "***REMOVED SENSITIVE VALUE***",
        "maintenance": false,
        "maintenance_window_start": 4,
        "default_phone_region": "+49",
        "mail_smtpmode": "smtp",
        "mail_smtphost": "***REMOVED SENSITIVE VALUE***",
        "mail_sendmailmode": "smtp",
        "mail_smtpport": "587",
        "mail_smtpauth": true,
        "mail_from_address": "***REMOVED SENSITIVE VALUE***",
        "mail_domain": "***REMOVED SENSITIVE VALUE***",
        "mail_smtpname": "***REMOVED SENSITIVE VALUE***",
        "mail_smtppassword": "***REMOVED SENSITIVE VALUE***",
        "config_preset": 7,
        "dbdriveroptions": {
            "1002": "SET NAMES utf8mb4 COLLATE utf8mb4_bin"
        },
        "theme": "",
        "loglevel": "2",
        "memcache.locking": "\\OC\\Memcache\\Redis",
        "memcache.local": "\\OC\\Memcache\\Redis",
        "memcache.distributed": "\\OC\\Memcache\\Redis",
        "redis": {
            "host": "***REMOVED SENSITIVE VALUE***",
            "port": 6379,
            "dbindex": 0
        },
        "trusted_proxies": "***REMOVED SENSITIVE VALUE***",
        "defaultapp": "spreed,files",
        "updater.release.channel": "stable",
        "blacklisted_files": [],
        "serverid": 1
    }
}

List of activated Apps

Enabled:
  - admin_audit: 1.23.0
  - app_api: 33.0.0
  - bruteforcesettings: 6.0.0
  - calendar: 6.5.0
  - calendar_resource_management: 0.12.1
  - circles: 33.0.0
  - cloud_federation_api: 1.17.0
  - comments: 1.23.0
  - contacts: 8.7.2
  - contactsinteraction: 1.14.1
  - dashboard: 7.13.0
  - dav: 1.36.0
  - event_update_notification: 2.9.0
  - federatedfilesharing: 1.23.0
  - federation: 1.23.0
  - files: 2.5.0
  - files_bpm: 1.8.2
  - files_downloadlimit: 5.1.0
  - files_fulltextsearch: 33.0.0
  - files_pdfviewer: 6.0.0
  - files_reminders: 1.6.0
  - files_sharing: 1.25.2
  - files_trashbin: 1.23.0
  - files_versions: 1.26.0
  - fileslibreofficeedit: 2.0.1
  - firstrunwizard: 6.0.0
  - fulltextsearch: 33.0.0
  - fulltextsearch_elasticsearch: 33.0.0
  - groupfolders: 21.0.9
  - guests: 4.7.5
  - logreader: 6.0.0
  - lookup_server_connector: 1.21.0
  - mail: 5.10.5
  - notifications: 6.0.0
  - notify_push: 1.3.3
  - oauth2: 1.21.0
  - password_policy: 5.0.0
  - privacy: 5.0.0
  - profile: 1.2.0
  - provisioning_api: 1.23.0
  - quota_warning: 1.24.0
  - recommendations: 6.0.0
  - related_resources: 4.0.0
  - richdocuments: 10.2.0
  - richdocumentscode: 26.4.104
  - serverinfo: 5.0.0
  - settings: 1.16.0
  - sharebymail: 1.23.0
  - spreed: 23.0.7
  - support: 5.0.0
  - survey_client: 5.0.0
  - systemtags: 1.23.0
  - tasks: 0.18.1
  - text: 7.0.1
  - theming: 2.8.0
  - twofactor_backupcodes: 1.22.0
  - twofactor_totp: 15.0.0
  - twofactor_webauthn: 2.7.0
  - updatenotification: 1.23.0
  - user_status: 1.13.0
  - viewer: 6.0.0
  - weather_status: 1.13.0
  - webhook_listeners: 1.5.0
  - workflowengine: 2.15.0
Disabled:
  - activity: 6.0.0 (installed 5.0.0-dev.0)
  - announcementcenter: 7.3.0 (installed 7.3.0)
  - appointments: 2.6.2 (installed 2.6.2)
  - certificate24: 0.4.0 (installed 0.4.0)
  - deck: 1.16.3 (installed 1.16.3)
  - encryption: 2.21.0
  - epubviewer: 1.8.1 (installed 1.8.1)
  - files_antivirus: 6.1.0 (installed 6.1.0)
  - files_external: 1.25.1
  - integration_deepl: 2.1.0 (installed 2.1.0)
  - nextcloud_announcements: 5.0.0 (installed 4.0.0-dev.0)
  - officeonline: 3.1.1 (installed 3.1.1)
  - passwords: 2025.12.21 (installed 2025.12.21)
  - photos: 6.0.0 (installed 5.0.0-dev.1)
  - suspicious_login: 11.0.0
  - twofactor_nextcloud_notification: 7.0.0
  - user_ldap: 1.24.0
  - whiteboard: 1.5.6 (installed 1.5.6)

Nextcloud Signing status

Nextcloud Logs

TypeError: in_array(): Argument #2 ($haystack) must be of type array, null given

Sabre\VObject\ITip\Broker::parseEventForAttendee()          Broker.php:686
  <- Sabre\VObject\ITip\Broker::parseEvent()                 Broker.php:260
  <- Sabre\CalDAV\Schedule\Plugin::processICalendarChange()  Schedule/Plugin.php:616
  <- OCA\DAV\CalDAV\Schedule\Plugin::calendarObjectChange()  Schedule/Plugin.php:208
  <- Sabre\CalDAV\Plugin::validateICalendar()                (beforeCreateFile hook)

Observed in production, four occurrences, all PUT requests from the same client to a shared calendar (values redacted):

2026-08-05T10:44:13+00:00  <user>  PUT  /remote.php/dav/calendars/<user>/<calendar>/<uid>.ics

Additional info

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions