From 37562646696c3c7d485afb7017d078c462ce2c0a Mon Sep 17 00:00:00 2001 From: djmaze Date: Mon, 30 Aug 2021 14:01:42 +0200 Subject: [PATCH] Improvements for issue #136 --- dev/Settings/Admin/Packages.js | 14 +- dev/Stores/Admin/Package.js | 3 +- .../app/libraries/RainLoop/Actions/Admin.php | 233 +++++++----------- .../app/libraries/snappymail/http/request.php | 3 +- .../snappymail/http/request/curl.php | 3 + .../app/libraries/snappymail/repository.php | 64 +++++ .../Views/Admin/AdminSettingsPackages.html | 6 +- 7 files changed, 175 insertions(+), 151 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/snappymail/repository.php diff --git a/dev/Settings/Admin/Packages.js b/dev/Settings/Admin/Packages.js index 818cd788f..644146b55 100644 --- a/dev/Settings/Admin/Packages.js +++ b/dev/Settings/Admin/Packages.js @@ -57,13 +57,6 @@ export class PackagesAdminSettings { requestHelper(packageToRequest, install) { return (iError, data) => { - if (iError) { - this.packagesError( - getNotification(install ? Notification.CantInstallPackage : Notification.CantDeletePackage) -// ':\n' + getNotification(iError); - ); - } - PackageAdminStore.forEach(item => { if (item && packageToRequest && item.loading && item.loading() && packageToRequest.file === item.file) { packageToRequest.loading(false); @@ -71,7 +64,12 @@ export class PackagesAdminSettings { } }); - if (!iError && data.Result.Reload) { + if (iError) { + this.packagesError( + getNotification(install ? Notification.CantInstallPackage : Notification.CantDeletePackage) + + (data.ErrorMessage ? ':\n' + data.ErrorMessage : '') + ); + } else if (data.Result.Reload) { location.reload(); } else { PackageAdminStore.fetch(); diff --git a/dev/Stores/Admin/Package.js b/dev/Stores/Admin/Package.js index bc824527f..a68ef1587 100644 --- a/dev/Stores/Admin/Package.js +++ b/dev/Stores/Admin/Package.js @@ -8,7 +8,7 @@ PackageAdminStore.real = ko.observable(true); PackageAdminStore.loading = ko.observable(false); -//PackageAdminStore.error = ko.observable(''); +PackageAdminStore.error = ko.observable(''); PackageAdminStore.fetch = () => { PackageAdminStore.loading(true); @@ -18,6 +18,7 @@ PackageAdminStore.fetch = () => { PackageAdminStore.real(false); } else { PackageAdminStore.real(!!data.Result.Real); + PackageAdminStore.error(data.Result.Error); const loading = {}; PackageAdminStore.forEach(item => { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php index 019f15089..755192ce6 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php @@ -181,7 +181,7 @@ trait Admin $totp = $this->Config()->Get('security', 'admin_totp', ''); - if (0 === strlen($sLogin) || 0 === strlen($sPassword) || + if (!\strlen($sLogin) || !\strlen($sPassword) || !$this->Config()->Get('security', 'allow_admin_panel', true) || $sLogin !== $this->Config()->Get('security', 'admin_login', '') || !$this->Config()->ValidatePassword($sPassword) @@ -245,7 +245,7 @@ trait Admin $this->Logger()->AddSecret($sPassword); $sNewPassword = $this->GetActionParam('NewPassword', ''); - if (0 < \strlen(\trim($sNewPassword))) + if (\strlen(\trim($sNewPassword))) { $this->Logger()->AddSecret($sNewPassword); } @@ -254,12 +254,12 @@ trait Admin if ($oConfig->ValidatePassword($sPassword)) { - if (0 < \strlen($sLogin)) + if (\strlen($sLogin)) { $oConfig->Set('security', 'admin_login', $sLogin); } - if (0 < \strlen(\trim($sNewPassword))) + if (\strlen(\trim($sNewPassword))) { $oConfig->SetPassword($sNewPassword); if (\is_file($passfile) && \trim(\file_get_contents($passfile)) !== $sNewPassword) { @@ -485,16 +485,7 @@ trait Admin return 'https://snappymail.eu/repository/v2/'; } - private function rainLoopUpdatable() : bool - { - return \file_exists(APP_INDEX_ROOT_PATH.'index.php') && - \is_writable(APP_INDEX_ROOT_PATH.'index.php') && - \is_writable(APP_INDEX_ROOT_PATH.'snappymail/') && - APP_VERSION !== APP_DEV_VERSION - ; - } - - private function getRepositoryDataByUrl(string $sRepo, bool &$bReal = false) : array + private function getRepositoryDataByUrl(bool &$bReal = false) : array { $bReal = false; $aRep = null; @@ -503,9 +494,7 @@ trait Admin $sRepoFile = 'packages.json'; $iRepTime = 0; - $oHttp = \MailSo\Base\Http::SingletonInstance(); - - $sCacheKey = KeyPathHelper::RepositoryCacheFile($sRepo, $sRepoFile); + $sCacheKey = KeyPathHelper::RepositoryCacheFile(\SnappyMail\Repository::BASE_URL, $sRepoFile); $sRep = $this->Cacher()->Get($sCacheKey); if ('' !== $sRep) { @@ -514,14 +503,11 @@ trait Admin if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) { - $iCode = 0; - $sContentType = ''; - - $sRepPath = $sRepo.$sRepoFile; - $sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepPath, 'SnappyMail', $sContentType, $iCode, $this->Logger(), 10, - $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false; - - if (false !== $sRep) + $sRep = \SnappyMail\Repository::get($sRepoFile, + $this->Config()->Get('labs', 'curl_proxy', ''), + $this->Config()->Get('labs', 'curl_proxy_auth', '') + ); + if ($sRep) { $aRep = \json_decode($sRep); $bReal = \is_array($aRep) && 0 < \count($aRep); @@ -534,7 +520,7 @@ trait Admin } else { - $this->Logger()->Write('Cannot read remote repository file: '.$sRepPath, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + throw new \Exception('Cannot read remote repository file: '.$sRepoFile); } } else if ('' !== $sRep) @@ -546,39 +532,42 @@ trait Admin return \is_array($aRep) ? $aRep : []; } - private function getRepositoryData(bool &$bReal, bool &$bRainLoopUpdatable) : array + private function getRepositoryData(bool &$bReal, string &$sError) : array { - $bRainLoopUpdatable = $this->rainLoopUpdatable(); - $aResult = array(); - foreach ($this->getRepositoryDataByUrl($this->snappyMailRepo(), $bReal) as $oItem) { - if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, - $oItem->version, $oItem->release, $oItem->file, $oItem->description)) - { - if (!empty($oItem->required) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->required, '<')) { - continue; - } + try { + foreach ($this->getRepositoryDataByUrl($bReal) as $oItem) { + if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, + $oItem->version, $oItem->release, $oItem->file, $oItem->description)) + { + if (!empty($oItem->required) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->required, '<')) { + continue; + } - if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) { - continue; - } + if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) { + continue; + } - if ('plugin' === $oItem->type) { - $aResult[$oItem->id] = array( - 'type' => $oItem->type, - 'id' => $oItem->id, - 'name' => $oItem->name, - 'installed' => '', - 'enabled' => true, - 'version' => $oItem->version, - 'file' => $oItem->file, - 'release' => $oItem->release, - 'desc' => $oItem->description, - 'canBeDeleted' => false, - 'canBeUpdated' => true - ); + if ('plugin' === $oItem->type) { + $aResult[$oItem->id] = array( + 'type' => $oItem->type, + 'id' => $oItem->id, + 'name' => $oItem->name, + 'installed' => '', + 'enabled' => true, + 'version' => $oItem->version, + 'file' => $oItem->file, + 'release' => $oItem->release, + 'desc' => $oItem->description, + 'canBeDeleted' => false, + 'canBeUpdated' => true + ); + } } } + } catch (\Throwable $e) { + $sError = "{$e->getCode()} {$e->getMessage()}"; + $this->Logger()->Write($sError, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } $aEnabledPlugins = \array_map('trim', @@ -619,15 +608,21 @@ trait Admin $this->IsAdminLoggined(); $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); + $sError = ''; + $aList = $this->getRepositoryData($bReal, $sError); + + $bRainLoopUpdatable = \file_exists(APP_INDEX_ROOT_PATH.'index.php') + && \is_writable(APP_INDEX_ROOT_PATH.'index.php') + && \is_writable(APP_INDEX_ROOT_PATH.'snappymail/') + && APP_VERSION !== APP_DEV_VERSION; // \uksort($aList, function($a, $b){return \strcasecmp($a['name'], $b['name']);}); return $this->DefaultResponse(__FUNCTION__, array( 'Real' => $bReal, 'MainUpdatable' => $bRainLoopUpdatable, - 'List' => $aList + 'List' => $aList, + 'Error' => $sError )); } @@ -637,24 +632,8 @@ trait Admin $sId = $this->GetActionParam('Id', ''); - $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); - - $sResultId = ''; - foreach ($aList as $oItem) - { - if ($oItem && 'plugin' === $oItem['type'] && $sId === $oItem['id']) - { - $sResultId = $sId; - break; - } - } - - $bResult = '' !== $sResultId && static::deletePackageDir($sResultId); - if ($bResult) { - $this->pluginEnable($sResultId, false); - } + $bResult = static::deletePackageDir($sId); + $this->pluginEnable($sId, false); return $this->DefaultResponse(__FUNCTION__, $bResult); } @@ -666,38 +645,6 @@ trait Admin && (!\is_file("{$sPath}.phar") || \unlink("{$sPath}.phar")); } - private function downloadRemotePackageByUrl(string $sUrl) : string - { - $bResult = false; - $sTmp = APP_PRIVATE_DATA.\md5(\microtime(true).$sUrl) . \substr($sUrl, -4); - $pDest = \fopen($sTmp, 'w+b'); - if ($pDest) - { - $iCode = 0; - $sContentType = ''; - - \set_time_limit(120); - - $oHttp = \MailSo\Base\Http::SingletonInstance(); - $bResult = $oHttp->SaveUrlToFile($sUrl, $pDest, $sTmp, $sContentType, $iCode, $this->Logger(), 60, - $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')); - - if (!$bResult) - { - $this->Logger()->Write('Cannot save url to temp file: ', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - $this->Logger()->Write($sUrl.' -> '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - - \fclose($pDest); - } - else - { - $this->Logger()->Write('Cannot create temp file: '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - - return $bResult ? $sTmp : ''; - } - public function DoAdminPackageInstall() : array { $this->IsAdminLoggined(); @@ -710,40 +657,48 @@ trait Admin $sRealFile = ''; - $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); - - if ('plugin' === $sType) - { - foreach ($aList as $oItem) - { - if ($oItem && $sFile === $oItem['file'] && $sId === $oItem['id']) - { - $sRealFile = $sFile; - break; - } - } - } - $bResult = false; - $sTmp = $sRealFile ? $this->downloadRemotePackageByUrl($this->snappyMailRepo().$sRealFile) : null; - if ($sTmp) - { - $oArchive = new \PharData($sTmp, 0, $sRealFile); - if (static::deletePackageDir($sId)) { - if ('.phar' === \substr($sRealFile, -5)) { - $bResult = \copy($sTmp, APP_PLUGINS_PATH . \basename($sRealFile)); - } else { - $bResult = $oArchive->extractTo(APP_PLUGINS_PATH); + $sTmp = null; + try { + if ('plugin' === $sType) { + $bReal = false; + $sError = ''; + $aList = $this->getRepositoryData($bReal, $sError); + if ($sError) { + throw new \Exception($sError); } - if (!$bResult) { - $this->Logger()->Write('Cannot extract package files: '.$oArchive->getStatusString(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + foreach ($aList as $oItem) { + if ($oItem && $sFile === $oItem['file'] && $sId === $oItem['id']) { + $sRealFile = $sFile; + $sTmp = \SnappyMail\Repository::download($sFile, + $this->Config()->Get('labs', 'curl_proxy', ''), + $this->Config()->Get('labs', 'curl_proxy_auth', '') + ); + break; + } } - } else { - $this->Logger()->Write('Cannot remove previous plugin folder: '.$sId, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); } - \unlink($sTmp); + + if ($sTmp) { + $oArchive = new \PharData($sTmp, 0, $sRealFile); + if (static::deletePackageDir($sId)) { + if ('.phar' === \substr($sRealFile, -5)) { + $bResult = \copy($sTmp, APP_PLUGINS_PATH . \basename($sRealFile)); + } else { + $bResult = $oArchive->extractTo(\rtrim(APP_PLUGINS_PATH, '\\/')); + } + if (!$bResult) { + throw new \Exception('Cannot extract package files: '.$oArchive->getStatusString()); + } + } else { + throw new \Exception('Cannot remove previous plugin folder: '.$sId); + } + } + } catch (\Throwable $e) { + $this->Logger()->Write("Install package {$sRealFile} failed: {$e->getMessage()}", \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + throw $e; + } finally { + $sTmp && \unlink($sTmp); } return $this->DefaultResponse(__FUNCTION__, $bResult ? @@ -752,7 +707,7 @@ trait Admin private function pluginEnable(string $sName, bool $bEnable = true) : bool { - if (0 === \strlen($sName)) + if (!\strlen($sName)) { return false; } @@ -773,7 +728,7 @@ trait Admin { foreach ($aEnabledPlugins as $sPlugin) { - if ($sName !== $sPlugin && 0 < \strlen($sPlugin)) + if ($sName !== $sPlugin && \strlen($sPlugin)) { $aNewEnabledPlugins[] = $sPlugin; } @@ -798,7 +753,7 @@ trait Admin if ($oPlugin) { $sValue = $oPlugin->Supported(); - if (0 < \strlen($sValue)) + if (\strlen($sValue)) { return $this->FalseResponse(__FUNCTION__, Notifications::UnsupportedPluginPackage, $sValue); } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php index 1ac098bf9..aa672b992 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php @@ -17,7 +17,8 @@ abstract class Request $user_agent, $max_redirects = 0, $verify_peer = false, - $proxy = null; + $proxy = null, + $proxy_auth = null; protected $auth = [ diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php index cbcfd8f14..a389a1a9f 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request/curl.php @@ -63,6 +63,9 @@ class CURL extends \SnappyMail\HTTP\Request } if ($this->proxy) { \curl_setopt($c, CURLOPT_PROXY, $this->proxy); + if ($this->proxy_auth) { + \curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->proxy_auth); + } } if ('HEAD' === $method) { \curl_setopt($c, CURLOPT_NOBODY, true); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/repository.php b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php new file mode 100644 index 000000000..2f3d4bdf4 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/repository.php @@ -0,0 +1,64 @@ +proxy = $proxy; + $oHTTP->proxy_auth = $proxy_auth; + $oHTTP->max_response_kb = 0; + $oHTTP->timeout = 15; // timeout in seconds. + $oResponse = $oHTTP->doRequest('GET', static::BASE_URL . $path); + if (!$oResponse) { + throw new \Exception('No HTTP response from repository'); + } + if (200 !== $oResponse->status) { + throw new \Exception(static::body2plain($oResponse->body), $oResponse->status); + } + return $oResponse->body; + } + +// $aRep = \json_decode($sRep); + + public static function download(string $path, string $proxy, string $proxy_auth) : string + { + $sTmp = APP_PRIVATE_DATA . \md5(\microtime(true).$path) . \preg_replace('/^.*?(\\.[a-z\\.]+)$/Di', '$1', $path); + $pDest = \fopen($sTmp, 'w+b'); + if (!$pDest) { + throw new \Exception('Cannot create temp file: '.$sTmp); + } + $oHTTP = HTTP\Request::factory(/*'socket' or 'curl'*/); + $oHTTP->proxy = $proxy; + $oHTTP->proxy_auth = $proxy_auth; + $oHTTP->max_response_kb = 0; + $oHTTP->timeout = 15; // timeout in seconds. + $oHTTP->streamBodyTo($pDest); + \set_time_limit(120); + $oResponse = $oHTTP->doRequest('GET', static::BASE_URL . $path); + \fclose($pDest); + if (!$oResponse) { + \unlink($sTmp); + throw new \Exception('No HTTP response from repository'); + } + if (200 !== $oResponse->status) { + $body = \file_get_contents($sTmp); + \unlink($sTmp); + throw new \Exception(static::body2plain($body), $oResponse->status); + } + return $sTmp; + } + + private static function body2plain(string $body) : string + { + return \trim(\strip_tags( + \preg_match('@]*>(.*) -
+
+
+ +