From 0232eec2cf5fc7bf678b7cf7748709a46765ef9f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 9 May 2023 17:10:57 +0200 Subject: [PATCH 1/5] allow passing more info to lazy folder Signed-off-by: Robin Appelman --- lib/private/Files/Node/LazyFolder.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index d495d6f4c57..c307b55b5bc 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -207,6 +207,9 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getId() { + if (isset($this->data['fileid'])) { + return $this->data['fileid']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -221,6 +224,9 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getMTime() { + if (isset($this->data['mtime'])) { + return $this->data['mtime']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -228,6 +234,9 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getSize($includeMounts = true): int|float { + if (isset($this->data['size'])) { + return $this->data['size']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -235,6 +244,9 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getEtag() { + if (isset($this->data['etag'])) { + return $this->data['etag']; + } return $this->__call(__FUNCTION__, func_get_args()); } @@ -299,6 +311,12 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getName() { + if (isset($this->data['path'])) { + return basename($this->data['path']); + } + if (isset($this->data['name'])) { + return $this->data['name']; + } return $this->__call(__FUNCTION__, func_get_args()); } From e718cbc6617ad83eda9274c59bb66c3b8f147796 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 9 May 2023 17:11:20 +0200 Subject: [PATCH 2/5] make Node::getParent lazy Signed-off-by: Robin Appelman --- lib/private/Files/Node/Node.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 61ae762638f..0318ae47784 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -300,7 +300,16 @@ class Node implements INode { return $this->root; } - $this->parent = $this->root->get($newPath); + $parentData = [ + 'path' => $newPath, + ]; + if ($this->fileInfo instanceof \OC\Files\FileInfo && isset($this->fileInfo['parent'])) { + $parentData['fileid'] = $this->fileInfo['parent']; + } + + $this->parent = new LazyFolder(function () use ($newPath) { + return $this->root->get($newPath); + }, $parentData); } return $this->parent; From 9055fef5eff14b74c1ecf4399ed4031f01edc044 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 14 Aug 2023 21:12:12 +0200 Subject: [PATCH 3/5] add IFileInfo::getParentId Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trash/TrashItem.php | 4 ++++ lib/private/Files/FileInfo.php | 4 ++++ lib/private/Files/Node/LazyFolder.php | 7 +++++++ lib/private/Files/Node/Node.php | 15 ++++++++------- lib/public/Files/FileInfo.php | 9 +++++++++ 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/apps/files_trashbin/lib/Trash/TrashItem.php b/apps/files_trashbin/lib/Trash/TrashItem.php index 29de0a2ed5a..3bfc905d3a1 100644 --- a/apps/files_trashbin/lib/Trash/TrashItem.php +++ b/apps/files_trashbin/lib/Trash/TrashItem.php @@ -186,4 +186,8 @@ class TrashItem implements ITrashItem { public function getUploadTime(): int { return $this->fileInfo->getUploadTime(); } + + public function getParentId(): int { + return $this->fileInfo->getParentId(); + } } diff --git a/lib/private/Files/FileInfo.php b/lib/private/Files/FileInfo.php index d0550913993..c0a5bf1b92b 100644 --- a/lib/private/Files/FileInfo.php +++ b/lib/private/Files/FileInfo.php @@ -412,4 +412,8 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess { public function getUploadTime(): int { return (int) $this->data['upload_time']; } + + public function getParentId(): int { + return $this->data['parent'] ?? -1; + } } diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index c307b55b5bc..026a16060c4 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -551,4 +551,11 @@ class LazyFolder implements Folder { public function getRelativePath($path) { return PathHelper::getRelativePath($this->getPath(), $path); } + + public function getParentId(): int { + if (isset($this->data['parent'])) { + return $this->data['parent']; + } + return $this->__call(__FUNCTION__, func_get_args()); + } } diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 0318ae47784..bc96a116da7 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -59,10 +59,7 @@ class Node implements INode { protected ?FileInfo $fileInfo; - /** - * @var Node|null - */ - protected $parent; + protected ?INode $parent; private bool $infoHasSubMountsIncluded; @@ -300,13 +297,13 @@ class Node implements INode { return $this->root; } + // gather the metadata we already know about our parent $parentData = [ 'path' => $newPath, + 'fileid' => $this->getFileInfo()->getParentId(), ]; - if ($this->fileInfo instanceof \OC\Files\FileInfo && isset($this->fileInfo['parent'])) { - $parentData['fileid'] = $this->fileInfo['parent']; - } + // and create lazy folder with it instead of always querying $this->parent = new LazyFolder(function () use ($newPath) { return $this->root->get($newPath); }, $parentData); @@ -486,4 +483,8 @@ class Node implements INode { public function getUploadTime(): int { return $this->getFileInfo()->getUploadTime(); } + + public function getParentId(): int { + return $this->fileInfo->getParentId(); + } } diff --git a/lib/public/Files/FileInfo.php b/lib/public/Files/FileInfo.php index 83ae4adef92..da35f7f9028 100644 --- a/lib/public/Files/FileInfo.php +++ b/lib/public/Files/FileInfo.php @@ -299,4 +299,13 @@ interface FileInfo { * @since 18.0.0 */ public function getUploadTime(): int; + + /** + * Get the fileid or the parent folder + * or -1 if this item has no parent folder (because it is the root) + * + * @return int + * @since 28.0.0 + */ + public function getParentId(): int; } From baf8d2e1d01201b32e74f2a46fd217f40bbcf473 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 15 Aug 2023 12:07:54 +0200 Subject: [PATCH 4/5] make LazyFolder::get not load the real folder if we know the path Signed-off-by: Robin Appelman --- lib/private/Files/Node/LazyFolder.php | 50 ++++++++++++++++------- lib/private/Files/Node/LazyRoot.php | 15 +++++-- lib/private/Files/Node/LazyUserFolder.php | 16 ++++---- lib/private/Files/Node/Node.php | 2 +- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 026a16060c4..2400d8e8097 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -29,7 +29,9 @@ namespace OC\Files\Node; use OC\Files\Utils\PathHelper; use OCP\Files\Folder; use OCP\Constants; +use OCP\Files\IRootFolder; use OCP\Files\Mount\IMountPoint; +use OCP\Files\NotPermittedException; /** * Class LazyFolder @@ -41,23 +43,33 @@ use OCP\Files\Mount\IMountPoint; */ class LazyFolder implements Folder { /** @var \Closure(): Folder */ - private $folderClosure; - - /** @var LazyFolder | null */ - protected $folder = null; - + private \Closure $folderClosure; + protected ?Folder $folder = null; + protected IRootFolder $rootFolder; protected array $data; /** - * LazyFolder constructor. - * + * @param IRootFolder $rootFolder * @param \Closure(): Folder $folderClosure + * @param array $data */ - public function __construct(\Closure $folderClosure, array $data = []) { + public function __construct(IRootFolder $rootFolder, \Closure $folderClosure, array $data = []) { + $this->rootFolder = $rootFolder; $this->folderClosure = $folderClosure; $this->data = $data; } + protected function getRootFolder(): IRootFolder { + return $this->rootFolder; + } + + protected function getRealFolder(): Folder { + if ($this->folder === null) { + $this->folder = call_user_func($this->folderClosure); + } + return $this->folder; + } + /** * Magic method to first get the real rootFolder and then * call $method with $args on it @@ -67,11 +79,7 @@ class LazyFolder implements Folder { * @return mixed */ public function __call($method, $args) { - if ($this->folder === null) { - $this->folder = call_user_func($this->folderClosure); - } - - return call_user_func_array([$this->folder, $method], $args); + return call_user_func_array([$this->getRealFolder(), $method], $args); } /** @@ -148,7 +156,7 @@ class LazyFolder implements Folder { * @inheritDoc */ public function get($path) { - return $this->__call(__FUNCTION__, func_get_args()); + return $this->getRootFolder()->get($this->getFullPath($path)); } /** @@ -408,9 +416,23 @@ class LazyFolder implements Folder { * @inheritDoc */ public function getFullPath($path) { + if (isset($this->data['path'])) { + $path = PathHelper::normalizePath($path); + if (!$this->isValidPath($path)) { + throw new NotPermittedException('Invalid path "' . $path . '"'); + } + return $this->data['path'] . $path; + } return $this->__call(__FUNCTION__, func_get_args()); } + public function isValidPath($path) { + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return !(str_contains($path, '/../') || strrchr($path, '/') === '/..'); + } + /** * @inheritDoc */ diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index c01b9fdbb83..ce140124f55 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -33,9 +33,18 @@ use OCP\Files\IRootFolder; * @package OC\Files\Node */ class LazyRoot extends LazyFolder implements IRootFolder { - /** - * @inheritDoc - */ + public function __construct(\Closure $folderClosure, array $data = []) { + parent::__construct($this, $folderClosure, $data); + } + + protected function getRootFolder(): IRootFolder { + $folder = $this->getRealFolder(); + if (!$folder instanceof IRootFolder) { + throw new \Exception('Lazy root folder closure didn\'t return a root folder'); + } + return $folder; + } + public function getUserFolder($userId) { return $this->__call(__FUNCTION__, func_get_args()); } diff --git a/lib/private/Files/Node/LazyUserFolder.php b/lib/private/Files/Node/LazyUserFolder.php index 7093f5ebd8f..503b0af8921 100644 --- a/lib/private/Files/Node/LazyUserFolder.php +++ b/lib/private/Files/Node/LazyUserFolder.php @@ -34,19 +34,17 @@ use OCP\IUser; use Psr\Log\LoggerInterface; class LazyUserFolder extends LazyFolder { - private IRootFolder $root; private IUser $user; private string $path; private IMountManager $mountManager; public function __construct(IRootFolder $rootFolder, IUser $user, IMountManager $mountManager) { - $this->root = $rootFolder; $this->user = $user; $this->mountManager = $mountManager; $this->path = '/' . $user->getUID() . '/files'; - parent::__construct(function () use ($user): Folder { + parent::__construct($rootFolder, function () use ($user): Folder { try { - $node = $this->root->get($this->path); + $node = $this->getRootFolder()->get($this->path); if ($node instanceof File) { $e = new \RuntimeException(); \OCP\Server::get(LoggerInterface::class)->error('User root storage is not a folder: ' . $this->path, [ @@ -56,10 +54,10 @@ class LazyUserFolder extends LazyFolder { } return $node; } catch (NotFoundException $e) { - if (!$this->root->nodeExists('/' . $user->getUID())) { - $this->root->newFolder('/' . $user->getUID()); + if (!$this->getRootFolder()->nodeExists('/' . $user->getUID())) { + $this->getRootFolder()->newFolder('/' . $user->getUID()); } - return $this->root->newFolder($this->path); + return $this->getRootFolder()->newFolder($this->path); } }, [ 'path' => $this->path, @@ -71,7 +69,7 @@ class LazyUserFolder extends LazyFolder { } public function get($path) { - return $this->root->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/')); + return $this->getRootFolder()->get('/' . $this->user->getUID() . '/files/' . ltrim($path, '/')); } /** @@ -79,7 +77,7 @@ class LazyUserFolder extends LazyFolder { * @return \OCP\Files\Node[] */ public function getById($id) { - return $this->root->getByIdInPath((int)$id, $this->getPath()); + return $this->getRootFolder()->getByIdInPath((int)$id, $this->getPath()); } public function getMountPoint() { diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index bc96a116da7..4f466dde532 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -304,7 +304,7 @@ class Node implements INode { ]; // and create lazy folder with it instead of always querying - $this->parent = new LazyFolder(function () use ($newPath) { + $this->parent = new LazyFolder($this->root, function () use ($newPath) { return $this->root->get($newPath); }, $parentData); } From 5ac0e9b63b173a1a06c09cbfdce985dcabef1e2b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 1 Sep 2023 15:58:28 +0200 Subject: [PATCH 5/5] reuse isValidPath from Filesystem Signed-off-by: Robin Appelman --- lib/private/Files/Node/LazyFolder.php | 10 ++-------- lib/private/Files/Node/Node.php | 8 +------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 2400d8e8097..f13cdc0c4f9 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OC\Files\Node; +use OC\Files\Filesystem; use OC\Files\Utils\PathHelper; use OCP\Files\Folder; use OCP\Constants; @@ -418,7 +419,7 @@ class LazyFolder implements Folder { public function getFullPath($path) { if (isset($this->data['path'])) { $path = PathHelper::normalizePath($path); - if (!$this->isValidPath($path)) { + if (!Filesystem::isValidPath($path)) { throw new NotPermittedException('Invalid path "' . $path . '"'); } return $this->data['path'] . $path; @@ -426,13 +427,6 @@ class LazyFolder implements Folder { return $this->__call(__FUNCTION__, func_get_args()); } - public function isValidPath($path) { - if (!str_starts_with($path, '/')) { - $path = '/' . $path; - } - return !(str_contains($path, '/../') || strrchr($path, '/') === '/..'); - } - /** * @inheritDoc */ diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php index 4f466dde532..4b916bd9c1b 100644 --- a/lib/private/Files/Node/Node.php +++ b/lib/private/Files/Node/Node.php @@ -334,13 +334,7 @@ class Node implements INode { * @return bool */ public function isValidPath($path) { - if (!$path || $path[0] !== '/') { - $path = '/' . $path; - } - if (strstr($path, '/../') || strrchr($path, '/') === '/..') { - return false; - } - return true; + return Filesystem::isValidPath($path); } public function isMounted() {