diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index d8fb0ab0d59..790b0264594 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -249,7 +249,7 @@ class ApiController extends Controller { * @param \OCP\Files\Node[] $nodes * @param int $depth The depth to traverse into the contents of each node */ - private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0): array { + private function getChildren(array $nodes, int $depth = 1, int $currentDepth = 0, string $mimeTypeFilter = ''): array { if ($currentDepth >= $depth) { return []; } @@ -264,7 +264,7 @@ class ApiController extends Controller { $entry = [ 'id' => $node->getId(), 'basename' => $basename, - 'children' => $this->getChildren($node->getDirectoryListing(), $depth, $currentDepth + 1), + 'children' => $this->getChildren($node->getDirectoryListing($mimeTypeFilter), $depth, $currentDepth + 1), ]; $displayName = $node->getName(); if ($basename !== $displayName) { @@ -308,8 +308,8 @@ class ApiController extends Controller { 'message' => $this->l10n->t('Invalid folder path'), ], Http::STATUS_BAD_REQUEST); } - $nodes = $node->getDirectoryListing(); - $tree = $this->getChildren($nodes, $depth); + $nodes = $node->getDirectoryListing('httpd/unix-directory'); + $tree = $this->getChildren($nodes, $depth, 0, 'httpd/unix-directory'); } catch (NotFoundException $e) { return new JSONResponse([ 'message' => $this->l10n->t('Folder not found'), diff --git a/apps/files_sharing/lib/External/Cache.php b/apps/files_sharing/lib/External/Cache.php index 027f682d818..865850fd537 100644 --- a/apps/files_sharing/lib/External/Cache.php +++ b/apps/files_sharing/lib/External/Cache.php @@ -41,8 +41,8 @@ class Cache extends \OC\Files\Cache\Cache { return $result; } - public function getFolderContentsById($fileId) { - $results = parent::getFolderContentsById($fileId); + public function getFolderContentsById($fileId, ?string $mimeTypeFilter = null): array { + $results = parent::getFolderContentsById($fileId, $mimeTypeFilter); foreach ($results as &$file) { $file['displayname_owner'] = $this->cloudId->getDisplayId(); } diff --git a/autotest.sh b/autotest.sh index bac67a768be..ec8631a7272 100755 --- a/autotest.sh +++ b/autotest.sh @@ -309,7 +309,7 @@ function execute_tests { if [ ! -z "$USEDOCKER" ] ; then echo "Fire up the postgres docker" DOCKER_CONTAINER_ID=$(docker run -e POSTGRES_DB="$DATABASENAME" -e POSTGRES_USER="$DATABASEUSER" -e POSTGRES_PASSWORD=owncloud -d postgres) - DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") + DATABASEHOST=$(docker inspect --format="{{ range .NetworkSettings.Networks }}{{ .IPAddress }}{{ end }}" "$DOCKER_CONTAINER_ID") echo "Waiting for Postgres initialisation ..." diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php index cfadf7ed4ec..25045d14769 100644 --- a/lib/private/Files/Cache/Cache.php +++ b/lib/private/Files/Cache/Cache.php @@ -218,7 +218,7 @@ class Cache implements ICache { * @param int $fileId the file id of the folder * @return ICacheEntry[] */ - public function getFolderContentsById($fileId) { + public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null) { if ($fileId > -1) { $query = $this->getQueryBuilder(); $query->selectFileCache() @@ -226,13 +226,22 @@ class Cache implements ICache { ->whereStorageId($this->getNumericStorageId()) ->orderBy('name', 'ASC'); + if ($mimeTypeFilter !== null) { + $mimetype = $this->mimetypeLoader->getId($mimeTypeFilter); + if (str_contains($mimeTypeFilter, '/')) { + $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimetype))); + } else { + $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimetype))); + } + } + $metadataQuery = $query->selectMetadata(); $result = $query->executeQuery(); $files = $result->fetchAll(); $result->closeCursor(); - return array_map(function (array $data) use ($metadataQuery) { + return array_map(function (array $data) use ($metadataQuery): ICacheEntry { $data['metadata'] = $metadataQuery->extractMetadata($data)->asArray(); return self::cacheEntryFromData($data, $this->mimetypeLoader); }, $files); diff --git a/lib/private/Files/Cache/FailedCache.php b/lib/private/Files/Cache/FailedCache.php index ce4d1a5eefb..acfc0a947d5 100644 --- a/lib/private/Files/Cache/FailedCache.php +++ b/lib/private/Files/Cache/FailedCache.php @@ -26,12 +26,11 @@ class FailedCache implements ICache { ) { } - - public function getNumericStorageId() { + public function getNumericStorageId(): int { return -1; } - public function get($file) { + public function get($file): false|ICacheEntry { if ($file === '') { return new CacheEntry([ 'fileid' => -1, @@ -46,11 +45,11 @@ class FailedCache implements ICache { } } - public function getFolderContents($folder) { + public function getFolderContents($folder): array { return []; } - public function getFolderContentsById($fileId) { + public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null): array { return []; } @@ -63,15 +62,15 @@ class FailedCache implements ICache { public function update($id, array $data) { } - public function getId($file) { + public function getId($file): int { return -1; } - public function getParentId($file) { + public function getParentId($file): int { return -1; } - public function inCache($file) { + public function inCache($file): bool { return false; } diff --git a/lib/private/Files/Cache/Wrapper/CacheWrapper.php b/lib/private/Files/Cache/Wrapper/CacheWrapper.php index 7d964e135b8..1a75a13a80f 100644 --- a/lib/private/Files/Cache/Wrapper/CacheWrapper.php +++ b/lib/private/Files/Cache/Wrapper/CacheWrapper.php @@ -102,9 +102,9 @@ class CacheWrapper extends Cache { * @param int $fileId the file id of the folder * @return array */ - public function getFolderContentsById($fileId) { - $results = $this->getCache()->getFolderContentsById($fileId); - return array_map([$this, 'formatCacheEntry'], $results); + public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null) { + $results = $this->getCache()->getFolderContentsById($fileId, $mimeTypeFilter); + return array_map($this->formatCacheEntry(...), $results); } /** diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 633ad43e26c..d883e7fbe86 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -663,14 +663,14 @@ class Filesystem { } /** - * get the content of a directory + * Get the content of a directory. * * @param string $directory path under datadirectory - * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @param string $mimeTypeFilter limit returned content to this mimetype or mimepart * @return FileInfo[] */ - public static function getDirectoryContent($directory, $mimetype_filter = '') { - return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter); + public static function getDirectoryContent($directory, string $mimeTypeFilter = '') { + return self::$defaultInstance->getDirectoryContent($directory, $mimeTypeFilter); } /** diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index fef0d634f05..c27f0511080 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -76,16 +76,11 @@ class Folder extends Node implements IFolder { return str_starts_with($node->getPath(), $this->path . '/'); } - /** - * get the content of this directory - * - * @return Node[] - * @throws \OCP\Files\NotFoundException - */ - public function getDirectoryListing() { - $folderContent = $this->view->getDirectoryContent($this->path, '', $this->getFileInfo(false)); + #[Override] + public function getDirectoryListing(?string $mimetypeFilter = null): array { + $folderContent = $this->view->getDirectoryContent($this->path, $mimetypeFilter, $this->getFileInfo(false)); - return array_map(function (FileInfo $info) { + return array_map(function (FileInfo $info): Node { if ($info->getMimetype() === FileInfo::MIMETYPE_FOLDER) { return new Folder($this->root, $this->view, $info->getPath(), $info, $this); } else { diff --git a/lib/private/Files/Node/LazyFolder.php b/lib/private/Files/Node/LazyFolder.php index 50b441ebc69..d04c8aefb7e 100644 --- a/lib/private/Files/Node/LazyFolder.php +++ b/lib/private/Files/Node/LazyFolder.php @@ -415,10 +415,8 @@ class LazyFolder implements Folder { return $this->__call(__FUNCTION__, func_get_args()); } - /** - * @inheritDoc - */ - public function getDirectoryListing() { + #[Override] + public function getDirectoryListing(?string $mimetypeFilter = null): array { return $this->__call(__FUNCTION__, func_get_args()); } diff --git a/lib/private/Files/Node/NonExistingFolder.php b/lib/private/Files/Node/NonExistingFolder.php index 4489fdaf010..ee07e53bedd 100644 --- a/lib/private/Files/Node/NonExistingFolder.php +++ b/lib/private/Files/Node/NonExistingFolder.php @@ -8,6 +8,7 @@ namespace OC\Files\Node; use OCP\Files\NotFoundException; +use Override; class NonExistingFolder extends Folder { /** @@ -118,7 +119,8 @@ class NonExistingFolder extends Folder { throw new NotFoundException(); } - public function getDirectoryListing() { + #[Override] + public function getDirectoryListing(?string $mimetypeFilter = null): never { throw new NotFoundException(); } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 27033efdfba..e7bf6d7aea9 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1481,15 +1481,20 @@ class View { * get the content of a directory * * @param string $directory path under datadirectory - * @param string $mimetype_filter limit returned content to this mimetype or mimepart + * @param ?non-empty-string $mimeTypeFilter limit returned content to this mimetype or mimepart * @return FileInfo[] */ - public function getDirectoryContent($directory, $mimetype_filter = '', ?\OCP\Files\FileInfo $directoryInfo = null) { + public function getDirectoryContent(string $directory, ?string $mimeTypeFilter = null, ?\OCP\Files\FileInfo $directoryInfo = null) { $this->assertPathLength($directory); if (!Filesystem::isValidPath($directory)) { return []; } + /** @psalm-suppress TypeDoesNotContainType For legacy compatibility */ + if ($mimeTypeFilter === '') { + $mimeTypeFilter = null; + } + $path = $this->getAbsolutePath($directory); $path = Filesystem::normalizePath($path); $mount = $this->getMount($directory); @@ -1516,7 +1521,7 @@ class View { } $folderId = $data->getId(); - $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + $contents = $cache->getFolderContentsById($folderId, $mimeTypeFilter); $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); $permissionsMask = ~\OCP\Constants::PERMISSION_SHARE; @@ -1577,79 +1582,79 @@ class View { $rootEntry = $subCache->get(''); } - if ($rootEntry && ($rootEntry->getPermissions() & Constants::PERMISSION_READ)) { - $relativePath = trim(substr($mountPoint, $dirLength), '/'); - if ($pos = strpos($relativePath, '/')) { - //mountpoint inside subfolder add size to the correct folder - $entryName = substr($relativePath, 0, $pos); + if (!$rootEntry || !($rootEntry->getPermissions() & Constants::PERMISSION_READ)) { + continue; + } - // Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet - if (!isset($files[$entryName])) { - try { - [$storage, ] = $this->resolvePath($path . '/' . $entryName); - // make sure we can create the mountpoint folder, even if the user has a quota of 0 - if ($storage->instanceOfStorage(Quota::class)) { - $storage->enableQuota(false); - } - - if ($this->mkdir($path . '/' . $entryName) !== false) { - $info = $this->getFileInfo($path . '/' . $entryName); - if ($info !== false) { - $files[$entryName] = $info; - } - } - - if ($storage->instanceOfStorage(Quota::class)) { - $storage->enableQuota(true); - } - } catch (\Exception $e) { - // Creating the parent folder might not be possible, for example due to a lack of permissions. - $this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]); - } - } - - if (isset($files[$entryName])) { - $files[$entryName]->addSubEntry($rootEntry, $mountPoint); - } - } else { //mountpoint in this folder, add an entry for it - $rootEntry['name'] = $relativePath; - $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; - $permissions = $rootEntry['permissions']; - // do not allow renaming/deleting the mount point if they are not shared files/folders - // for shared files/folders we use the permissions given by the owner - if ($mount instanceof MoveableMount) { - $rootEntry['permissions'] = $permissions | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE; - } else { - $rootEntry['permissions'] = $permissions & (Constants::PERMISSION_ALL - (Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)); - } - - $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ - - // if sharing was disabled for the user we remove the share permissions - if ($sharingDisabled) { - $rootEntry['permissions'] = $rootEntry['permissions'] & ~Constants::PERMISSION_SHARE; - } - - $ownerId = $subStorage->getOwner(''); - if ($ownerId !== false) { - $owner = $this->getUserObjectForOwner($ownerId); - } else { - $owner = null; - } - $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); + if ($mimeTypeFilter !== null) { + if (strpos($mimeTypeFilter, '/') !== false && $rootEntry['mimetype'] !== $mimeTypeFilter) { + continue; + } elseif (strpos($mimeTypeFilter, '/') === false && $rootEntry['mimepart'] !== $mimeTypeFilter) { + continue; } } - } - } - if ($mimetype_filter) { - $files = array_filter($files, function (FileInfo $file) use ($mimetype_filter) { - if (strpos($mimetype_filter, '/')) { - return $file->getMimetype() === $mimetype_filter; - } else { - return $file->getMimePart() === $mimetype_filter; + $relativePath = trim(substr($mountPoint, $dirLength), '/'); + if ($pos = strpos($relativePath, '/')) { + //mountpoint inside subfolder add size to the correct folder + $entryName = substr($relativePath, 0, $pos); + + // Create parent folders if the mountpoint is inside a subfolder that doesn't exist yet + if (!isset($files[$entryName])) { + try { + [$storage, ] = $this->resolvePath($path . '/' . $entryName); + // make sure we can create the mountpoint folder, even if the user has a quota of 0 + if ($storage->instanceOfStorage(Quota::class)) { + $storage->enableQuota(false); + } + + if ($this->mkdir($path . '/' . $entryName) !== false) { + $info = $this->getFileInfo($path . '/' . $entryName); + if ($info !== false) { + $files[$entryName] = $info; + } + } + + if ($storage->instanceOfStorage(Quota::class)) { + $storage->enableQuota(true); + } + } catch (\Exception $e) { + // Creating the parent folder might not be possible, for example due to a lack of permissions. + $this->logger->debug('Failed to create non-existent parent', ['exception' => $e, 'path' => $path . '/' . $entryName]); + } + } + + if (isset($files[$entryName])) { + $files[$entryName]->addSubEntry($rootEntry, $mountPoint); + } + } else { //mountpoint in this folder, add an entry for it + $rootEntry['name'] = $relativePath; + $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file'; + $permissions = $rootEntry['permissions']; + // do not allow renaming/deleting the mount point if they are not shared files/folders + // for shared files/folders we use the permissions given by the owner + if ($mount instanceof MoveableMount) { + $rootEntry['permissions'] = $permissions | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE; + } else { + $rootEntry['permissions'] = $permissions & (Constants::PERMISSION_ALL - (Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)); + } + + $rootEntry['path'] = substr(Filesystem::normalizePath($path . '/' . $rootEntry['name']), strlen($user) + 2); // full path without /$user/ + + // if sharing was disabled for the user we remove the share permissions + if ($sharingDisabled) { + $rootEntry['permissions'] = $rootEntry['permissions'] & ~Constants::PERMISSION_SHARE; + } + + $ownerId = $subStorage->getOwner(''); + if ($ownerId !== false) { + $owner = $this->getUserObjectForOwner($ownerId); + } else { + $owner = null; + } + $files[$rootEntry->getName()] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry, $mount, $owner); } - }); + } } return array_values($files); diff --git a/lib/private/Lockdown/Filesystem/NullCache.php b/lib/private/Lockdown/Filesystem/NullCache.php index 41e76bc0d97..d17ff49c3d3 100644 --- a/lib/private/Lockdown/Filesystem/NullCache.php +++ b/lib/private/Lockdown/Filesystem/NullCache.php @@ -20,11 +20,11 @@ use OCP\Files\Search\ISearchOperator; use OCP\Files\Search\ISearchQuery; class NullCache implements ICache { - public function getNumericStorageId() { + public function getNumericStorageId(): int { return -1; } - public function get($file) { + public function get($file): false|ICacheEntry { if ($file !== '') { return false; } @@ -44,23 +44,23 @@ class NullCache implements ICache { ]); } - public function getFolderContents($folder) { + public function getFolderContents($folder): array { return []; } - public function getFolderContentsById($fileId) { + public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null): array { return []; } - public function put($file, array $data) { + public function put($file, array $data): never { throw new ForbiddenException('This request is not allowed to access the filesystem'); } - public function insert($file, array $data) { + public function insert($file, array $data): never { throw new ForbiddenException('This request is not allowed to access the filesystem'); } - public function update($id, array $data) { + public function update($id, array $data): never { throw new ForbiddenException('This request is not allowed to access the filesystem'); } diff --git a/lib/public/Files/Cache/ICache.php b/lib/public/Files/Cache/ICache.php index cd610b15545..00bed53b44f 100644 --- a/lib/public/Files/Cache/ICache.php +++ b/lib/public/Files/Cache/ICache.php @@ -77,10 +77,12 @@ interface ICache { * Only returns files one level deep, no recursion * * @param int $fileId the file id of the folder + * @param ?non-empty-string $mimeTypeFilter The mimetype or mimepart for which the content should be filtered * @return ICacheEntry[] * @since 9.0.0 + * @since 34.0.0 The $mimetypeFilter was added. */ - public function getFolderContentsById($fileId); + public function getFolderContentsById(int $fileId, ?string $mimeTypeFilter = null); /** * store meta data for a file or folder diff --git a/lib/public/Files/Folder.php b/lib/public/Files/Folder.php index ba23b872abd..9ed1957e8ca 100644 --- a/lib/public/Files/Folder.php +++ b/lib/public/Files/Folder.php @@ -46,13 +46,14 @@ interface Folder extends Node { public function isSubNode($node); /** - * get the content of this directory + * Get the content of this directory. * + * @param ?non-empty-string $mimetypeFilter Limit the returned content to this mimetype or mimepart * @throws \OCP\Files\NotFoundException * @return \OCP\Files\Node[] * @since 6.0.0 */ - public function getDirectoryListing(); + public function getDirectoryListing(?string $mimetypeFilter = null): array; /** * Get the node at $path diff --git a/tests/lib/Encryption/DecryptAllTest.php b/tests/lib/Encryption/DecryptAllTest.php index f7a6497e011..04cfb5b56fe 100644 --- a/tests/lib/Encryption/DecryptAllTest.php +++ b/tests/lib/Encryption/DecryptAllTest.php @@ -248,7 +248,7 @@ class DecryptAllTest extends TestCase { ->method('getDirectoryContent') ->willReturnMap([ [ - '/user1/files', '', null, + '/user1/files', null, null, [ new FileInfo('path', $storage, 'intPath', ['name' => 'foo', 'type' => 'dir'], null), new FileInfo('path', $storage, 'intPath', ['name' => 'bar', 'type' => 'file', 'encrypted' => true], null), @@ -256,7 +256,7 @@ class DecryptAllTest extends TestCase { ], ], [ - '/user1/files/foo', '', null, + '/user1/files/foo', null, null, [ new FileInfo('path', $storage, 'intPath', ['name' => 'subfile', 'type' => 'file', 'encrypted' => true], null) ],