{"record":{"id":"1416d5741816aaf6","repo":"nextcloud/server","slug":"permission-denied-to-create-a-directory-in-the-tra","errorCode":null,"errorMessage":"Permission denied to create a directory in the trashbin","messagePattern":"Permission denied to create a directory in the trashbin","errorType":"exception","errorClass":"Forbidden","httpStatus":403,"severity":"error","filePath":"apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php","lineNumber":70,"sourceCode":"\t\t\t\t'principal' => $ownerPrincipal . '/calendar-proxy-write',\n\t\t\t\t'protected' => true,\n\t\t\t],\n\t\t\t[\n\t\t\t\t'privilege' => '{DAV:}read',\n\t\t\t\t'principal' => $ownerPrincipal . '/calendar-proxy-read',\n\t\t\t\t'protected' => true,\n\t\t\t],\n\t\t];\n\t}\n\n\t#[\\Override]\n\tpublic function createFile($name, $data = null) {\n\t\tthrow new Forbidden('Permission denied to create files in the trashbin');\n\t}\n\n\t#[\\Override]\n\tpublic function createDirectory($name) {\n\t\tthrow new Forbidden('Permission denied to create a directory in the trashbin');\n\t}\n\n\t#[\\Override]\n\tpublic function getChild($name): INode {\n\t\tswitch ($name) {\n\t\t\tcase RestoreTarget::NAME:\n\t\t\t\treturn new RestoreTarget();\n\t\t\tcase DeletedCalendarObjectsCollection::NAME:\n\t\t\t\treturn new DeletedCalendarObjectsCollection(\n\t\t\t\t\t$this->caldavBackend,\n\t\t\t\t\t$this->principalInfo\n\t\t\t\t);\n\t\t}\n\n\t\tthrow new NotFound();\n\t}\n\n\t#[\\Override]","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/CalDAV/Trashbin/TrashbinHome.php#L52-L88","documentation":"TrashbinHome is the CalDAV trashbin collection at /remote.php/dav/calendars/<principal>/trashbin. It implements Sabre\\DAV\\ICollection but is deliberately read-only: deleted-calendar storage cannot be written to directly, and the only valid children are 'restore' and 'objects'. createDirectory() therefore throws Sabre\\DAV\\Exception\\Forbidden unconditionally, which the DAV server maps to HTTP 403 regardless of the caller's ACL privileges.","triggerScenarios":"An MKCOL request whose Request-URI is /remote.php/dav/calendars/<user>/trashbin/<name>; a client that discovers the trashbin via PROPFIND and then offers 'new calendar' on it; code that reuses a calendar-home URL builder for the trashbin path.","commonSituations":"Custom sync or test tooling that blanket-issues MKCOL on every discovered collection; URL mixups between the calendar home (.../calendars/<user>/) and its trashbin sibling (.../calendars/<user>/trashbin/); scripts trying to seed deleted data directly.","solutions":["Create calendars under the calendar home /remote.php/dav/calendars/<user>/, never under trashbin/","To undelete, MOVE the object from trashbin/objects/ into trashbin/restore/ instead of creating collections","Treat 403 on trashbin mutations as expected behavior and do not retry them"],"exampleFix":"// before\nMKCOL /remote.php/dav/calendars/alice/trashbin/holidays/\n-> 403 Permission denied to create a directory in the trashbin\n\n// after\nMKCOL /remote.php/dav/calendars/alice/holidays/\n-> 201 Created","handlingStrategy":"validation","validationCode":"// the trashbin is a read-only sibling of the calendar home\n$isTrashbin = str_contains(rtrim($requestUri, '/') . '/', \"/remote.php/dav/calendars/{$userId}/trashbin/\");\nif ($isTrashbin && $method === 'MKCOL') {\n    throw new LogicException('The CalDAV trashbin is read-only; create calendars under the calendar home');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $client->request('MKCOL', $uri);\n} catch (\\Sabre\\HTTP\\ClientHttpException $e) {\n    if ($e->getResponse()->getStatus() === 403) {\n        return; // read-only trashbin: skip, never retry\n    }\n    throw $e;\n}","preventionTips":["Only the calendar home (.../calendars/<user>/) accepts MKCOL; the trashbin sibling never does","Mark collections whose resourcetype contains {http://nextcloud.org/ns}trash-bin as read-only in client UIs","Never reuse calendar-home URL builders for trashbin paths"],"tags":["caldav","dav","trashbin","http-403","mkcol"],"backgroundTag":"dav-403-forbidden","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}