Browse Source

Merge pull request #50436 from nextcloud/fix/encoding-wrapper-scanner

fix: Harden files scanner for invalid null access
pull/50514/head
Ferdinand Thiessen 9 months ago
committed by GitHub
parent
commit
fb131c1831
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      apps/files_sharing/lib/Cache.php
  2. 5
      apps/files_sharing/lib/External/Scanner.php
  3. 2
      apps/files_sharing/lib/Scanner.php
  4. 15
      build/psalm-baseline.xml
  5. 32
      lib/private/Files/Cache/Cache.php
  6. 246
      lib/private/Files/Cache/Scanner.php
  7. 35
      lib/private/Files/Cache/Wrapper/CacheJail.php
  8. 15
      lib/private/Files/Cache/Wrapper/CacheWrapper.php
  9. 4
      lib/private/Files/ObjectStore/ObjectStoreScanner.php
  10. 10
      lib/private/Files/View.php
  11. 7
      tests/lib/Files/ObjectStore/ObjectStoreScannerTest.php
  12. 8
      tests/lib/Files/Storage/Wrapper/EncodingTest.php

4
apps/files_sharing/lib/Cache.php

@ -63,12 +63,12 @@ class Cache extends CacheJail {
/** @var Jail $currentStorage */
$absoluteRoot = $currentStorage->getJailedPath($absoluteRoot);
}
$this->root = $absoluteRoot;
$this->root = $absoluteRoot ?? '';
}
return $this->root;
}
protected function getGetUnjailedRoot() {
protected function getGetUnjailedRoot(): string {
return $this->sourceRootInfo->getPath();
}

5
apps/files_sharing/lib/External/Scanner.php

@ -29,9 +29,10 @@ class Scanner extends \OC\Files\Cache\Scanner {
* @param string $file file to scan
* @param int $reuseExisting
* @param int $parentId
* @param array | null $cacheData existing data in the cache for the file to be scanned
* @param \OC\Files\Cache\CacheEntry|array|null|false $cacheData existing data in the cache for the file to be scanned
* @param bool $lock set to false to disable getting an additional read lock during scanning
* @return array | null an array of metadata of the scanned file
* @param array|null $data the metadata for the file, as returned by the storage
* @return array|null an array of metadata of the scanned file
*/
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
try {

2
apps/files_sharing/lib/Scanner.php

@ -57,7 +57,7 @@ class Scanner extends \OC\Files\Cache\Scanner {
$sourceScanner = $this->getSourceScanner();
if ($sourceScanner instanceof ObjectStoreScanner) {
// ObjectStoreScanner doesn't scan
return [];
return null;
} else {
return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock);
}

15
build/psalm-baseline.xml

@ -873,11 +873,6 @@
<code><![CDATA[Mount]]></code>
</MoreSpecificReturnType>
</file>
<file src="apps/files_sharing/lib/External/Scanner.php">
<MoreSpecificImplementedParamType>
<code><![CDATA[$cacheData]]></code>
</MoreSpecificImplementedParamType>
</file>
<file src="apps/files_sharing/lib/MountProvider.php">
<RedundantFunctionCall>
<code><![CDATA[array_values]]></code>
@ -1758,14 +1753,10 @@
</MoreSpecificReturnType>
</file>
<file src="lib/private/Files/Cache/Cache.php">
<InvalidArgument>
<code><![CDATA[$parentData]]></code>
</InvalidArgument>
<InvalidNullableReturnType>
<code><![CDATA[array]]></code>
</InvalidNullableReturnType>
<InvalidScalarArgument>
<code><![CDATA[$path]]></code>
<code><![CDATA[\OC_Util::normalizeUnicode($path)]]></code>
</InvalidScalarArgument>
<NullableReturnStatement>
@ -1796,12 +1787,6 @@
<InvalidArgument>
<code><![CDATA[self::SCAN_RECURSIVE_INCOMPLETE]]></code>
</InvalidArgument>
<InvalidReturnStatement>
<code><![CDATA[$existingChildren]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[array[]]]></code>
</InvalidReturnType>
</file>
<file src="lib/private/Files/Cache/Storage.php">
<InvalidNullableReturnType>

32
lib/private/Files/Cache/Cache.php

@ -109,7 +109,7 @@ class Cache implements ICache {
/**
* get the stored metadata of a file or folder
*
* @param string | int $file either the path of a file or folder or the file id for a file or folder
* @param string|int $file either the path of a file or folder or the file id for a file or folder
* @return ICacheEntry|false the cache entry as array or false if the file is not found in the cache
*/
public function get($file) {
@ -131,15 +131,17 @@ class Cache implements ICache {
$data = $result->fetch();
$result->closeCursor();
//merge partial data
if (!$data && is_string($file) && isset($this->partial[$file])) {
return $this->partial[$file];
} elseif (!$data) {
return $data;
} else {
if ($data !== false) {
$data['metadata'] = $metadataQuery->extractMetadata($data)->asArray();
return self::cacheEntryFromData($data, $this->mimetypeLoader);
} else {
//merge partial data
if (is_string($file) && isset($this->partial[$file])) {
return $this->partial[$file];
}
}
return false;
}
/**
@ -886,19 +888,23 @@ class Cache implements ICache {
/**
* Re-calculate the folder size and the size of all parent folders
*
* @param string|boolean $path
* @param array $data (optional) meta data of the folder
* @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
$this->calculateFolderSize($path, $data);
if ($path !== '') {
$parent = dirname($path);
if ($parent === '.' || $parent === '/') {
$parent = '';
}
if ($isBackgroundScan) {
$parentData = $this->get($parent);
if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
if ($parentData !== false
&& $parentData['size'] !== -1
&& $this->getIncompleteChildrenCount($parentData['fileid']) === 0
) {
$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
}
} else {
@ -1009,8 +1015,8 @@ class Cache implements ICache {
}
// only set unencrypted size for a folder if any child entries have it set, or the folder is empty
$shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || $entry['unencrypted_size'] > 0;
if ($entry['size'] !== $totalSize || ($entry['unencrypted_size'] !== $unencryptedTotal && $shouldWriteUnEncryptedSize)) {
$shouldWriteUnEncryptedSize = $unencryptedMax > 0 || $totalSize === 0 || ($entry['unencrypted_size'] ?? 0) > 0;
if ($entry['size'] !== $totalSize || (($entry['unencrypted_size'] ?? 0) !== $unencryptedTotal && $shouldWriteUnEncryptedSize)) {
if ($shouldWriteUnEncryptedSize) {
// if all children have an unencrypted size of 0, just set the folder unencrypted size to 0 instead of summing the sizes
if ($unencryptedMax === 0) {

246
lib/private/Files/Cache/Scanner.php

@ -83,7 +83,7 @@ class Scanner extends BasicEmitter implements IScanner {
*
* @param bool $useTransactions
*/
public function setUseTransactions($useTransactions) {
public function setUseTransactions($useTransactions): void {
$this->useTransactions = $useTransactions;
}
@ -108,9 +108,9 @@ class Scanner extends BasicEmitter implements IScanner {
* @param string $file
* @param int $reuseExisting
* @param int $parentId
* @param array|null|false $cacheData existing data in the cache for the file to be scanned
* @param array|CacheEntry|null|false $cacheData existing data in the cache for the file to be scanned
* @param bool $lock set to false to disable getting an additional read lock during scanning
* @param null $data the metadata for the file, as returned by the storage
* @param array|null $data the metadata for the file, as returned by the storage
* @return array|null an array of metadata of the scanned file
* @throws \OCP\Lock\LockedException
*/
@ -122,139 +122,130 @@ class Scanner extends BasicEmitter implements IScanner {
return null;
}
}
// only proceed if $file is not a partial file, blacklist is handled by the storage
if (!self::isPartialFile($file)) {
// acquire a lock
if (self::isPartialFile($file)) {
return null;
}
// acquire a lock
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
}
try {
$data = $data ?? $this->getData($file);
} catch (ForbiddenException $e) {
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->acquireLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
}
try {
$data = $data ?? $this->getData($file);
} catch (ForbiddenException $e) {
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
return null;
}
try {
if ($data === null) {
$this->removeFromCache($file);
} else {
// pre-emit only if it was a file. By that we avoid counting/treating folders as files
if ($data['mimetype'] !== 'httpd/unix-directory') {
$this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
}
return null;
}
$parent = dirname($file);
if ($parent === '.' || $parent === '/') {
$parent = '';
}
if ($parentId === -1) {
$parentId = $this->cache->getParentId($file);
}
try {
if ($data) {
// pre-emit only if it was a file. By that we avoid counting/treating folders as files
if ($data['mimetype'] !== 'httpd/unix-directory') {
$this->emit('\OC\Files\Cache\Scanner', 'scanFile', [$file, $this->storageId]);
\OC_Hook::emit('\OC\Files\Cache\Scanner', 'scan_file', ['path' => $file, 'storage' => $this->storageId]);
// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
if ($file && $parentId === -1) {
$parentData = $this->scanFile($parent);
if ($parentData === null) {
return null;
}
$parent = dirname($file);
if ($parent === '.' || $parent === '/') {
$parent = '';
}
if ($parentId === -1) {
$parentId = $this->cache->getParentId($file);
}
$parentId = $parentData['fileid'];
}
if ($parent) {
$data['parent'] = $parentId;
}
// scan the parent if it's not in the cache (id -1) and the current file is not the root folder
if ($file && $parentId === -1) {
$parentData = $this->scanFile($parent);
if (!$parentData) {
return null;
}
$parentId = $parentData['fileid'];
}
if ($parent) {
$data['parent'] = $parentId;
}
if (is_null($cacheData)) {
/** @var CacheEntry $cacheData */
$cacheData = $this->cache->get($file);
}
if ($cacheData && $reuseExisting && isset($cacheData['fileid'])) {
// prevent empty etag
$etag = empty($cacheData['etag']) ? $data['etag'] : $cacheData['etag'];
$fileId = $cacheData['fileid'];
$data['fileid'] = $fileId;
// only reuse data if the file hasn't explicitly changed
$mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
// if the folder is marked as unscanned, never reuse etags
if ($mtimeUnchanged && $cacheData['size'] !== -1) {
$data['mtime'] = $cacheData['mtime'];
if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
$data['size'] = $cacheData['size'];
}
if ($reuseExisting & self::REUSE_ETAG && !$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
$data['etag'] = $etag;
}
$cacheData = $cacheData ?? $this->cache->get($file);
if ($reuseExisting && $cacheData !== false && isset($cacheData['fileid'])) {
// prevent empty etag
$etag = empty($cacheData['etag']) ? $data['etag'] : $cacheData['etag'];
$fileId = $cacheData['fileid'];
$data['fileid'] = $fileId;
// only reuse data if the file hasn't explicitly changed
$mtimeUnchanged = isset($data['storage_mtime']) && isset($cacheData['storage_mtime']) && $data['storage_mtime'] === $cacheData['storage_mtime'];
// if the folder is marked as unscanned, never reuse etags
if ($mtimeUnchanged && $cacheData['size'] !== -1) {
$data['mtime'] = $cacheData['mtime'];
if (($reuseExisting & self::REUSE_SIZE) && ($data['size'] === -1)) {
$data['size'] = $cacheData['size'];
}
// we only updated unencrypted_size if it's already set
if ($cacheData['unencrypted_size'] === 0) {
unset($data['unencrypted_size']);
if ($reuseExisting & self::REUSE_ETAG && !$this->storage->instanceOfStorage(IReliableEtagStorage::class)) {
$data['etag'] = $etag;
}
// Only update metadata that has changed
// i.e. get all the values in $data that are not present in the cache already
$newData = $this->array_diff_assoc_multi($data, $cacheData->getData());
// make it known to the caller that etag has been changed and needs propagation
if (isset($newData['etag'])) {
$data['etag_changed'] = true;
}
} else {
// we only updated unencrypted_size if it's already set
unset($data['unencrypted_size']);
$newData = $data;
$fileId = -1;
}
if (!empty($newData)) {
// Reset the checksum if the data has changed
$newData['checksum'] = '';
$newData['parent'] = $parentId;
$data['fileid'] = $this->addToCache($file, $newData, $fileId);
}
$data['oldSize'] = ($cacheData && isset($cacheData['size'])) ? $cacheData['size'] : 0;
if ($cacheData && isset($cacheData['encrypted'])) {
$data['encrypted'] = $cacheData['encrypted'];
// we only updated unencrypted_size if it's already set
if (isset($cacheData['unencrypted_size']) && $cacheData['unencrypted_size'] === 0) {
unset($data['unencrypted_size']);
}
// post-emit only if it was a file. By that we avoid counting/treating folders as files
if ($data['mimetype'] !== 'httpd/unix-directory') {
$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
/**
* Only update metadata that has changed.
* i.e. get all the values in $data that are not present in the cache already
*
* We need the OC implementation for usage of "getData" method below.
* @var \OC\Files\Cache\CacheEntry $cacheData
*/
$newData = $this->array_diff_assoc_multi($data, $cacheData->getData());
// make it known to the caller that etag has been changed and needs propagation
if (isset($newData['etag'])) {
$data['etag_changed'] = true;
}
} else {
$this->removeFromCache($file);
unset($data['unencrypted_size']);
$newData = $data;
$fileId = -1;
}
} catch (\Exception $e) {
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
if (!empty($newData)) {
// Reset the checksum if the data has changed
$newData['checksum'] = '';
$newData['parent'] = $parentId;
$data['fileid'] = $this->addToCache($file, $newData, $fileId);
}
throw $e;
}
// release the acquired lock
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
if ($cacheData !== false) {
$data['oldSize'] = $cacheData['size'] ?? 0;
$data['encrypted'] = $cacheData['encrypted'] ?? false;
}
}
if ($data && !isset($data['encrypted'])) {
$data['encrypted'] = false;
// post-emit only if it was a file. By that we avoid counting/treating folders as files
if ($data['mimetype'] !== 'httpd/unix-directory') {
$this->emit('\OC\Files\Cache\Scanner', 'postScanFile', [$file, $this->storageId]);
\OC_Hook::emit('\OC\Files\Cache\Scanner', 'post_scan_file', ['path' => $file, 'storage' => $this->storageId]);
}
}
} finally {
// release the acquired lock
if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($file, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
return $data;
}
return null;
return $data;
}
protected function removeFromCache($path) {
@ -319,29 +310,26 @@ class Scanner extends BasicEmitter implements IScanner {
if ($reuse === -1) {
$reuse = ($recursive === self::SCAN_SHALLOW) ? self::REUSE_ETAG | self::REUSE_SIZE : self::REUSE_ETAG;
}
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->acquireLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
$this->storage->acquireLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
}
try {
try {
$data = $this->scanFile($path, $reuse, -1, null, $lock);
if ($data && $data['mimetype'] === 'httpd/unix-directory') {
$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock, $data['size']);
$data['size'] = $size;
}
} catch (NotFoundException $e) {
$this->removeFromCache($path);
return null;
$data = $this->scanFile($path, $reuse, -1, lock: $lock);
if ($data !== null && $data['mimetype'] === 'httpd/unix-directory') {
$size = $this->scanChildren($path, $recursive, $reuse, $data['fileid'], $lock, $data['size']);
$data['size'] = $size;
}
} catch (NotFoundException $e) {
$this->removeFromCache($path);
return null;
} finally {
if ($lock) {
if ($this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
}
if ($lock && $this->storage->instanceOfStorage(ILockingStorage::class)) {
$this->storage->releaseLock($path, ILockingProvider::LOCK_SHARED, $this->lockingProvider);
$this->storage->releaseLock('scanner::' . $path, ILockingProvider::LOCK_EXCLUSIVE, $this->lockingProvider);
}
}
return $data;
@ -395,9 +383,9 @@ class Scanner extends BasicEmitter implements IScanner {
* Get the children currently in the cache
*
* @param int $folderId
* @return array[]
* @return array<string, \OCP\Files\Cache\ICacheEntry>
*/
protected function getExistingChildren($folderId) {
protected function getExistingChildren($folderId): array {
$existingChildren = [];
$children = $this->cache->getFolderContentsById($folderId);
foreach ($children as $child) {

35
lib/private/Files/Cache/Wrapper/CacheJail.php

@ -21,19 +21,15 @@ use OCP\Files\Search\ISearchOperator;
* Jail to a subdirectory of the wrapped cache
*/
class CacheJail extends CacheWrapper {
/**
* @var string
*/
protected $root;
protected $unjailedRoot;
protected string $unjailedRoot;
public function __construct(
?ICache $cache,
string $root,
protected string $root,
?CacheDependencies $dependencies = null,
) {
parent::__construct($cache, $dependencies);
$this->root = $root;
if ($cache instanceof CacheJail) {
$this->unjailedRoot = $cache->getSourcePath($root);
@ -42,6 +38,9 @@ class CacheJail extends CacheWrapper {
}
}
/**
* @return string
*/
protected function getRoot() {
return $this->root;
}
@ -55,7 +54,10 @@ class CacheJail extends CacheWrapper {
return $this->unjailedRoot;
}
protected function getSourcePath($path) {
/**
* @return string
*/
protected function getSourcePath(string $path) {
if ($path === '') {
return $this->getRoot();
} else {
@ -95,7 +97,7 @@ class CacheJail extends CacheWrapper {
/**
* get the stored metadata of a file or folder
*
* @param string /int $file
* @param string|int $file
* @return ICacheEntry|false
*/
public function get($file) {
@ -206,12 +208,12 @@ class CacheJail extends CacheWrapper {
/**
* update the folder size and the size of all parent folders
*
* @param string|boolean $path
* @param array $data (optional) meta data of the folder
* @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
if ($this->getCache() instanceof Cache) {
$this->getCache()->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan);
public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
$cache = $this->getCache();
if ($cache instanceof Cache) {
$cache->correctFolderSize($this->getSourcePath($path), $data, $isBackgroundScan);
}
}
@ -223,8 +225,9 @@ class CacheJail extends CacheWrapper {
* @return int|float
*/
public function calculateFolderSize($path, $entry = null) {
if ($this->getCache() instanceof Cache) {
return $this->getCache()->calculateFolderSize($this->getSourcePath($path), $entry);
$cache = $this->getCache();
if ($cache instanceof Cache) {
return $cache->calculateFolderSize($this->getSourcePath($path), $entry);
} else {
return 0;
}

15
lib/private/Files/Cache/Wrapper/CacheWrapper.php

@ -221,12 +221,12 @@ class CacheWrapper extends Cache {
/**
* update the folder size and the size of all parent folders
*
* @param string|boolean $path
* @param array $data (optional) meta data of the folder
* @param array|ICacheEntry|null $data (optional) meta data of the folder
*/
public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
if ($this->getCache() instanceof Cache) {
$this->getCache()->correctFolderSize($path, $data, $isBackgroundScan);
public function correctFolderSize(string $path, $data = null, bool $isBackgroundScan = false): void {
$cache = $this->getCache();
if ($cache instanceof Cache) {
$cache->correctFolderSize($path, $data, $isBackgroundScan);
}
}
@ -238,8 +238,9 @@ class CacheWrapper extends Cache {
* @return int|float
*/
public function calculateFolderSize($path, $entry = null) {
if ($this->getCache() instanceof Cache) {
return $this->getCache()->calculateFolderSize($path, $entry);
$cache = $this->getCache();
if ($cache instanceof Cache) {
return $cache->calculateFolderSize($path, $entry);
} else {
return 0;
}

4
lib/private/Files/ObjectStore/ObjectStoreScanner.php

@ -13,11 +13,11 @@ use OCP\Files\FileInfo;
class ObjectStoreScanner extends Scanner {
public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
return [];
return null;
}
public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
return [];
return null;
}
protected function scanChildren(string $path, $recursive, int $reuse, int $folderId, bool $lock, int|float $oldSize, &$etagChanged = false) {

10
lib/private/Files/View.php

@ -610,13 +610,13 @@ class View {
$this->lockFile($path, ILockingProvider::LOCK_SHARED);
$exists = $this->file_exists($path);
$run = true;
if ($this->shouldEmitHooks($path)) {
$run = true;
$this->emit_file_hooks_pre($exists, $path, $run);
}
if (!$run) {
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
return false;
if (!$run) {
$this->unlockFile($path, ILockingProvider::LOCK_SHARED);
return false;
}
}
try {

7
tests/lib/Files/ObjectStore/ObjectStoreScannerTest.php

@ -35,9 +35,7 @@ class ObjectStoreScannerTest extends TestCase {
$data = "dummy file data\n";
$this->storage->file_put_contents('foo.txt', $data);
$this->assertEquals(
[],
$this->scanner->scanFile('foo.txt'),
$this->assertNull($this->scanner->scanFile('foo.txt'),
'Asserting that no error occurred while scanFile()'
);
}
@ -54,8 +52,7 @@ class ObjectStoreScannerTest extends TestCase {
public function testFolder(): void {
$this->fillTestFolders();
$this->assertEquals(
[],
$this->assertNull(
$this->scanner->scan(''),
'Asserting that no error occurred while scan()'
);

8
tests/lib/Files/Storage/Wrapper/EncodingTest.php

@ -240,4 +240,12 @@ class EncodingTest extends \Test\Files\Storage\Storage {
$entry = $this->instance->getMetaData('/test/' . self::NFD_NAME);
$this->assertEquals(self::NFC_NAME, $entry['name']);
}
/**
* Regression test of https://github.com/nextcloud/server/issues/50431
*/
public function testNoMetadata() {
$this->assertNull($this->instance->getMetaData('/test/null'));
}
}
Loading…
Cancel
Save