Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@
'OC\\App\\CodeChecker\\NodeVisitor' => $baseDir . '/lib/private/App/CodeChecker/NodeVisitor.php',
'OC\\App\\CodeChecker\\PrivateCheck' => $baseDir . '/lib/private/App/CodeChecker/PrivateCheck.php',
'OC\\App\\CodeChecker\\StrongComparisonCheck' => $baseDir . '/lib/private/App/CodeChecker/StrongComparisonCheck.php',
'OC\\App\\CompareVersion' => $baseDir . '/lib/private/App/CompareVersion.php',
'OC\\App\\DependencyAnalyzer' => $baseDir . '/lib/private/App/DependencyAnalyzer.php',
'OC\\App\\InfoParser' => $baseDir . '/lib/private/App/InfoParser.php',
'OC\\App\\Platform' => $baseDir . '/lib/private/App/Platform.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\App\\CodeChecker\\NodeVisitor' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/NodeVisitor.php',
'OC\\App\\CodeChecker\\PrivateCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/PrivateCheck.php',
'OC\\App\\CodeChecker\\StrongComparisonCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/StrongComparisonCheck.php',
'OC\\App\\CompareVersion' => __DIR__ . '/../../..' . '/lib/private/App/CompareVersion.php',
'OC\\App\\DependencyAnalyzer' => __DIR__ . '/../../..' . '/lib/private/App/DependencyAnalyzer.php',
'OC\\App\\InfoParser' => __DIR__ . '/../../..' . '/lib/private/App/InfoParser.php',
'OC\\App\\Platform' => __DIR__ . '/../../..' . '/lib/private/App/Platform.php',
Expand Down
34 changes: 23 additions & 11 deletions lib/private/App/AppStore/Fetcher/AppFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,32 @@
namespace OC\App\AppStore\Fetcher;

use OC\App\AppStore\Version\VersionParser;
use OC\App\CompareVersion;
use OC\Files\AppData\Factory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Util;

