diff --git a/Makefile b/Makefile index 7a7b2073..ad9d80b2 100644 --- a/Makefile +++ b/Makefile @@ -10,11 +10,11 @@ updater.phar: updater.php lib/*.php buildVersionFile.php clean: rm updater.phar index.php -index.php: lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorWithoutData.php lib/Updater.php index.web.php +index.php: lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorFilter.php lib/Updater.php index.web.php # First put openining php tag and license awk '/^<\?php$$/,/\*\//' index.web.php > index.php # Then concat all files while filtering php tag and license - cat lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorWithoutData.php lib/Updater.php index.web.php| grep -v "^namespace" | awk '/^<\?php$$/,/\*\//{next} 1' >> index.php + cat lib/UpdateException.php lib/LogException.php lib/RecursiveDirectoryIteratorFilter.php lib/Updater.php index.web.php| grep -v "^namespace" | awk '/^<\?php$$/,/\*\//{next} 1' >> index.php test/vendor: cd tests && composer install diff --git a/index.php b/index.php index 5dc80709..7463e9d3 100644 --- a/index.php +++ b/index.php @@ -41,28 +41,25 @@ class LogException extends \Exception { } -class RecursiveDirectoryIteratorWithoutData extends \RecursiveFilterIterator { - public function accept(): bool { - $excludes = [ - '.rnd', - '.well-known', - 'data', - '..', - ]; +class RecursiveDirectoryIteratorFilter extends \RecursiveFilterIterator { + private array $excludedPaths; - /** @var \SplFileInfo|false */ - $current = $this->current(); - if (!$current) { - return false; - } + public function __construct( + \RecursiveDirectoryIterator $iterator, + array $excludedPaths = ['data'], + ) { + parent::__construct($iterator); + $this->excludedPaths = array_flip($excludedPaths); + } - return !(in_array($current->getFilename(), $excludes, true) || $current->isDir()); + public function accept(): bool { + return !isset($this->excludedPaths[$this->current()->getFilename()]); } } class Updater { - private string $baseDir; + private string $nextcloudDir; private array $configValues = []; private string $currentVersion = 'unknown'; private string $buildTime; @@ -75,13 +72,15 @@ class Updater { * @param string $baseDir the absolute path to the /updater/ directory in the Nextcloud root * @throws \Exception */ - public function __construct(string $baseDir) { - $this->baseDir = $baseDir; + public function __construct( + private string $baseDir + ) { + $this->nextcloudDir = realpath(dirname($baseDir)); if ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { - $configFileName = rtrim($dir, '/') . '/config.php'; + $configFileName = realpath($dir . '/config.php'); } else { - $configFileName = $this->baseDir . '/../config/config.php'; + $configFileName = $this->nextcloudDir . '/config/config.php'; } if (!file_exists($configFileName)) { throw new \Exception('Could not find config.php. Is this file in the "updater" subfolder of Nextcloud?'); @@ -102,7 +101,7 @@ public function __construct(string $baseDir) { throw new \Exception('Could not read data directory from config.php.'); } - $versionFileName = $this->baseDir . '/../version.php'; + $versionFileName = $this->nextcloudDir . '/version.php'; if (!file_exists($versionFileName)) { // fallback to version in config.php $version = $this->getConfigOptionString('version'); @@ -133,19 +132,15 @@ public function __construct(string $baseDir) { /** * Returns whether the web updater is disabled - * - * @return bool */ - public function isDisabled() { + public function isDisabled(): bool { return $this->disabled; } /** * Returns current version or "unknown" if this could not be determined. - * - * @return string */ - public function getCurrentVersion() { + public function getCurrentVersion(): string { return $this->currentVersion; } @@ -153,7 +148,7 @@ public function getCurrentVersion() { * Returns currently used release channel */ private function getCurrentReleaseChannel(): string { - return ($this->getConfigOptionString('updater.release.channel') ?? 'stable'); + return $this->getConfigOptionString('updater.release.channel') ?? 'stable'; } /** @@ -323,16 +318,19 @@ private function getAppDirectories(): array { /** * Gets the recursive directory iterator over the Nextcloud folder * - * @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator> + * @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator|RecursiveDirectoryIteratorFilter> */ - private function getRecursiveDirectoryIterator(?string $folder = null): \RecursiveIteratorIterator { + private function getRecursiveDirectoryIterator(?string $folder = null, array $excludedPaths = []): \RecursiveIteratorIterator { if ($folder === null) { $folder = $this->baseDir . '/../'; } - return new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); + + $iterator = new \RecursiveDirectoryIterator($folder, \FilesystemIterator::SKIP_DOTS); + if (!empty($excludedPaths)) { + $iterator = new RecursiveDirectoryIteratorFilter($iterator, $excludedPaths); + } + + return new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); } /** @@ -343,7 +341,7 @@ public function checkForExpectedFilesAndFolders(): void { $expectedElements = $this->getExpectedElementsList(); $unexpectedElements = []; - foreach (new \DirectoryIterator($this->baseDir . '/../') as $fileInfo) { + foreach (new \DirectoryIterator($this->nextcloudDir) as $fileInfo) { if (array_search($fileInfo->getFilename(), $expectedElements) === false) { $unexpectedElements[] = $fileInfo->getFilename(); } @@ -361,15 +359,21 @@ public function checkForExpectedFilesAndFolders(): void { public function checkWritePermissions(): void { $this->silentLog('[info] checkWritePermissions()'); - $notWritablePaths = array(); - $dir = new \RecursiveDirectoryIterator($this->baseDir . '/../'); - $filter = new RecursiveDirectoryIteratorWithoutData($dir); - /** @var iterable */ - $it = new \RecursiveIteratorIterator($filter); + $excludedPaths = [ + '.rnd' => true, + '.well-known' => true, + 'data' => true, + ]; + + $it = new \DirectoryIterator($this->nextcloudDir); - foreach ($it as $path => $dir) { - if (!is_writable($path)) { - $notWritablePaths[] = $path; + $notWritablePaths = []; + foreach ($it as $path => $fileInfo) { + if ($fileInfo->isDot() || isset($excludedPaths[$fileInfo->getFilename()])) { + continue; + } + if (!$fileInfo->isWritable()) { + $notWritablePaths[] = $fileInfo->getFilename(); } } if (count($notWritablePaths) > 0) { @@ -442,7 +446,7 @@ public function createBackup(): void { * @var string $path * @var \SplFileInfo $fileInfo */ - foreach ($this->getRecursiveDirectoryIterator($currentDir) as $path => $fileInfo) { + foreach ($this->getRecursiveDirectoryIterator($currentDir, $excludedElements) as $path => $fileInfo) { $fileName = explode($currentDir, $path)[1]; $folderStructure = explode('/', $fileName, -1); @@ -971,8 +975,9 @@ public function deleteOldFiles(): void { } /** - * Moves the specified filed except the excluded elements to the correct position + * Moves the specified files except the excluded elements to the correct position * + * @param string[] $excludedElements * @throws \Exception */ private function moveWithExclusions(string $dataLocation, array $excludedElements): void { diff --git a/index.web.php b/index.web.php index 62495575..21967998 100644 --- a/index.web.php +++ b/index.web.php @@ -68,6 +68,7 @@ public function isAuthenticated(): bool { if (isset($_POST['step'])) { // mark step as failed http_response_code(500); + header('Content-Type: application/json'); echo(json_encode(['proceed' => false, 'response' => $e->getMessage()])); die(); } @@ -154,6 +155,7 @@ public function isAuthenticated(): bool { break; } $updater->endStep($step); + header('Content-Type: application/json'); echo(json_encode(['proceed' => true])); } catch (UpdateException $e) { $data = $e->getData(); @@ -169,6 +171,7 @@ public function isAuthenticated(): bool { $updater->rollbackChanges($step); } http_response_code(500); + header('Content-Type: application/json'); echo(json_encode(['proceed' => false, 'response' => $data])); } catch (\Exception $e) { $message = $e->getMessage(); @@ -184,6 +187,7 @@ public function isAuthenticated(): bool { $updater->rollbackChanges($step); } http_response_code(500); + header('Content-Type: application/json'); echo(json_encode(['proceed' => false, 'response' => $message])); } diff --git a/lib/RecursiveDirectoryIteratorWithoutData.php b/lib/RecursiveDirectoryIteratorFilter.php similarity index 71% rename from lib/RecursiveDirectoryIteratorWithoutData.php rename to lib/RecursiveDirectoryIteratorFilter.php index 52980eb7..11f9b0b1 100644 --- a/lib/RecursiveDirectoryIteratorWithoutData.php +++ b/lib/RecursiveDirectoryIteratorFilter.php @@ -22,21 +22,18 @@ namespace NC\Updater; -class RecursiveDirectoryIteratorWithoutData extends \RecursiveFilterIterator { - public function accept(): bool { - $excludes = [ - '.rnd', - '.well-known', - 'data', - '..', - ]; +class RecursiveDirectoryIteratorFilter extends \RecursiveFilterIterator { + private array $excludedPaths; - /** @var \SplFileInfo|false */ - $current = $this->current(); - if (!$current) { - return false; - } + public function __construct( + \RecursiveDirectoryIterator $iterator, + array $excludedPaths = ['data'], + ) { + parent::__construct($iterator); + $this->excludedPaths = array_flip($excludedPaths); + } - return !(in_array($current->getFilename(), $excludes, true) || $current->isDir()); + public function accept(): bool { + return !isset($this->excludedPaths[$this->current()->getFilename()]); } } diff --git a/lib/Updater.php b/lib/Updater.php index 4adc6820..0c525bf1 100644 --- a/lib/Updater.php +++ b/lib/Updater.php @@ -24,7 +24,7 @@ namespace NC\Updater; class Updater { - private string $baseDir; + private string $nextcloudDir; private array $configValues = []; private string $currentVersion = 'unknown'; private string $buildTime; @@ -37,13 +37,15 @@ class Updater { * @param string $baseDir the absolute path to the /updater/ directory in the Nextcloud root * @throws \Exception */ - public function __construct(string $baseDir) { - $this->baseDir = $baseDir; + public function __construct( + private string $baseDir + ) { + $this->nextcloudDir = realpath(dirname($baseDir)); if ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { - $configFileName = rtrim($dir, '/') . '/config.php'; + $configFileName = realpath($dir . '/config.php'); } else { - $configFileName = $this->baseDir . '/../config/config.php'; + $configFileName = $this->nextcloudDir . '/config/config.php'; } if (!file_exists($configFileName)) { throw new \Exception('Could not find config.php. Is this file in the "updater" subfolder of Nextcloud?'); @@ -64,7 +66,7 @@ public function __construct(string $baseDir) { throw new \Exception('Could not read data directory from config.php.'); } - $versionFileName = $this->baseDir . '/../version.php'; + $versionFileName = $this->nextcloudDir . '/version.php'; if (!file_exists($versionFileName)) { // fallback to version in config.php $version = $this->getConfigOptionString('version'); @@ -95,19 +97,15 @@ public function __construct(string $baseDir) { /** * Returns whether the web updater is disabled - * - * @return bool */ - public function isDisabled() { + public function isDisabled(): bool { return $this->disabled; } /** * Returns current version or "unknown" if this could not be determined. - * - * @return string */ - public function getCurrentVersion() { + public function getCurrentVersion(): string { return $this->currentVersion; } @@ -115,7 +113,7 @@ public function getCurrentVersion() { * Returns currently used release channel */ private function getCurrentReleaseChannel(): string { - return ($this->getConfigOptionString('updater.release.channel') ?? 'stable'); + return $this->getConfigOptionString('updater.release.channel') ?? 'stable'; } /** @@ -285,16 +283,19 @@ private function getAppDirectories(): array { /** * Gets the recursive directory iterator over the Nextcloud folder * - * @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator> + * @return \RecursiveIteratorIterator<\RecursiveDirectoryIterator|RecursiveDirectoryIteratorFilter> */ - private function getRecursiveDirectoryIterator(?string $folder = null): \RecursiveIteratorIterator { + private function getRecursiveDirectoryIterator(?string $folder = null, array $excludedPaths = []): \RecursiveIteratorIterator { if ($folder === null) { $folder = $this->baseDir . '/../'; } - return new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); + + $iterator = new \RecursiveDirectoryIterator($folder, \FilesystemIterator::SKIP_DOTS); + if (!empty($excludedPaths)) { + $iterator = new RecursiveDirectoryIteratorFilter($iterator, $excludedPaths); + } + + return new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); } /** @@ -305,7 +306,7 @@ public function checkForExpectedFilesAndFolders(): void { $expectedElements = $this->getExpectedElementsList(); $unexpectedElements = []; - foreach (new \DirectoryIterator($this->baseDir . '/../') as $fileInfo) { + foreach (new \DirectoryIterator($this->nextcloudDir) as $fileInfo) { if (array_search($fileInfo->getFilename(), $expectedElements) === false) { $unexpectedElements[] = $fileInfo->getFilename(); } @@ -323,15 +324,21 @@ public function checkForExpectedFilesAndFolders(): void { public function checkWritePermissions(): void { $this->silentLog('[info] checkWritePermissions()'); - $notWritablePaths = array(); - $dir = new \RecursiveDirectoryIterator($this->baseDir . '/../'); - $filter = new RecursiveDirectoryIteratorWithoutData($dir); - /** @var iterable */ - $it = new \RecursiveIteratorIterator($filter); + $excludedPaths = [ + '.rnd' => true, + '.well-known' => true, + 'data' => true, + ]; + + $it = new \DirectoryIterator($this->nextcloudDir); - foreach ($it as $path => $dir) { - if (!is_writable($path)) { - $notWritablePaths[] = $path; + $notWritablePaths = []; + foreach ($it as $path => $fileInfo) { + if ($fileInfo->isDot() || isset($excludedPaths[$fileInfo->getFilename()])) { + continue; + } + if (!$fileInfo->isWritable()) { + $notWritablePaths[] = $fileInfo->getFilename(); } } if (count($notWritablePaths) > 0) { @@ -404,7 +411,7 @@ public function createBackup(): void { * @var string $path * @var \SplFileInfo $fileInfo */ - foreach ($this->getRecursiveDirectoryIterator($currentDir) as $path => $fileInfo) { + foreach ($this->getRecursiveDirectoryIterator($currentDir, $excludedElements) as $path => $fileInfo) { $fileName = explode($currentDir, $path)[1]; $folderStructure = explode('/', $fileName, -1); @@ -933,8 +940,9 @@ public function deleteOldFiles(): void { } /** - * Moves the specified filed except the excluded elements to the correct position + * Moves the specified files except the excluded elements to the correct position * + * @param string[] $excludedElements * @throws \Exception */ private function moveWithExclusions(string $dataLocation, array $excludedElements): void { diff --git a/updater.phar b/updater.phar index d21639a6..796a90be 100755 Binary files a/updater.phar and b/updater.phar differ diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 383d88b0..562d6b62 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -24,7 +24,7 @@ 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'NC\\Updater\\CommandApplication' => $baseDir . '/lib/CommandApplication.php', 'NC\\Updater\\LogException' => $baseDir . '/lib/LogException.php', - 'NC\\Updater\\RecursiveDirectoryIteratorWithoutData' => $baseDir . '/lib/RecursiveDirectoryIteratorWithoutData.php', + 'NC\\Updater\\RecursiveDirectoryIteratorFilter' => $baseDir . '/lib/RecursiveDirectoryIteratorFilter.php', 'NC\\Updater\\UpdateCommand' => $baseDir . '/lib/UpdateCommand.php', 'NC\\Updater\\UpdateException' => $baseDir . '/lib/UpdateException.php', 'NC\\Updater\\Updater' => $baseDir . '/lib/Updater.php', diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index d9f93773..d1b65dfe 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -95,7 +95,7 @@ class ComposerStaticInitba7c5c8f0885d00c3b669d0399f96c80 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'NC\\Updater\\CommandApplication' => __DIR__ . '/../..' . '/lib/CommandApplication.php', 'NC\\Updater\\LogException' => __DIR__ . '/../..' . '/lib/LogException.php', - 'NC\\Updater\\RecursiveDirectoryIteratorWithoutData' => __DIR__ . '/../..' . '/lib/RecursiveDirectoryIteratorWithoutData.php', + 'NC\\Updater\\RecursiveDirectoryIteratorFilter' => __DIR__ . '/../..' . '/lib/RecursiveDirectoryIteratorFilter.php', 'NC\\Updater\\UpdateCommand' => __DIR__ . '/../..' . '/lib/UpdateCommand.php', 'NC\\Updater\\UpdateException' => __DIR__ . '/../..' . '/lib/UpdateException.php', 'NC\\Updater\\Updater' => __DIR__ . '/../..' . '/lib/Updater.php', diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 0c941aa1..5bbf451e 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '14ccc22088938656fb221e3d7f7e8928a58332ab', + 'reference' => '782b428fc1077036feb12e897047850d9855a1cd', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '14ccc22088938656fb221e3d7f7e8928a58332ab', + 'reference' => '782b428fc1077036feb12e897047850d9855a1cd', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),