{"record":{"id":"99d9775d23b67b86","repo":"nextcloud/server","slug":"version-file-not-accessible-by-current-user","errorCode":null,"errorMessage":"Version file not accessible by current user","messagePattern":"Version file not accessible by current user","errorType":"exception","errorClass":"OCP\\Files\\NotFoundException","httpStatus":404,"severity":"error","filePath":"apps/dav/lib/DAV/ViewOnlyPlugin.php","lineNumber":76,"sourceCode":"\t */\n\tpublic function checkViewOnly(RequestInterface $request): bool {\n\t\t$path = $request->getPath();\n\n\t\ttry {\n\t\t\tassert($this->server !== null);\n\t\t\t$davNode = $this->server->tree->getNodeForPath($path);\n\t\t\tif ($davNode instanceof DavFile) {\n\t\t\t\t// Restrict view-only to nodes which are shared\n\t\t\t\t$node = $davNode->getNode();\n\t\t\t} elseif ($davNode instanceof VersionFile) {\n\t\t\t\t$node = $davNode->getVersion()->getSourceFile();\n\t\t\t\t$currentUserId = $this->userFolder?->getOwner()?->getUID();\n\t\t\t\t// The version source file is relative to the owner storage.\n\t\t\t\t// But we need the node from the current user perspective.\n\t\t\t\tif ($node->getOwner()->getUID() !== $currentUserId) {\n\t\t\t\t\t$node = $this->userFolder->getFirstNodeById($node->getId());\n\t\t\t\t\tif ($node === null) {\n\t\t\t\t\t\tthrow new NotFoundException('Version file not accessible by current user');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t$storage = $node->getStorage();\n\t\t\tif (!$storage->instanceOfStorage(ISharedStorage::class)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t/** @var ISharedStorage $storage */\n\t\t\t$share = $storage->getShare();\n\t\t\tswitch ($request->getMethod()) {\n\t\t\t\tcase 'GET':\n\t\t\t\t\t// If download is disabled, but viewing is allowed, we still allow the GET method to return the file content.\n\t\t\t\t\tif (!$share->canSeeContent()) {\n\t\t\t\t\t\tthrow new Forbidden('Access to this shared resource has been denied because its download permission is disabled.');","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/DAV/ViewOnlyPlugin.php#L58-L94","documentation":"OCP\\Files\\NotFoundException thrown by ViewOnlyPlugin::checkViewOnly() when a GET/COPY/MOVE hits a versions DAV node whose source file is owned by someone else and the current user's folder contains no node with that file id (userFolder->getFirstNodeById() returns null). Because the surrounding catch only handles Sabre\\DAV\\Exception\\NotFound, this OCP exception propagates to the client as HTTP 404 on the version path.","triggerScenarios":"A share recipient requests a version under /remote.php/dav/versions/ (or a meta path) for a file that no longer resolves in their own mount tree: the share was deleted, is unaccepted/pending, a group share was revoked, or the file id was never shared to this user.","commonSituations":"Deep-linked or cached version URLs used after a share was revoked; view-only shares (download disabled) where a client still attempts COPY/MOVE of a version; races between share removal and client synchronization.","solutions":["Re-establish or accept the share so the source file is mounted in the recipient's folder","Have the file owner fetch the version instead of the recipient","Treat a 404 on a version path as 'no longer shared' and drop the cached entry rather than retrying","Check the share state (pending/declined/removed) before requesting versions"],"exampleFix":"// before: any failure of a version GET is fatal\nconst content = await davGet(`/remote.php/dav/versions/${owner}/.../${fileId}/${revId}`);\n// after: 404 on a shared file's version means 'not accessible for this user'\ntry {\n  const content = await davGet(`/remote.php/dav/versions/${owner}/.../${fileId}/${revId}`);\n} catch (e) {\n  if (e.status === 404) { dropCachedVersion(fileId, revId); return; }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// resolve the source file in the current user's tree before touching versions\nconst file = await propfindByFileId(fileId);\nif (file === null) {\n  // not mounted for this user anymore: share revoked/unaccepted -> skip version fetch\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await davGet(versionPath);\n} catch (e) {\n  if (e.status === 404) {\n    // source file no longer mounted for this user: treat as stale state, not transport error\n    removeFromCache(versionPath);\n    return;\n  }\n  throw e;\n}","preventionTips":["PROPFIND the shared file first; if it no longer resolves, skip version requests","Accept pending shares before deep-linking version URLs","Prefer the owner fetching their own versions","Drop cached version entries on 404 instead of retrying"],"tags":["dav","files-versions","sharing","not-found","view-only"],"backgroundTag":"resource-not-found","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}