nextcloud/server · error · Sabre\DAV\Exception\Forbidden
This calendar-object is read-only
Error message
This calendar-object is read-only
What it means
CalendarObject::put() accepts new content only when the backend provider implements ICreateFromString AND the calendar grants PERMISSION_UPDATE; otherwise the object is read-only and PUT fails with Sabre\DAV\Exception\Forbidden (HTTP 403). Note AppCalendar::getPermissions() returns only PERMISSION_READ when the provider lacks ICreateFromString, so the ACL check blocks the write early.
Source
Thrown at apps/dav/lib/CalDAV/AppCalendar/CalendarObject.php:77
#[\Override]
public function setACL(array $acl): void {
throw new Forbidden('Setting ACL is not supported on this node');
}
#[\Override]
public function getSupportedPrivilegeSet(): ?array {
return null;
}
#[\Override]
public function put($data): void {
if ($this->backend instanceof ICreateFromString && $this->calendar->getPermissions() & Constants::PERMISSION_UPDATE) {
if (is_resource($data)) {
$data = stream_get_contents($data) ?: '';
}
$this->backend->createFromString($this->getName(), $data);
} else {
throw new Forbidden('This calendar-object is read-only');
}
}
#[\Override]
public function get(): string {
return $this->vobject->serialize();
}
#[\Override]
public function getContentType(): string {
return 'text/calendar; charset=utf-8';
}
#[\Override]
public function getETag(): ?string {
return null;
}
View on GitHub (pinned to ecdeb153ff)
Solutions
- App developers: implement ICreateFromString on the provider AND include PERMISSION_UPDATE in getPermissions()
- Clients: check write permission (getPermissions() & PERMISSION_UPDATE, or the DAV ACL write-content privilege) before sending PUT
- End users: edit such events in the app that owns the calendar
Example fix
// before
public function getPermissions(): int { return Constants::PERMISSION_READ; }
// after
public function getPermissions(): int { return Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE | Constants::PERMISSION_CREATE; } // with ICreateFromString implemented Defensive patterns
Strategy: type-guard
Validate before calling
// Only PUT when the backend allows updates
$writable = $provider instanceof \OCP\Calendar\ICreateFromString
&& ($provider->getPermissions() & \OCP\Constants::PERMISSION_UPDATE) !== 0;
if (!$writable) {
// skip PUT, keep read-only sync
} Type guard
function canUpdateCalendarObject(\OCP\Calendar\ICalendar $c): bool {
return $c instanceof \OCP\Calendar\ICreateFromString
&& ($c->getPermissions() & \OCP\Constants::PERMISSION_UPDATE) !== 0;
} Try / catch
try {
$object->put($data);
} catch (Sabre\DAV\Exception\Forbidden $e) {
// read-only object; switch this calendar to read-only sync mode
} Prevention
- Providers: implement ICreateFromString and set PERMISSION_UPDATE to enable PUT
- Clients: read the {DAV:}write-content privilege from getACL() before writing
- Treat 403 as a stable capability, not a transient failure
When it happens
Trigger: PUT to calendars/<user>/<app-calendar>/<object>.ics when the provider is read-only (only ICalendar) or its getPermissions() lacks the PERMISSION_UPDATE bit.
Common situations: Two-way CalDAV sync against read-only app calendars (holiday, embedded feeds); providers implementing ICreateFromString but returning permission bits without PERMISSION_UPDATE.
Related errors
- Creating a new entry is not allowed
- Setting ACL is not supported on this node
- Deleting an entry is not implemented
- Node not found
- Setting ACL is not supported on this node
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/9d28263045fd3edb.
Report an issue: GitHub.