diff options
Diffstat (limited to 'lib/private/Updater')
-rw-r--r-- | lib/private/Updater/Changes.php | 46 | ||||
-rw-r--r-- | lib/private/Updater/ChangesCheck.php | 48 | ||||
-rw-r--r-- | lib/private/Updater/ChangesMapper.php | 32 | ||||
-rw-r--r-- | lib/private/Updater/ChangesResult.php | 63 | ||||
-rw-r--r-- | lib/private/Updater/Exceptions/ReleaseMetadataException.php | 17 | ||||
-rw-r--r-- | lib/private/Updater/ReleaseMetadata.php | 79 | ||||
-rw-r--r-- | lib/private/Updater/VersionCheck.php | 125 |
7 files changed, 235 insertions, 175 deletions
diff --git a/lib/private/Updater/Changes.php b/lib/private/Updater/Changes.php new file mode 100644 index 00000000000..c941dfb3fa5 --- /dev/null +++ b/lib/private/Updater/Changes.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Updater; + +use OCP\AppFramework\Db\Entity; +use OCP\DB\Types; + +/** + * Class Changes + * + * @package OC\Updater + * @method string getVersion()=1 + * @method void setVersion(string $version) + * @method string getEtag() + * @method void setEtag(string $etag) + * @method int getLastCheck() + * @method void setLastCheck(int $lastCheck) + * @method string getData() + * @method void setData(string $data) + */ +class Changes extends Entity { + /** @var string */ + protected $version = ''; + + /** @var string */ + protected $etag = ''; + + /** @var int */ + protected $lastCheck = 0; + + /** @var string */ + protected $data = ''; + + public function __construct() { + $this->addType('version', 'string'); + $this->addType('etag', 'string'); + $this->addType('lastCheck', Types::INTEGER); + $this->addType('data', 'string'); + } +} diff --git a/lib/private/Updater/ChangesCheck.php b/lib/private/Updater/ChangesCheck.php index 62b1e04aae8..e88969f62a8 100644 --- a/lib/private/Updater/ChangesCheck.php +++ b/lib/private/Updater/ChangesCheck.php @@ -3,49 +3,28 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OC\Updater; use OCP\AppFramework\Db\DoesNotExistException; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; -use OCP\ILogger; +use Psr\Log\LoggerInterface; class ChangesCheck { /** @var IClientService */ protected $clientService; /** @var ChangesMapper */ private $mapper; - /** @var ILogger */ - private $logger; + private LoggerInterface $logger; public const RESPONSE_NO_CONTENT = 0; public const RESPONSE_USE_CACHE = 1; public const RESPONSE_HAS_CONTENT = 2; - public function __construct(IClientService $clientService, ChangesMapper $mapper, ILogger $logger) { + public function __construct(IClientService $clientService, ChangesMapper $mapper, LoggerInterface $logger) { $this->clientService = $clientService; $this->mapper = $mapper; $this->logger = $logger; @@ -53,6 +32,7 @@ class ChangesCheck { /** * @throws DoesNotExistException + * @return array{changelogURL: string, whatsNew: array<string, array{admin: list<string>, regular: list<string>}>} */ public function getChangesForVersion(string $version): array { $version = $this->normalizeVersion($version); @@ -75,7 +55,7 @@ class ChangesCheck { return json_decode($changesInfo->getData(), true); } } catch (DoesNotExistException $e) { - $changesInfo = new ChangesResult(); + $changesInfo = new Changes(); } $response = $this->queryChangesServer($uri, $changesInfo); @@ -111,7 +91,7 @@ class ChangesCheck { return self::RESPONSE_NO_CONTENT; } - protected function cacheResult(ChangesResult $entry, string $version) { + protected function cacheResult(Changes $entry, string $version) { if ($entry->getVersion() === $version) { $this->mapper->update($entry); } else { @@ -123,7 +103,7 @@ class ChangesCheck { /** * @throws \Exception */ - protected function queryChangesServer(string $uri, ChangesResult $entry): IResponse { + protected function queryChangesServer(string $uri, Changes $entry): IResponse { $headers = []; if ($entry->getEtag() !== '') { $headers['If-None-Match'] = [$entry->getEtag()]; @@ -139,9 +119,13 @@ class ChangesCheck { protected function extractData($body):array { $data = []; if ($body) { - $loadEntities = libxml_disable_entity_loader(true); - $xml = @simplexml_load_string($body); - libxml_disable_entity_loader($loadEntities); + if (\LIBXML_VERSION < 20900) { + $loadEntities = libxml_disable_entity_loader(true); + $xml = @simplexml_load_string($body); + libxml_disable_entity_loader($loadEntities); + } else { + $xml = @simplexml_load_string($body); + } if ($xml !== false) { $data['changelogURL'] = (string)$xml->changelog['href']; $data['whatsNew'] = []; diff --git a/lib/private/Updater/ChangesMapper.php b/lib/private/Updater/ChangesMapper.php index 83a695b571f..c399948ff10 100644 --- a/lib/private/Updater/ChangesMapper.php +++ b/lib/private/Updater/ChangesMapper.php @@ -3,28 +3,9 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OC\Updater; use OCP\AppFramework\Db\DoesNotExistException; @@ -32,6 +13,9 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +/** + * @template-extends QBMapper<Changes> + */ class ChangesMapper extends QBMapper { public const TABLE_NAME = 'whats_new'; @@ -42,19 +26,19 @@ class ChangesMapper extends QBMapper { /** * @throws DoesNotExistException */ - public function getChanges(string $version): ChangesResult { + public function getChanges(string $version): Changes { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $result = $qb->select('*') ->from(self::TABLE_NAME) ->where($qb->expr()->eq('version', $qb->createNamedParameter($version))) - ->execute(); + ->executeQuery(); $data = $result->fetch(); $result->closeCursor(); if ($data === false) { throw new DoesNotExistException('Changes info is not present'); } - return ChangesResult::fromRow($data); + return Changes::fromRow($data); } } diff --git a/lib/private/Updater/ChangesResult.php b/lib/private/Updater/ChangesResult.php deleted file mode 100644 index dfd3aaa1cd8..00000000000 --- a/lib/private/Updater/ChangesResult.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -namespace OC\Updater; - -use OCP\AppFramework\Db\Entity; - -/** - * Class ChangesResult - * - * @package OC\Updater - * @method string getVersion()=1 - * @method void setVersion(string $version) - * @method string getEtag() - * @method void setEtag(string $etag) - * @method int getLastCheck() - * @method void setLastCheck(int $lastCheck) - * @method string getData() - * @method void setData(string $data) - */ -class ChangesResult extends Entity { - /** @var string */ - protected $version = ''; - - /** @var string */ - protected $etag = ''; - - /** @var int */ - protected $lastCheck = 0; - - /** @var string */ - protected $data = ''; - - public function __construct() { - $this->addType('version', 'string'); - $this->addType('etag', 'string'); - $this->addType('lastCheck', 'int'); - $this->addType('data', 'string'); - } -} diff --git a/lib/private/Updater/Exceptions/ReleaseMetadataException.php b/lib/private/Updater/Exceptions/ReleaseMetadataException.php new file mode 100644 index 00000000000..bc82e4e03df --- /dev/null +++ b/lib/private/Updater/Exceptions/ReleaseMetadataException.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Updater\Exceptions; + +use Exception; + +/** + * @since 30.0.0 + */ +class ReleaseMetadataException extends Exception { +} diff --git a/lib/private/Updater/ReleaseMetadata.php b/lib/private/Updater/ReleaseMetadata.php new file mode 100644 index 00000000000..665847037e7 --- /dev/null +++ b/lib/private/Updater/ReleaseMetadata.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Updater; + +use Exception; +use JsonException; +use OC\Updater\Exceptions\ReleaseMetadataException; +use OCP\Http\Client\IClientService; + +/** retrieve releases metadata from official servers + * + * @since 30.0.0 + */ +class ReleaseMetadata { + public function __construct( + private readonly IClientService $clientService, + ) { + } + + /** + * returns metadata based on release version + * + * - version is a stable release, metadata is downloaded from official releases folder + * - version is not a table release, metadata is downloaded from official prereleases folder + * - version is a major version (30, 31, 32, ...), latest metadata are downloaded + * + * @param string $version + * + * @return array + * @throws ReleaseMetadataException + * @since 30.0.0 + */ + public function getMetadata(string $version): array { + if (!str_contains($version, '.')) { + $url = 'https://download.nextcloud.com/server/releases/latest-' . $version . '.metadata'; + } else { + [,,$minor] = explode('.', $version); + if (ctype_digit($minor)) { + $url = 'https://download.nextcloud.com/server/releases/nextcloud-' . $version . '.metadata'; + } else { + $url = 'https://download.nextcloud.com/server/prereleases/nextcloud-' . $version . '.metadata'; + } + } + return $this->downloadMetadata($url); + } + + /** + * download Metadata from a link + * + * @param string $url + * + * @return array + * @throws ReleaseMetadataException + * @since 30.0.0 + */ + public function downloadMetadata(string $url): array { + $client = $this->clientService->newClient(); + try { + $response = $client->get($url, [ + 'timeout' => 10, + 'connect_timeout' => 10 + ]); + } catch (Exception $e) { + throw new ReleaseMetadataException('could not reach metadata at ' . $url, previous: $e); + } + + try { + return json_decode($response->getBody(), true, flags: JSON_THROW_ON_ERROR); + } catch (JsonException) { + throw new ReleaseMetadataException('remote document is not valid'); + } + } +} diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php index 7b1f1344e66..be410b06c3e 100644 --- a/lib/private/Updater/VersionCheck.php +++ b/lib/private/Updater/VersionCheck.php @@ -1,52 +1,31 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ - namespace OC\Updater; use OCP\Http\Client\IClientService; +use OCP\IAppConfig; use OCP\IConfig; +use OCP\IUserManager; +use OCP\ServerVersion; +use OCP\Support\Subscription\IRegistry; use OCP\Util; +use Psr\Log\LoggerInterface; class VersionCheck { - - /** @var IClientService */ - private $clientService; - - /** @var IConfig */ - private $config; - - /** - * @param IClientService $clientService - * @param IConfig $config - */ - public function __construct(IClientService $clientService, - IConfig $config) { - $this->clientService = $clientService; - $this->config = $config; + public function __construct( + private ServerVersion $serverVersion, + private IClientService $clientService, + private IConfig $config, + private IAppConfig $appConfig, + private IUserManager $userManager, + private IRegistry $registry, + private LoggerInterface $logger, + ) { } @@ -62,27 +41,29 @@ class VersionCheck { } // Look up the cache - it is invalidated all 30 minutes - if (((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) { + if (($this->appConfig->getValueInt('core', 'lastupdatedat') + 1800) > time()) { return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true); } - $updaterUrl = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/updater_server/'); + $updaterUrl = $this->config->getSystemValueString('updater.server.url', 'https://updates.nextcloud.com/updater_server/'); - $this->config->setAppValue('core', 'lastupdatedat', time()); + $this->appConfig->setValueInt('core', 'lastupdatedat', time()); if ($this->config->getAppValue('core', 'installedat', '') === '') { - $this->config->setAppValue('core', 'installedat', microtime(true)); + $this->config->setAppValue('core', 'installedat', (string)microtime(true)); } $version = Util::getVersion(); $version['installed'] = $this->config->getAppValue('core', 'installedat'); - $version['updated'] = $this->config->getAppValue('core', 'lastupdatedat'); - $version['updatechannel'] = \OC_Util::getChannel(); + $version['updated'] = $this->appConfig->getValueInt('core', 'lastupdatedat'); + $version['updatechannel'] = $this->serverVersion->getChannel(); $version['edition'] = ''; - $version['build'] = \OC_Util::getBuild(); + $version['build'] = $this->serverVersion->getBuild(); $version['php_major'] = PHP_MAJOR_VERSION; $version['php_minor'] = PHP_MINOR_VERSION; $version['php_release'] = PHP_RELEASE_VERSION; + $version['category'] = $this->computeCategory(); + $version['isSubscriber'] = (int)$this->registry->delegateHasValidSubscription(); $versionString = implode('x', $version); //fetch xml data from updater @@ -92,13 +73,19 @@ class VersionCheck { try { $xml = $this->getUrlContent($url); } catch (\Exception $e) { + $this->logger->info('Version could not be fetched from updater server: ' . $url, ['exception' => $e]); + return false; } if ($xml) { - $loadEntities = libxml_disable_entity_loader(true); - $data = @simplexml_load_string($xml); - libxml_disable_entity_loader($loadEntities); + if (\LIBXML_VERSION < 20900) { + $loadEntities = libxml_disable_entity_loader(true); + $data = @simplexml_load_string($xml); + libxml_disable_entity_loader($loadEntities); + } else { + $data = @simplexml_load_string($xml); + } if ($data !== false) { $tmp['version'] = (string)$data->version; $tmp['versionstring'] = (string)$data->versionstring; @@ -118,14 +105,40 @@ class VersionCheck { } /** - * @codeCoverageIgnore - * @param string $url - * @return resource|string * @throws \Exception */ - protected function getUrlContent($url) { - $client = $this->clientService->newClient(); - $response = $client->get($url); - return $response->getBody(); + protected function getUrlContent(string $url): string { + $response = $this->clientService->newClient()->get($url, [ + 'timeout' => 5, + ]); + + $content = $response->getBody(); + + // IResponse.getBody responds with null|resource if returning a stream response was requested. + // As that's not the case here, we can just ignore the psalm warning by adding an assertion. + assert(is_string($content)); + + return $content; + } + + private function computeCategory(): int { + $categoryBoundaries = [ + 100, + 500, + 1000, + 5000, + 10000, + 100000, + 1000000, + ]; + + $nbUsers = $this->userManager->countSeenUsers(); + foreach ($categoryBoundaries as $categoryId => $boundary) { + if ($nbUsers <= $boundary) { + return $categoryId; + } + } + + return count($categoryBoundaries); } } |