aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Installer.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Installer.php')
-rw-r--r--lib/private/Installer.php100
1 files changed, 88 insertions, 12 deletions
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index 00fdd84c1bc..91d20a129ae 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -10,20 +10,23 @@ declare(strict_types=1);
namespace OC;
use Doctrine\DBAL\Exception\TableExistsException;
+use OC\App\AppStore\AppNotFoundException;
use OC\App\AppStore\Bundles\Bundle;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Archive\TAR;
use OC\DB\Connection;
use OC\DB\MigrationService;
+use OC\Files\FilenameValidator;
use OC_App;
-use OC_Helper;
use OCP\App\IAppManager;
+use OCP\Files;
use OCP\HintException;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\ITempManager;
use OCP\Migration\IOutput;
+use OCP\Server;
use phpseclib\File\X509;
use Psr\Log\LoggerInterface;
@@ -167,16 +170,44 @@ class Installer {
}
/**
+ * Get the path where to install apps
+ *
+ * @throws \RuntimeException if an app folder is marked as writable but is missing permissions
+ */
+ public function getInstallPath(): ?string {
+ foreach (\OC::$APPSROOTS as $dir) {
+ if (isset($dir['writable']) && $dir['writable'] === true) {
+ // Check if there is a writable install folder.
+ if (!is_writable($dir['path'])
+ || !is_readable($dir['path'])
+ ) {
+ throw new \RuntimeException(
+ 'Cannot write into "apps" directory. This can usually be fixed by giving the web server write access to the apps directory or disabling the App Store in the config file.'
+ );
+ }
+ return $dir['path'];
+ }
+ }
+ return null;
+ }
+
+ /**
* Downloads an app and puts it into the app directory
*
* @param string $appId
* @param bool [$allowUnstable]
*
+ * @throws AppNotFoundException If the app is not found on the appstore
* @throws \Exception If the installation was not successful
*/
public function downloadApp(string $appId, bool $allowUnstable = false): void {
$appId = strtolower($appId);
+ $installPath = $this->getInstallPath();
+ if ($installPath === null) {
+ throw new \Exception('No application directories are marked as writable.');
+ }
+
$apps = $this->appFetcher->get($allowUnstable);
foreach ($apps as $app) {
if ($app['id'] === $appId) {
@@ -241,6 +272,10 @@ class Installer {
// Download the release
$tempFile = $this->tempManager->getTemporaryFile('.tar.gz');
+ if ($tempFile === false) {
+ throw new \RuntimeException('Could not create temporary file for downloading app archive.');
+ }
+
$timeout = $this->isCLI ? 0 : 120;
$client = $this->clientService->newClient();
$client->get($app['releases'][0]['download'], ['sink' => $tempFile, 'timeout' => $timeout]);
@@ -252,8 +287,11 @@ class Installer {
if ($verified === true) {
// Seems to match, let's proceed
$extractDir = $this->tempManager->getTemporaryFolder();
- $archive = new TAR($tempFile);
+ if ($extractDir === false) {
+ throw new \RuntimeException('Could not create temporary directory for unpacking app.');
+ }
+ $archive = new TAR($tempFile);
if (!$archive->extract($extractDir)) {
$errorMessage = 'Could not extract app ' . $appId;
@@ -322,16 +360,19 @@ class Installer {
);
}
- $baseDir = OC_App::getInstallPath() . '/' . $appId;
+ $baseDir = $installPath . '/' . $appId;
// Remove old app with the ID if existent
- OC_Helper::rmdirr($baseDir);
+ Files::rmdirr($baseDir);
// Move to app folder
if (@mkdir($baseDir)) {
$extractDir .= '/' . $folders[0];
- OC_Helper::copyr($extractDir, $baseDir);
}
- OC_Helper::copyr($extractDir, $baseDir);
- OC_Helper::rmdirr($extractDir);
+ // otherwise we just copy the outer directory
+ $this->copyRecursive($extractDir, $baseDir);
+ Files::rmdirr($extractDir);
+ if (function_exists('opcache_reset')) {
+ opcache_reset();
+ }
return;
}
// Signature does not match
@@ -344,9 +385,9 @@ class Installer {
}
}
- throw new \Exception(
+ throw new AppNotFoundException(
sprintf(
- 'Could not download app %s',
+ 'Could not download app %s, it was not found on the appstore',
$appId
)
);
@@ -361,7 +402,7 @@ class Installer {
*/
public function isUpdateAvailable($appId, $allowUnstable = false): string|false {
if ($this->isInstanceReadyForUpdates === null) {
- $installPath = OC_App::getInstallPath();
+ $installPath = $this->getInstallPath();
if ($installPath === null) {
$this->isInstanceReadyForUpdates = false;
} else {
@@ -449,8 +490,14 @@ class Installer {
if (\OCP\Server::get(IAppManager::class)->isShipped($appId)) {
return false;
}
- $appDir = OC_App::getInstallPath() . '/' . $appId;
- OC_Helper::rmdirr($appDir);
+
+ $installPath = $this->getInstallPath();
+ if ($installPath === null) {
+ $this->logger->error('No application directories are marked as writable.', ['app' => 'core']);
+ return false;
+ }
+ $appDir = $installPath . '/' . $appId;
+ Files::rmdirr($appDir);
return true;
} else {
$this->logger->error('can\'t remove app ' . $appId . '. It is not installed.');
@@ -587,4 +634,33 @@ class Installer {
include $script;
}
}
+
+ /**
+ * Recursive copying of local folders.
+ *
+ * @param string $src source folder
+ * @param string $dest target folder
+ */
+ private function copyRecursive(string $src, string $dest): void {
+ if (!file_exists($src)) {
+ return;
+ }
+
+ if (is_dir($src)) {
+ if (!is_dir($dest)) {
+ mkdir($dest);
+ }
+ $files = scandir($src);
+ foreach ($files as $file) {
+ if ($file != '.' && $file != '..') {
+ $this->copyRecursive("$src/$file", "$dest/$file");
+ }
+ }
+ } else {
+ $validator = Server::get(FilenameValidator::class);
+ if (!$validator->isForbidden($src)) {
+ copy($src, $dest);
+ }
+ }
+ }
}