class AppFetcher extends Fetcher {

/** @var CompareVersion */
private $compareVersion;

/**
* @param Factory $appDataFactory
* @param IClientService $clientService
* @param ITimeFactory $timeFactory
* @param IConfig $config
* @param CompareVersion $compareVersion
* @param ILogger $logger
*/
public function __construct(Factory $appDataFactory,
IClientService $clientService,
ITimeFactory $timeFactory,
IConfig $config,
CompareVersion $compareVersion,
ILogger $logger) {
parent::__construct(
$appDataFactory,
Expand All @@ -56,6 +64,7 @@ public function __construct(Factory $appDataFactory,

$this->fileName = 'apps.json';
$this->setEndpoint();
$this->compareVersion = $compareVersion;
}

/**
Expand All @@ -70,8 +79,6 @@ protected function fetch($ETag, $content) {
/** @var mixed[] $response */
$response = parent::fetch($ETag, $content);

$ncVersion = $this->getVersion();
$ncMajorVersion = explode('.', $ncVersion)[0];
foreach($response['data'] as $dataKey => $app) {
$releases = [];

Expand All @@ -81,15 +88,20 @@ protected function fetch($ETag, $content) {
if($release['isNightly'] === false
&& strpos($release['version'], '-') === false) {
// Exclude all versions not compatible with the current version
$versionParser = new VersionParser();
$version = $versionParser->getVersion($release['rawPlatformVersionSpec']);
if (
// Major version is bigger or equals to the minimum version of the app
version_compare($ncMajorVersion, $version->getMinimumVersion(), '>=')
// Major version is smaller or equals to the maximum version of the app
&& version_compare($ncMajorVersion, $version->getMaximumVersion(), '<=')
) {
$releases[] = $release;
try {
$versionParser = new VersionParser();
$version = $versionParser->getVersion($release['rawPlatformVersionSpec']);
$ncVersion = $this->getVersion();
$min = $version->getMinimumVersion();
$max = $version->getMaximumVersion();
$minFulfilled = $this->compareVersion->isCompatible($ncVersion, $min, '>=');
$maxFulfilled = $max !== '' &&
$this->compareVersion->isCompatible($ncVersion, $max, '<=');
if ($minFulfilled && $maxFulfilled) {
$releases[] = $release;
}
} catch (\InvalidArgumentException $e) {
$this->logger->logException($e, ['app' => 'appstoreFetcher', 'level' => Util::WARN]);
}
}
}
Expand Down
97 changes: 97 additions & 0 deletions lib/private/App/CompareVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

/**
* @copyright 2018 Christoph Wurst <[email protected]>
*
* @author 2018 Christoph Wurst <[email protected]>
*
* @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\App;

use InvalidArgumentException;

class CompareVersion {

const REGEX_MAJOR = '/^\d+$/';
const REGEX_MAJOR_MINOR = '/^\d+.\d+$/';
const REGEX_MAJOR_MINOR_PATCH = '/^\d+.\d+.\d+$/';
const REGEX_SERVER = '/^\d+.\d+.\d+(.\d+)?$/';

/**
* Checks if the given server version fulfills the given (app) version requirements.
*
* Version requirements can be 'major.minor.patch', 'major.minor' or just 'major',
* so '13.0.1', '13.0' and '13' are valid.
*
* @param string $actual version as major.minor.patch notation
* @param string $required version where major is requried and minor and patch are optional
* @param string $comparator passed to `version_compare`
* @return bool whether the requirement is fulfilled
* @throws InvalidArgumentException if versions specified in an invalid format
*/
public function isCompatible(string $actual, string $required,
string $comparator = '>='): bool {

if (!preg_match(self::REGEX_SERVER, $actual)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to do then only a check agains REGEX_MAJOR_MINOR_PATCH because it is excluded below anyways (in the else branch)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below only $required is checked, not $actual. And the server version is in x.x.x(.x) format, hence the special regex with the optional forth-level version.

throw new InvalidArgumentException('server version is invalid');
}

if (preg_match(self::REGEX_MAJOR, $required) === 1) {
return $this->compareMajor($actual, $required, $comparator);
} else if (preg_match(self::REGEX_MAJOR_MINOR, $required) === 1) {
return $this->compareMajorMinor($actual, $required, $comparator);
} else if (preg_match(self::REGEX_MAJOR_MINOR_PATCH, $required) === 1) {
return $this->compareMajorMinorPatch($actual, $required, $comparator);
} else {
throw new InvalidArgumentException('required version is invalid');
}
}

private function compareMajor(string $actual, string $required,
string $comparator) {
$actualMajor = explode('.', $actual)[0];
$requiredMajor = explode('.', $required)[0];

return version_compare($actualMajor, $requiredMajor, $comparator);
}

private function compareMajorMinor(string $actual, string $required,
string $comparator) {
$actualMajor = explode('.', $actual)[0];
$actualMinor = explode('.', $actual)[1];
$requiredMajor = explode('.', $required)[0];
$requiredMinor = explode('.', $required)[1];

return version_compare("$actualMajor.$actualMinor",
"$requiredMajor.$requiredMinor", $comparator);
}

private function compareMajorMinorPatch($actual, $required, $comparator) {
$actualMajor = explode('.', $actual)[0];
$actualMinor = explode('.', $actual)[1];
$actualPatch = explode('.', $actual)[2];
$requiredMajor = explode('.', $required)[0];
$requiredMinor = explode('.', $required)[1];
$requiredPatch = explode('.', $required)[2];

return version_compare("$actualMajor.$actualMinor.$actualPatch",
"$requiredMajor.$requiredMinor.$requiredPatch", $comparator);
}

}
18 changes: 12 additions & 6 deletions tests/lib/App/AppStore/Fetcher/AppFetcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
namespace Test\App\AppStore\Fetcher;

use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\CompareVersion;
use OC\Files\AppData\Factory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IAppData;
Expand All @@ -33,18 +34,21 @@
use OCP\Http\Client\IResponse;
use OCP\IConfig;
use OCP\ILogger;
use PHPUnit_Framework_MockObject_MockObject;
use Test\TestCase;

class AppFetcherTest extends TestCase {
/** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */
/** @var IAppData|PHPUnit_Framework_MockObject_MockObject */
protected $appData;
/** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */
/** @var IClientService|PHPUnit_Framework_MockObject_MockObject */
protected $clientService;
/** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
/** @var ITimeFactory|PHPUnit_Framework_MockObject_MockObject */
protected $timeFactory;
/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
/** @var IConfig|PHPUnit_Framework_MockObject_MockObject */
protected $config;
/** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
/** @var CompareVersion|PHPUnit_Framework_MockObject_MockObject */
protected $compareVersion;
/** @var ILogger|PHPUnit_Framework_MockObject_MockObject */
protected $logger;
/** @var AppFetcher */
protected $fetcher;
Expand All @@ -57,7 +61,7 @@ class AppFetcherTest extends TestCase {
public function setUp() {
parent::setUp();

/** @var Factory|\PHPUnit_Framework_MockObject_MockObject $factory */
/** @var Factory|PHPUnit_Framework_MockObject_MockObject $factory */
$factory = $this->createMock(Factory::class);
$this->appData = $this->createMock(IAppData::class);
$factory->expects($this->once())
Expand All @@ -67,6 +71,7 @@ public function setUp() {
$this->clientService = $this->createMock(IClientService::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->config = $this->createMock(IConfig::class);
$this->compareVersion = new CompareVersion();
$this->logger = $this->createMock(ILogger::class);

$this->config
Expand All @@ -79,6 +84,7 @@ public function setUp() {
$this->clientService,
$this->timeFactory,
$this->config,
$this->compareVersion,
$this->logger
);
}
Expand Down
91 changes: 91 additions & 0 deletions tests/lib/App/CompareVersionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

/**
* @copyright 2018 Christoph Wurst <[email protected]>
*
* @author 2018 Christoph Wurst <[email protected]>
*
* @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 Test\App;

use InvalidArgumentException;
use OC\App\CompareVersion;
use Test\TestCase;

class CompareVersionTest extends TestCase {

/** @var CompareVersion */
private $compare;

protected function setUp() {
parent::setUp();

$this->compare = new CompareVersion();
}

public function comparisonData() {
return [
// Compatible versions
['13.0.0.3', '13.0.0', '>=', true],
['13.0.0.3', '13.0.0', '<=', true],
['13.0.0', '13.0.0', '>=', true],
['13.0.0', '13.0', '<=', true],
['13.0.0', '13', '>=', true],
['13.0.1', '13', '>=', true],
['13.0.1', '13', '<=', true],
// Incompatible major versions
['13.0.0.3', '13.0.0', '<', false],
['12.0.0', '13.0.0', '>=', false],
['12.0.0', '13.0', '>=', false],
['12.0.0', '13', '>=', false],
// Incompatible minor and patch versions
['13.0.0', '13.0.1', '>=', false],
['13.0.0', '13.1', '>=', false],
// Compatible minor and patch versions
['13.0.1', '13.0.0', '>=', true],
['13.2.0', '13.1', '>=', true],
];
}

/**
* @dataProvider comparisonData
*/
public function testComparison(string $actualVersion, string $requiredVersion,
string $comparator, bool $expected) {
$isCompatible = $this->compare->isCompatible($actualVersion, $requiredVersion,
$comparator);

$this->assertEquals($expected, $isCompatible);
}

public function testInvalidServerVersion() {
$actualVersion = '13';
$this->expectException(InvalidArgumentException::class);

$this->compare->isCompatible($actualVersion, '13.0.0');
}

public function testInvalidRequiredVersion() {
$actualVersion = '13.0.0';
$this->expectException(InvalidArgumentException::class);

$this->compare->isCompatible($actualVersion, '13.0.0.9');
}

}