Improvements for issue #136

This commit is contained in:
djmaze 2021-08-30 14:01:42 +02:00
parent 9cf83eb62a
commit 3756264669
7 changed files with 175 additions and 151 deletions

View file

@ -57,13 +57,6 @@ export class PackagesAdminSettings {
requestHelper(packageToRequest, install) { requestHelper(packageToRequest, install) {
return (iError, data) => { return (iError, data) => {
if (iError) {
this.packagesError(
getNotification(install ? Notification.CantInstallPackage : Notification.CantDeletePackage)
// ':\n' + getNotification(iError);
);
}
PackageAdminStore.forEach(item => { PackageAdminStore.forEach(item => {
if (item && packageToRequest && item.loading && item.loading() && packageToRequest.file === item.file) { if (item && packageToRequest && item.loading && item.loading() && packageToRequest.file === item.file) {
packageToRequest.loading(false); 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(); location.reload();
} else { } else {
PackageAdminStore.fetch(); PackageAdminStore.fetch();

View file

@ -8,7 +8,7 @@ PackageAdminStore.real = ko.observable(true);
PackageAdminStore.loading = ko.observable(false); PackageAdminStore.loading = ko.observable(false);
//PackageAdminStore.error = ko.observable(''); PackageAdminStore.error = ko.observable('');
PackageAdminStore.fetch = () => { PackageAdminStore.fetch = () => {
PackageAdminStore.loading(true); PackageAdminStore.loading(true);
@ -18,6 +18,7 @@ PackageAdminStore.fetch = () => {
PackageAdminStore.real(false); PackageAdminStore.real(false);
} else { } else {
PackageAdminStore.real(!!data.Result.Real); PackageAdminStore.real(!!data.Result.Real);
PackageAdminStore.error(data.Result.Error);
const loading = {}; const loading = {};
PackageAdminStore.forEach(item => { PackageAdminStore.forEach(item => {

View file

@ -181,7 +181,7 @@ trait Admin
$totp = $this->Config()->Get('security', 'admin_totp', ''); $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) || !$this->Config()->Get('security', 'allow_admin_panel', true) ||
$sLogin !== $this->Config()->Get('security', 'admin_login', '') || $sLogin !== $this->Config()->Get('security', 'admin_login', '') ||
!$this->Config()->ValidatePassword($sPassword) !$this->Config()->ValidatePassword($sPassword)
@ -245,7 +245,7 @@ trait Admin
$this->Logger()->AddSecret($sPassword); $this->Logger()->AddSecret($sPassword);
$sNewPassword = $this->GetActionParam('NewPassword', ''); $sNewPassword = $this->GetActionParam('NewPassword', '');
if (0 < \strlen(\trim($sNewPassword))) if (\strlen(\trim($sNewPassword)))
{ {
$this->Logger()->AddSecret($sNewPassword); $this->Logger()->AddSecret($sNewPassword);
} }
@ -254,12 +254,12 @@ trait Admin
if ($oConfig->ValidatePassword($sPassword)) if ($oConfig->ValidatePassword($sPassword))
{ {
if (0 < \strlen($sLogin)) if (\strlen($sLogin))
{ {
$oConfig->Set('security', 'admin_login', $sLogin); $oConfig->Set('security', 'admin_login', $sLogin);
} }
if (0 < \strlen(\trim($sNewPassword))) if (\strlen(\trim($sNewPassword)))
{ {
$oConfig->SetPassword($sNewPassword); $oConfig->SetPassword($sNewPassword);
if (\is_file($passfile) && \trim(\file_get_contents($passfile)) !== $sNewPassword) { if (\is_file($passfile) && \trim(\file_get_contents($passfile)) !== $sNewPassword) {
@ -485,16 +485,7 @@ trait Admin
return 'https://snappymail.eu/repository/v2/'; return 'https://snappymail.eu/repository/v2/';
} }
private function rainLoopUpdatable() : bool private function getRepositoryDataByUrl(bool &$bReal = false) : array
{
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
{ {
$bReal = false; $bReal = false;
$aRep = null; $aRep = null;
@ -503,9 +494,7 @@ trait Admin
$sRepoFile = 'packages.json'; $sRepoFile = 'packages.json';
$iRepTime = 0; $iRepTime = 0;
$oHttp = \MailSo\Base\Http::SingletonInstance(); $sCacheKey = KeyPathHelper::RepositoryCacheFile(\SnappyMail\Repository::BASE_URL, $sRepoFile);
$sCacheKey = KeyPathHelper::RepositoryCacheFile($sRepo, $sRepoFile);
$sRep = $this->Cacher()->Get($sCacheKey); $sRep = $this->Cacher()->Get($sCacheKey);
if ('' !== $sRep) if ('' !== $sRep)
{ {
@ -514,14 +503,11 @@ trait Admin
if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime)
{ {
$iCode = 0; $sRep = \SnappyMail\Repository::get($sRepoFile,
$sContentType = ''; $this->Config()->Get('labs', 'curl_proxy', ''),
$this->Config()->Get('labs', 'curl_proxy_auth', '')
$sRepPath = $sRepo.$sRepoFile; );
$sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepPath, 'SnappyMail', $sContentType, $iCode, $this->Logger(), 10, if ($sRep)
$this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false;
if (false !== $sRep)
{ {
$aRep = \json_decode($sRep); $aRep = \json_decode($sRep);
$bReal = \is_array($aRep) && 0 < \count($aRep); $bReal = \is_array($aRep) && 0 < \count($aRep);
@ -534,7 +520,7 @@ trait Admin
} }
else 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) else if ('' !== $sRep)
@ -546,39 +532,42 @@ trait Admin
return \is_array($aRep) ? $aRep : []; 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(); $aResult = array();
foreach ($this->getRepositoryDataByUrl($this->snappyMailRepo(), $bReal) as $oItem) { try {
if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, foreach ($this->getRepositoryDataByUrl($bReal) as $oItem) {
$oItem->version, $oItem->release, $oItem->file, $oItem->description)) 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->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, '>=')) { if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) {
continue; continue;
} }
if ('plugin' === $oItem->type) { if ('plugin' === $oItem->type) {
$aResult[$oItem->id] = array( $aResult[$oItem->id] = array(
'type' => $oItem->type, 'type' => $oItem->type,
'id' => $oItem->id, 'id' => $oItem->id,
'name' => $oItem->name, 'name' => $oItem->name,
'installed' => '', 'installed' => '',
'enabled' => true, 'enabled' => true,
'version' => $oItem->version, 'version' => $oItem->version,
'file' => $oItem->file, 'file' => $oItem->file,
'release' => $oItem->release, 'release' => $oItem->release,
'desc' => $oItem->description, 'desc' => $oItem->description,
'canBeDeleted' => false, 'canBeDeleted' => false,
'canBeUpdated' => true 'canBeUpdated' => true
); );
}
} }
} }
} catch (\Throwable $e) {
$sError = "{$e->getCode()} {$e->getMessage()}";
$this->Logger()->Write($sError, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER');
} }
$aEnabledPlugins = \array_map('trim', $aEnabledPlugins = \array_map('trim',
@ -619,15 +608,21 @@ trait Admin
$this->IsAdminLoggined(); $this->IsAdminLoggined();
$bReal = false; $bReal = false;
$bRainLoopUpdatable = false; $sError = '';
$aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); $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']);}); // \uksort($aList, function($a, $b){return \strcasecmp($a['name'], $b['name']);});
return $this->DefaultResponse(__FUNCTION__, array( return $this->DefaultResponse(__FUNCTION__, array(
'Real' => $bReal, 'Real' => $bReal,
'MainUpdatable' => $bRainLoopUpdatable, 'MainUpdatable' => $bRainLoopUpdatable,
'List' => $aList 'List' => $aList,
'Error' => $sError
)); ));
} }
@ -637,24 +632,8 @@ trait Admin
$sId = $this->GetActionParam('Id', ''); $sId = $this->GetActionParam('Id', '');
$bReal = false; $bResult = static::deletePackageDir($sId);
$bRainLoopUpdatable = false; $this->pluginEnable($sId, 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);
}
return $this->DefaultResponse(__FUNCTION__, $bResult); return $this->DefaultResponse(__FUNCTION__, $bResult);
} }
@ -666,38 +645,6 @@ trait Admin
&& (!\is_file("{$sPath}.phar") || \unlink("{$sPath}.phar")); && (!\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 public function DoAdminPackageInstall() : array
{ {
$this->IsAdminLoggined(); $this->IsAdminLoggined();
@ -710,40 +657,48 @@ trait Admin
$sRealFile = ''; $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; $bResult = false;
$sTmp = $sRealFile ? $this->downloadRemotePackageByUrl($this->snappyMailRepo().$sRealFile) : null; $sTmp = null;
if ($sTmp) try {
{ if ('plugin' === $sType) {
$oArchive = new \PharData($sTmp, 0, $sRealFile); $bReal = false;
if (static::deletePackageDir($sId)) { $sError = '';
if ('.phar' === \substr($sRealFile, -5)) { $aList = $this->getRepositoryData($bReal, $sError);
$bResult = \copy($sTmp, APP_PLUGINS_PATH . \basename($sRealFile)); if ($sError) {
} else { throw new \Exception($sError);
$bResult = $oArchive->extractTo(APP_PLUGINS_PATH);
} }
if (!$bResult) { foreach ($aList as $oItem) {
$this->Logger()->Write('Cannot extract package files: '.$oArchive->getStatusString(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); 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 ? return $this->DefaultResponse(__FUNCTION__, $bResult ?
@ -752,7 +707,7 @@ trait Admin
private function pluginEnable(string $sName, bool $bEnable = true) : bool private function pluginEnable(string $sName, bool $bEnable = true) : bool
{ {
if (0 === \strlen($sName)) if (!\strlen($sName))
{ {
return false; return false;
} }
@ -773,7 +728,7 @@ trait Admin
{ {
foreach ($aEnabledPlugins as $sPlugin) foreach ($aEnabledPlugins as $sPlugin)
{ {
if ($sName !== $sPlugin && 0 < \strlen($sPlugin)) if ($sName !== $sPlugin && \strlen($sPlugin))
{ {
$aNewEnabledPlugins[] = $sPlugin; $aNewEnabledPlugins[] = $sPlugin;
} }
@ -798,7 +753,7 @@ trait Admin
if ($oPlugin) if ($oPlugin)
{ {
$sValue = $oPlugin->Supported(); $sValue = $oPlugin->Supported();
if (0 < \strlen($sValue)) if (\strlen($sValue))
{ {
return $this->FalseResponse(__FUNCTION__, Notifications::UnsupportedPluginPackage, $sValue); return $this->FalseResponse(__FUNCTION__, Notifications::UnsupportedPluginPackage, $sValue);
} }

View file

@ -17,7 +17,8 @@ abstract class Request
$user_agent, $user_agent,
$max_redirects = 0, $max_redirects = 0,
$verify_peer = false, $verify_peer = false,
$proxy = null; $proxy = null,
$proxy_auth = null;
protected protected
$auth = [ $auth = [

View file

@ -63,6 +63,9 @@ class CURL extends \SnappyMail\HTTP\Request
} }
if ($this->proxy) { if ($this->proxy) {
\curl_setopt($c, CURLOPT_PROXY, $this->proxy); \curl_setopt($c, CURLOPT_PROXY, $this->proxy);
if ($this->proxy_auth) {
\curl_setopt($c, CURLOPT_PROXYUSERPWD, $this->proxy_auth);
}
} }
if ('HEAD' === $method) { if ('HEAD' === $method) {
\curl_setopt($c, CURLOPT_NOBODY, true); \curl_setopt($c, CURLOPT_NOBODY, true);

View file

@ -0,0 +1,64 @@
<?php
namespace SnappyMail;
abstract class Repository
{
// snappyMailRepo
const BASE_URL = 'https://snappymail.eu/repository/v2/';
public static function get(string $path, string $proxy, string $proxy_auth) : string
{
$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.
$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('@<body[^>]*>(.*)</body@si', $body, $match) ? $match[1] : $body
));
}
}

View file

@ -5,8 +5,10 @@
<i class="icon-spinner" style="margin: 5px" data-bind="visible: packages.loading"></i> <i class="icon-spinner" style="margin: 5px" data-bind="visible: packages.loading"></i>
</div> </div>
<div class="alert" style="margin-top: 10px;" data-bind="visible: !packages.real() && !packages.loading()" <div class="alert" style="margin-top: 10px;" data-bind="visible: !packages.real() && !packages.loading()">
data-i18n="TAB_PACKAGES/ALERT_CANNOT_ACCESS_REPOSITORY"></div> <div data-i18n="TAB_PACKAGES/ALERT_CANNOT_ACCESS_REPOSITORY"></div>
<!-- ko text: packages.error() --><!-- /ko -->
</div>
<div class="alert" style="margin-top: 10px;" data-bind="visible: '' !== packagesError()"> <div class="alert" style="margin-top: 10px;" data-bind="visible: '' !== packagesError()">
<button type="button" class="close" data-bind="click: function () { packagesError('') }">×</button> <button type="button" class="close" data-bind="click: function () { packagesError('') }">×</button>