aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Updater
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Updater')
-rw-r--r--lib/private/Updater/Changes.php46
-rw-r--r--lib/private/Updater/ChangesCheck.php48
-rw-r--r--lib/private/Updater/ChangesMapper.php32
-rw-r--r--lib/private/Updater/ChangesResult.php63
-rw-r--r--lib/private/Updater/Exceptions/ReleaseMetadataException.php17
-rw-r--r--lib/private/Updater/ReleaseMetadata.php79
-rw-r--r--lib/private/Updater/VersionCheck.php125
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);
}
}