From 050fa56e575f17570bcfd5f9884a42032ee21d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 19 May 2017 14:24:44 +0100 Subject: [PATCH 1/7] A few simple API changes. Tests and README updated. --- README.md | 115 ++++++++++-------- composer.json | 3 +- src/Strategy/GithubStrategy.php | 4 +- src/Strategy/Sha256Strategy.php | 2 +- src/Strategy/ShaStrategy.php | 2 +- src/Strategy/ShaStrategyAbstract.php | 2 +- src/Updater.php | 48 +++++++- .../SelfUpdate/UpdaterGithubStrategyTest.php | 12 +- .../SelfUpdate/UpdaterSha256StrategyTest.php | 11 +- tests/Humbug/Test/SelfUpdate/UpdaterTest.php | 30 +++-- 10 files changed, 151 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 18e65aa..d2ace9b 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,16 @@ will follow in time. Usage ===== -The default update strategy uses an SHA-1 hash of the current remote phar in a +A basic strategy uses an SHA-256 hash of the current remote phar in a version file, and will update the local phar when this version is changed. There is also a Github strategy which tracks Github Releases where you can upload a -new phar file for a release. +new phar file for a release. If you need it for backwards compatibility, or a +transition period, there is an alternative SHA-1 strategy. -### Basic SHA-1 Strategy +You can write custom strategies which must implement +`\Humbug\SelfUpdate\Strategy\StrategyInterface`. + +### Basic SHA-256 Strategy Create your self-update command, or even an update command for some other phar other than the current one, and include this. @@ -67,15 +71,20 @@ other than the current one, and include this. * The simplest usage assumes the currently running phar is to be updated and * that it has been signed with a private key (using OpenSSL). * - * The first constructor parameter is the path to a phar if you are not updating - * the currently running phar. + * The first constructor parameter is the strategy object, the second a boolean + * indicating if the phar is signed, and the third the path to a different PHAR. + * Only the first parameter is required. */ use Humbug\SelfUpdate\Updater; +use Humbug\SelfUpdate\Strategy\Sha256Strategy; + +$strategy = new Sha256Strategy(); +$strategy->setPharUrl('https://example.com/current.phar'); +$strategy->setVersionUrl('https://example.com/current.version'); + +$updater = new Updater($strategy, true); -$updater = new Updater(); -$updater->getStrategy()->setPharUrl('https://example.com/current.phar'); -$updater->getStrategy()->setVersionUrl('https://example.com/current.version'); try { $result = $updater->update(); echo $result ? "Updated!\n" : "No update needed!\n"; @@ -89,15 +98,19 @@ If you are not signing the phar using OpenSSL: ```php /** - * The second parameter to the constructor must be false if your phars are + * The second parameter to the Updater constructor must be false if your phars are * not signed using OpenSSL. */ use Humbug\SelfUpdate\Updater; +use Humbug\SelfUpdate\Strategy\Sha256Strategy; + +$strategy = new Sha256Strategy(); +$strategy->setPharUrl('https://example.com/current.phar'); +$strategy->setVersionUrl('https://example.com/current.version'); + +$updater = new Updater($strategy); -$updater = new Updater(null, false); -$updater->getStrategy()->setPharUrl('https://example.com/current.phar'); -$updater->getStrategy()->setVersionUrl('https://example.com/current.version'); try { $result = $updater->update(); echo $result ? "Updated!\n" : "No update needed!\n"; @@ -107,21 +120,26 @@ try { } ``` -If you need version information: +If you need version information (for signed phars): ```php use Humbug\SelfUpdate\Updater; +use Humbug\SelfUpdate\Strategy\Sha256Strategy; + + +$strategy = new Sha256Strategy(); +$strategy->setPharUrl('https://example.com/current.phar'); +$strategy->setVersionUrl('https://example.com/current.version'); + +$updater = new Updater($strategy, true); -$updater = new Updater(); -$updater->getStrategy()->setPharUrl('https://example.com/current.phar'); -$updater->getStrategy()->setVersionUrl('https://example.com/current.version'); try { $result = $updater->update(); if ($result) { $new = $updater->getNewVersion(); $old = $updater->getOldVersion(); printf( - 'Updated from SHA-1 %s to SHA-1 %s', $old, $new + 'Updated from SHA-256 %s to SHA-256 %s', $old, $new ); } else { echo "No update needed!\n"; @@ -132,9 +150,9 @@ try { } ``` -See the Update Strategies section for an overview of how to setup the SHA-1 +See the Update Strategies section for an overview of how to setup the SHA-256 strategy. It's a simple to maintain choice for development or nightly versions of -phars which are released to a specific numbered version. +phars which do not need SemVer version comparisons for updates. ### Github Release Strategy @@ -147,14 +165,17 @@ the Github Release. * Other than somewhat different setters for the strategy, all other operations * are identical. */ - use Humbug\SelfUpdate\Updater; +use Humbug\SelfUpdate\Strategy\GithubStrategy; + + +$strategy = new GithubStrategy(); +$strategy->setPackageName('myvendor/myapp'); +$strategy->setPharName('myapp.phar'); +$strategy->setCurrentLocalVersion('v1.0.1'); + +$updater = new Updater($strategy); -$updater = new Updater(); -$updater->setStrategy(Updater::STRATEGY_GITHUB); -$updater->getStrategy()->setPackageName('myvendor/myapp'); -$updater->getStrategy()->setPharName('myapp.phar'); -$updater->getStrategy()->setCurrentLocalVersion('v1.0.1'); try { $result = $updater->update(); echo $result ? "Updated!\n" : "No update needed!\n"; @@ -199,7 +220,8 @@ use Humbug\SelfUpdate\Updater; /** * Same constructor parameters as you would use for updating. Here, just defaults. */ -$updater = new Updater(); +$strategy = new SomeStrategy(); +$updater = new Updater($strategy); try { $result = $updater->rollback(); if (!$result) { @@ -225,30 +247,23 @@ The Updater constructor is fairly simple. The three basic variations are: ```php /** - * Default: Update currently running phar which has been signed. - */ -$updater = new Updater; -``` - -```php -/** - * Update currently running phar which has NOT been signed. + * Default: Update currently running phar using passed strategy. */ -$updater = new Updater(null, false); +$updater = new Updater(new SomeStrategy); ``` ```php /** - * Use a strategy other than the default SHA Hash. + * Update currently running phar which HAS been signed. */ -$updater = new Updater(null, false, Updater::STRATEGY_GITHUB); +$updater = new Updater(new SomeStrategy, true); ``` ```php /** * Update a different phar which has NOT been signed. */ -$updater = new Updater('/path/to/impersonatephil.phar', false); +$updater = new Updater(new SomeStrategy, false, '/path/to/impersonatephil.phar'); ``` ### Check For Updates @@ -263,20 +278,22 @@ where a version did exist, but `false` if not. ```php use Humbug\SelfUpdate\Updater; +use Humbug\SelfUpdate\Strategy\GithubStrategy; /** * Configuration is identical in every way for actual updates. You can run this * across multiple configuration variants to get recent stable, unstable, and dev * versions available. * - * This would configure update for an unsigned phar (second constructor must be - * false in this case). + * This would configure update for an unsigned phar (second parameter must be set + * as `true` otherwise). */ -$updater = new Updater(null, false); -$updater->setStrategy(Updater::STRATEGY_GITHUB); -$updater->getStrategy()->setPackageName('myvendor/myapp'); -$updater->getStrategy()->setPharName('myapp.phar'); -$updater->getStrategy()->setCurrentLocalVersion('v1.0.1'); +$strategy = new GithubStrategy(); +$strategy->setPackageName('myvendor/myapp'); +$strategy->setPharName('myapp.phar'); +$strategy->setCurrentLocalVersion('v1.0.1'); + +$updater = new Updater($strategy); try { $result = $updater->hasUpdate(); @@ -327,11 +344,11 @@ strategies. Update Strategies ================= -SHA-1 Hash Synchronisation +SHA-256 Hash Synchronisation -------------------------- The phar-updater package only (that will change!) supports an update strategy -where phars are updated according to the SHA-1 hash of the current phar file +where phars are updated according to the SHA-256 hash of the current phar file available remotely. This assumes the existence of only two to three remote files: * myname.phar @@ -345,11 +362,11 @@ the hash is the very first string (if not the only string). You can generate thi quite easily from bash using: ```sh -sha1sum myname.phar > myname.version +sha256sum myname.phar > myname.version ``` Remember to regenerate the version file for each new phar build you want to distribute. -Using `sha1sum` adds additional data after the hash, but it's fine since the hash is +Using `sha256sum` adds additional data after the hash, but it's fine since the hash is the first string in the file which is the only requirement. If using OpenSSL signing, which is very much recommended, you can also put the diff --git a/composer.json b/composer.json index 20d6b39..34280ae 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,7 @@ } ], "require": { - "php": "^5.6|^7.0", - "padraic/humbug_get_contents": "^1.0" + "php": "^5.6|^7.0" }, "require-dev": { "phpunit/phpunit": "^5.5|^6.0" diff --git a/src/Strategy/GithubStrategy.php b/src/Strategy/GithubStrategy.php index 68a1b4a..2386282 100644 --- a/src/Strategy/GithubStrategy.php +++ b/src/Strategy/GithubStrategy.php @@ -68,7 +68,7 @@ public function download(Updater $updater) { /** Switch remote request errors to HttpRequestExceptions */ set_error_handler(array($updater, 'throwHttpRequestException')); - $result = humbug_get_contents($this->remoteUrl); + $result = file_get_contents($this->remoteUrl); restore_error_handler(); if (false === $result) { throw new HttpRequestException(sprintf( @@ -90,7 +90,7 @@ public function getCurrentRemoteVersion(Updater $updater) /** Switch remote request errors to HttpRequestExceptions */ set_error_handler(array($updater, 'throwHttpRequestException')); $packageUrl = $this->getApiUrl(); - $package = json_decode(humbug_get_contents($packageUrl), true); + $package = json_decode(file_get_contents($packageUrl), true); restore_error_handler(); if (null === $package || json_last_error() !== JSON_ERROR_NONE) { diff --git a/src/Strategy/Sha256Strategy.php b/src/Strategy/Sha256Strategy.php index 1083e18..87df643 100644 --- a/src/Strategy/Sha256Strategy.php +++ b/src/Strategy/Sha256Strategy.php @@ -28,7 +28,7 @@ public function getCurrentRemoteVersion(Updater $updater) { /** Switch remote request errors to HttpRequestExceptions */ set_error_handler(array($updater, 'throwHttpRequestException')); - $version = humbug_get_contents($this->getVersionUrl()); + $version = file_get_contents($this->getVersionUrl()); restore_error_handler(); if (false === $version) { throw new HttpRequestException(sprintf( diff --git a/src/Strategy/ShaStrategy.php b/src/Strategy/ShaStrategy.php index 24e3165..a5a5db0 100644 --- a/src/Strategy/ShaStrategy.php +++ b/src/Strategy/ShaStrategy.php @@ -32,7 +32,7 @@ public function getCurrentRemoteVersion(Updater $updater) { /** Switch remote request errors to HttpRequestExceptions */ set_error_handler(array($updater, 'throwHttpRequestException')); - $version = humbug_get_contents($this->getVersionUrl()); + $version = file_get_contents($this->getVersionUrl()); restore_error_handler(); if (false === $version) { throw new HttpRequestException(sprintf( diff --git a/src/Strategy/ShaStrategyAbstract.php b/src/Strategy/ShaStrategyAbstract.php index 1f1127d..f91b609 100644 --- a/src/Strategy/ShaStrategyAbstract.php +++ b/src/Strategy/ShaStrategyAbstract.php @@ -46,7 +46,7 @@ public function download(Updater $updater) { /** Switch remote request errors to HttpRequestExceptions */ set_error_handler(array($updater, 'throwHttpRequestException')); - $result = humbug_get_contents($this->getPharUrl()); + $result = file_get_contents($this->getPharUrl()); restore_error_handler(); if (false === $result) { throw new HttpRequestException(sprintf( diff --git a/src/Updater.php b/src/Updater.php index 0e03277..bd45fbe 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -93,11 +93,11 @@ class Updater /** * Constructor * - * @param string $localPharFile - * @param bool $hasPubKey * @param string $strategy + * @param bool $hasPubKey + * @param string $localPharFile */ - public function __construct($localPharFile = null, $hasPubKey = true, $strategy = self::STRATEGY_SHA1) + public function __construct(StrategyInterface $strategy, $hasPubKey = false, $localPharFile = null) { ini_set('phar.require_hash', 1); $this->setLocalPharFile($localPharFile); @@ -112,7 +112,7 @@ public function __construct($localPharFile = null, $hasPubKey = true, $strategy $this->setLocalPubKeyFile(); } $this->setTempDirectory(); - $this->setStrategy($strategy); + $this->setStrategyObject($strategy); } /** @@ -176,11 +176,17 @@ public function setStrategy($strategy) } } + /** + * @param StrategyInterface $strategy + */ public function setStrategyObject(StrategyInterface $strategy) { $this->strategy = $strategy; } + /** + * @return StrategyInterface + */ public function getStrategy() { return $this->strategy; @@ -206,26 +212,41 @@ public function getBackupExtension() return $this->backupExtension; } + /** + * @return string + */ public function getLocalPharFile() { return $this->localPharFile; } + /** + * @return string + */ public function getLocalPharFileBasename() { return $this->localPharFileBasename; } + /** + * @return string + */ public function getLocalPubKeyFile() { return $this->localPubKeyFile; } + /** + * @return string + */ public function getTempDirectory() { return $this->tempDirectory; } + /** + * @return string + */ public function getTempPharFile() { return $this->getTempDirectory() @@ -233,11 +254,17 @@ public function getTempPharFile() . sprintf('%s.phar.temp', $this->getLocalPharFileBasename()); } + /** + * @return string + */ public function getNewVersion() { return $this->newVersion; } + /** + * @return string + */ public function getOldVersion() { return $this->oldVersion; @@ -305,16 +332,29 @@ public function getRestorePath() return $this->restorePath; } + /** + * @param int $errno + * @param string $errstr + * @throws RuntimeException + */ public function throwRuntimeException($errno, $errstr) { throw new RuntimeException($errstr); } + /** + * @param int $errno + * @param string $errstr + * @throws HttpRequestException + */ public function throwHttpRequestException($errno, $errstr) { throw new HttpRequestException($errstr); } + /** + * @return boolean + */ protected function hasPubKey() { return $this->hasPubKey; diff --git a/tests/Humbug/Test/SelfUpdate/UpdaterGithubStrategyTest.php b/tests/Humbug/Test/SelfUpdate/UpdaterGithubStrategyTest.php index b336b40..538443c 100644 --- a/tests/Humbug/Test/SelfUpdate/UpdaterGithubStrategyTest.php +++ b/tests/Humbug/Test/SelfUpdate/UpdaterGithubStrategyTest.php @@ -22,6 +22,9 @@ class UpdaterGithubStrategyTest extends TestCase /** @var Updater */ private $updater; + /** @var GithubStrategy */ + private $strategy; + private $tmp; private $data; @@ -30,7 +33,9 @@ public function setUp() { $this->tmp = sys_get_temp_dir(); $this->files = __DIR__ . '/_files'; - $this->updater = new Updater($this->files . '/test.phar', false, Updater::STRATEGY_GITHUB); + + $this->strategy = new GithubStrategy; + $this->updater = new Updater($this->strategy, false, $this->files . '/test.phar'); } public function tearDown() @@ -40,7 +45,7 @@ public function tearDown() public function testConstruction() { - $updater = new Updater(null, false, Updater::STRATEGY_GITHUB); + $updater = new Updater($this->strategy, false); $this->assertTrue( $updater->getStrategy() instanceof GithubStrategy ); @@ -106,8 +111,7 @@ public function testUpdatePhar() $this->createTestPharAndKey(); $this->assertEquals('old', $this->getPharOutput($this->tmp . '/old.phar')); - $updater = new Updater($this->tmp . '/old.phar'); - $updater->setStrategyObject(new GithubTestStrategy); + $updater = new Updater(new GithubTestStrategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharName('new.phar'); $updater->getStrategy()->setPackageName(''); // not used in this test $updater->getStrategy()->setCurrentLocalVersion('1.0.0'); diff --git a/tests/Humbug/Test/SelfUpdate/UpdaterSha256StrategyTest.php b/tests/Humbug/Test/SelfUpdate/UpdaterSha256StrategyTest.php index f63be0f..07ba0aa 100644 --- a/tests/Humbug/Test/SelfUpdate/UpdaterSha256StrategyTest.php +++ b/tests/Humbug/Test/SelfUpdate/UpdaterSha256StrategyTest.php @@ -22,6 +22,9 @@ class UpdaterSha256StrategyTest extends TestCase /** @var Updater */ private $updater; + /** @var Sha256Strategy */ + private $strategy; + private $tmp; private $data; @@ -30,7 +33,9 @@ public function setup() { $this->tmp = sys_get_temp_dir(); $this->files = __DIR__ . '/_files'; - $this->updater = new Updater($this->files . '/test.phar', true, Updater::STRATEGY_SHA256); + + $this->strategy = new Sha256Strategy; + $this->updater = new Updater($this->strategy, true, $this->files . '/test.phar'); } public function teardown() @@ -40,7 +45,7 @@ public function teardown() public function testConstruction() { - $updater = new Updater(null, false, Updater::STRATEGY_SHA256); + $updater = new Updater($this->strategy, false); $this->assertTrue( $updater->getStrategy() instanceof Sha256Strategy ); @@ -127,7 +132,7 @@ public function testUpdatePhar() $this->createTestPharAndKey(); $this->assertEquals('old', $this->getPharOutput($this->tmp . '/old.phar')); - $updater = new Updater($this->tmp . '/old.phar', true, Updater::STRATEGY_SHA256); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharUrl('file://' . $this->files . '/build/new.phar'); $updater->getStrategy()->setVersionUrl('file://' . $this->files . '/build/new.sha256.version'); $this->assertTrue($updater->update()); diff --git a/tests/Humbug/Test/SelfUpdate/UpdaterTest.php b/tests/Humbug/Test/SelfUpdate/UpdaterTest.php index b4feeec..5e9e549 100644 --- a/tests/Humbug/Test/SelfUpdate/UpdaterTest.php +++ b/tests/Humbug/Test/SelfUpdate/UpdaterTest.php @@ -13,8 +13,12 @@ use Humbug\SelfUpdate\Updater; use Humbug\SelfUpdate\Strategy\StrategyInterface; +use Humbug\SelfUpdate\Strategy\ShaStrategy; use PHPUnit\Framework\TestCase; +/** + * @group UpdaterOnly + */ class UpdaterTest extends TestCase { private $files; @@ -22,6 +26,9 @@ class UpdaterTest extends TestCase /** @var Updater */ private $updater; + /** @var StrategyInterface */ + private $strategy; + private $tmp; public function setUp() @@ -29,7 +36,8 @@ public function setUp() $this->tmp = sys_get_temp_dir(); $this->files = __DIR__ . '/_files'; - $this->updater = new Updater($this->files . '/test.phar'); + $this->strategy = new ShaStrategy; + $this->updater = new Updater($this->strategy, true, $this->files . '/test.phar'); } public function tearDown() @@ -40,17 +48,17 @@ public function tearDown() public function testConstruction() { // with key - $updater = new Updater($this->files . '/test.phar'); + $updater = new Updater($this->strategy, true, $this->files . '/test.phar'); $this->assertEquals($updater->getLocalPharFile(), $this->files . '/test.phar'); $this->assertEquals($updater->getLocalPubKeyFile(), $this->files . '/test.phar.pubkey'); // without key - $updater = new Updater($this->files . '/test.phar', false); + $updater = new Updater($this->strategy, false, $this->files . '/test.phar'); $this->assertEquals($updater->getLocalPharFile(), $this->files . '/test.phar'); $this->assertNull($updater->getLocalPubKeyFile()); // no name - detect running console app - $updater = new Updater(null, false); + $updater = new Updater($this->strategy); $this->assertStringEndsWith( 'phpunit.phar', basename($updater->getLocalPharFile(), '.phar') . '.phar' @@ -60,7 +68,7 @@ public function testConstruction() public function testConstructorThrowsExceptionIfPubKeyNotExistsButFlagTrue() { $this->expectException('Humbug\\SelfUpdate\\Exception\\RuntimeException'); - $updater = new Updater($this->files . '/test-nopubkey.phar'); + $updater = new Updater($this->strategy, true, $this->files . '/test-nopubkey.phar'); } public function testConstructorAncilliaryValues() @@ -139,7 +147,7 @@ public function testUpdatePhar() $this->createTestPharAndKey(); $this->assertEquals('old', $this->getPharOutput($this->tmp . '/old.phar')); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharUrl('file://' . $this->files . '/build/new.phar'); $updater->getStrategy()->setVersionUrl('file://' . $this->files . '/build/new.version'); $this->assertTrue($updater->update()); @@ -156,7 +164,7 @@ public function testUpdatePharFailsIfCurrentPublicKeyEmpty() chmod($this->tmp . '/old.phar', 0755); copy($this->files . '/build/badkey.phar.pubkey', $this->tmp . '/old.phar.pubkey'); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharUrl('file://' . $this->files . '/build/new.phar'); $updater->getStrategy()->setVersionUrl('file://' . $this->files . '/build/new.version'); @@ -189,7 +197,7 @@ public function testUpdatePharFailsOnExpectedSignatureMismatch() /** Signature check should fail with invalid signature by a different privkey */ $this->expectException('UnexpectedValueException'); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharUrl('file://' . $this->files . '/build/badsig.phar'); $updater->getStrategy()->setVersionUrl('file://' . $this->files . '/build/badsig.version'); $updater->update(); @@ -201,7 +209,7 @@ public function testUpdatePharFailsOnExpectedSignatureMismatch() public function testUpdatePharFailsIfDownloadPharIsUnsignedWhenExpected() { $this->createTestPharAndKey(); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->getStrategy()->setPharUrl('file://' . $this->files . '/build/nosig.phar'); $updater->getStrategy()->setVersionUrl('file://' . $this->files . '/build/nosig.version'); @@ -213,7 +221,7 @@ public function testUpdatePharFailsIfDownloadPharIsUnsignedWhenExpected() public function testSetBackupPathSetsThePathWhenTheDirectoryExistsAndIsWriteable() { $this->createTestPharAndKey(); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->setBackupPath($this->tmp . '/backup.phar'); $res = $updater->getBackupPath(); $this->assertEquals($this->tmp . '/backup.phar', $res); @@ -222,7 +230,7 @@ public function testSetBackupPathSetsThePathWhenTheDirectoryExistsAndIsWriteable public function testSetRestorePathSetsThePathWhenTheDirectoryExistsAndIsWriteable() { $this->createTestPharAndKey(); - $updater = new Updater($this->tmp . '/old.phar'); + $updater = new Updater($this->strategy, true, $this->tmp . '/old.phar'); $updater->setRestorePath($this->tmp . '/backup.phar'); $res = $updater->getRestorePath(); $this->assertEquals($this->tmp . '/backup.phar', $res); From 899834f054f37a69fc0c343ffc6e93d24abda5b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 19 May 2017 16:17:47 +0100 Subject: [PATCH 2/7] Move SHA specific logic to SHA abstract class (for now) --- src/Strategy/ShaStrategyAbstract.php | 20 +++++++++++++++++++ src/Updater.php | 29 +++++++--------------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Strategy/ShaStrategyAbstract.php b/src/Strategy/ShaStrategyAbstract.php index f91b609..9392f14 100644 --- a/src/Strategy/ShaStrategyAbstract.php +++ b/src/Strategy/ShaStrategyAbstract.php @@ -55,6 +55,26 @@ public function download(Updater $updater) } file_put_contents($updater->getTempPharFile(), $result); + + if ($this instanceof ShaStrategy + || $this instanceof Sha256Strategy + ) { + if ($this instanceof ShaStrategy) { + $tmpVersion = sha1_file($updater->getTempPharFile()); + $algo = 'SHA-1'; + } else { + $tmpVersion = hash_file('sha256', $updater->getTempPharFile()); + $algo = 'SHA-256'; + } + if ($tmpVersion !== $updater->getNewVersion()) { + throw new HttpRequestException(sprintf( + 'Download file appears to be corrupted or outdated. The file ' + . 'received does not have the expected %s hash: %s.', + $algo, + $updater->getNewVersion() + )); + } + } } /** diff --git a/src/Updater.php b/src/Updater.php index bd45fbe..79eaa39 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -386,7 +386,13 @@ protected function backupPhar() protected function downloadPhar() { - $this->strategy->download($this); + try { + $this->strategy->download($this); + } catch (\Exception $e) { + restore_error_handler(); + $this->cleanupAfterError(); + throw $e; + } if (!file_exists($this->getTempPharFile())) { throw new FilesystemException( @@ -394,27 +400,6 @@ protected function downloadPhar() ); } - if ($this->getStrategy() instanceof ShaStrategy - || $this->getStrategy() instanceof Sha256Strategy - ) { - if ($this->getStrategy() instanceof ShaStrategy) { - $tmpVersion = sha1_file($this->getTempPharFile()); - $algo = 'SHA-1'; - } else { - $tmpVersion = hash_file('sha256', $this->getTempPharFile()); - $algo = 'SHA-256'; - } - if ($tmpVersion !== $this->getNewVersion()) { - $this->cleanupAfterError(); - throw new HttpRequestException(sprintf( - 'Download file appears to be corrupted or outdated. The file ' - . 'received does not have the expected %s hash: %s.', - $algo, - $this->getNewVersion() - )); - } - } - try { $this->validatePhar($this->getTempPharFile()); } catch (\Exception $e) { From dd1f71219d88a4ab6ad9a88f2d70191ce2760880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Sat, 20 May 2017 13:55:00 +0100 Subject: [PATCH 3/7] Disable major version updates by default --- README.md | 8 ++++++ src/Strategy/GithubStrategy.php | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/README.md b/README.md index d2ace9b..08f35a8 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,14 @@ the version string used by Github. This can follow any standard practice with recognisable pre- and postfixes, e.g. `v1.0.3`, `1.0.3`, `1.1`, `1.3rc`, `1.3.2pl2`. +By default, the Github Strategy will not update beyond the current major version +of the phar being updated. To allow updates to another major version (e.g. `1.1` +to `2.0`), you should explicitly enable this on the strategy object. + +```php +$strategy->allowMajorVersionUpdates(); +``` + If you wish to update to a non-stable version, for example where users want to update according to a development track, you can set the stability flag for the Github strategy. By default this is set to `stable` or, in constant form, diff --git a/src/Strategy/GithubStrategy.php b/src/Strategy/GithubStrategy.php index 2386282..6ed2b39 100644 --- a/src/Strategy/GithubStrategy.php +++ b/src/Strategy/GithubStrategy.php @@ -58,6 +58,11 @@ class GithubStrategy implements StrategyInterface */ private $stability = self::STABLE; + /** + * @var boolean + */ + private $allowMajor = false; + /** * Download the remote Phar file. * @@ -101,6 +106,11 @@ public function getCurrentRemoteVersion(Updater $updater) } $versions = array_keys($package['package']['versions']); + + if (false === $this->allowMajor) { + $versions = $this->filterByLocalMajorVersion($versions); + } + $versionParser = new VersionParser($versions); if ($this->getStability() === self::STABLE) { $this->remoteVersion = $versionParser->getMostRecentStable(); @@ -135,20 +145,24 @@ public function getCurrentLocalVersion(Updater $updater) * Set version string of the local phar * * @param string $version + * @return self */ public function setCurrentLocalVersion($version) { $this->localVersion = $version; + return $this; } /** * Set Package name * * @param string $name + * @return self */ public function setPackageName($name) { $this->packageName = $name; + return $this; } /** @@ -165,10 +179,12 @@ public function getPackageName() * Set phar file's name * * @param string $name + * @return self */ public function setPharName($name) { $this->pharName = $name; + return $this; } /** @@ -185,6 +201,7 @@ public function getPharName() * Set target stability * * @param string $stability + * @return self */ public function setStability($stability) { @@ -194,6 +211,7 @@ public function setStability($stability) ); } $this->stability = $stability; + return $this; } /** @@ -206,11 +224,27 @@ public function getStability() return $this->stability; } + /** + * @return self + */ + public function allowMajorVersionUpdates() + { + $this->allowMajor = true; + return $this; + } + + /** + * @return string + */ protected function getApiUrl() { return sprintf(self::API_URL, $this->getPackageName()); } + /** + * @param array $package + * @return string + */ protected function getDownloadUrl(array $package) { $baseUrl = preg_replace( @@ -226,4 +260,20 @@ protected function getDownloadUrl(array $package) ); return $downloadUrl; } + + /** + * Filter a list of versions to those that match the current local version. + * + * @param string[] $versions + * @return string[] + */ + private function filterByLocalMajorVersion(array $versions) + { + list($localMajorVersion, ) = explode('.', $this->localVersion, 2); + + return array_filter($versions, function ($version) use ($localMajorVersion) { + list($majorVersion, ) = explode('.', $version, 2); + return $majorVersion === $localMajorVersion; + }); + } } From 68ca2e9e80f115fa2866259fe79cf29d89d7afcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Sat, 20 May 2017 13:56:41 +0100 Subject: [PATCH 4/7] Move setStrategyObject to setStrategy --- src/Updater.php | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/Updater.php b/src/Updater.php index 79eaa39..1360ed6 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -24,11 +24,6 @@ class Updater { - const STRATEGY_SHA1 = 'sha1'; - - const STRATEGY_SHA256 = 'sha256'; - - const STRATEGY_GITHUB = 'github'; /** * @var StrategyInterface @@ -156,30 +151,10 @@ public function rollback() return true; } - /** - * @param string $strategy - */ - public function setStrategy($strategy) - { - switch ($strategy) { - case self::STRATEGY_GITHUB: - $this->strategy = new GithubStrategy; - break; - - case self::STRATEGY_SHA256: - $this->strategy = new Sha256Strategy; - break; - - default: - $this->strategy = new ShaStrategy; - break; - } - } - /** * @param StrategyInterface $strategy */ - public function setStrategyObject(StrategyInterface $strategy) + public function setStrategy(StrategyInterface $strategy) { $this->strategy = $strategy; } From 827a69cb4b9f0609f207d1ad1beacc4091a5c796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Sat, 20 May 2017 14:00:22 +0100 Subject: [PATCH 5/7] Fix tests --- src/Updater.php | 2 +- tests/Humbug/Test/SelfUpdate/UpdaterTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Updater.php b/src/Updater.php index 1360ed6..5eb4810 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -107,7 +107,7 @@ public function __construct(StrategyInterface $strategy, $hasPubKey = false, $lo $this->setLocalPubKeyFile(); } $this->setTempDirectory(); - $this->setStrategyObject($strategy); + $this->setStrategy($strategy); } /** diff --git a/tests/Humbug/Test/SelfUpdate/UpdaterTest.php b/tests/Humbug/Test/SelfUpdate/UpdaterTest.php index 5e9e549..cf81c5c 100644 --- a/tests/Humbug/Test/SelfUpdate/UpdaterTest.php +++ b/tests/Humbug/Test/SelfUpdate/UpdaterTest.php @@ -242,7 +242,7 @@ public function testSetRestorePathSetsThePathWhenTheDirectoryExistsAndIsWriteabl public function testCanSetCustomStrategyObjects() { - $this->updater->setStrategyObject(new FooStrategy); + $this->updater->setStrategy(new FooStrategy); $this->assertTrue($this->updater->getStrategy() instanceof FooStrategy); } From e109eeb993c67411844ff0215fd18f978253c066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Sat, 20 May 2017 14:06:19 +0100 Subject: [PATCH 6/7] A little refactor for SHA hash check --- README.md | 7 +++++-- src/Strategy/Sha256Strategy.php | 21 +++++++++++++++++++++ src/Strategy/ShaStrategy.php | 22 +++++++++++++++++++++- src/Strategy/ShaStrategyAbstract.php | 20 -------------------- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 08f35a8..2640b9e 100644 --- a/README.md +++ b/README.md @@ -342,8 +342,11 @@ To create a custom strategy, you can implement `Humbug\SelfUpdate\Strategy\Strat and pass a new instance of your implementation post-construction. ```php -$updater = new Updater(null, false); -$updater->setStrategyObject(new MyStrategy); +$updater = new Updater(new MyStrategy, false); + +// OR (post construction) + +$updater->setStrategy(new MyStrategy); ``` The similar `setStrategy()` method is solely used to pass flags matching internal diff --git a/src/Strategy/Sha256Strategy.php b/src/Strategy/Sha256Strategy.php index 87df643..5b2dc98 100644 --- a/src/Strategy/Sha256Strategy.php +++ b/src/Strategy/Sha256Strategy.php @@ -18,6 +18,27 @@ final class Sha256Strategy extends ShaStrategyAbstract { + /** + * Download the remote Phar file. + * + * @param Updater $updater + * @return void + */ + public function download(Updater $updater) + { + parent::download($updater); + + $tmpVersion = hash_file('sha256', $updater->getTempPharFile()); + + if ($tmpVersion !== $updater->getNewVersion()) { + throw new HttpRequestException(sprintf( + 'Download file appears to be corrupted or outdated. The file ' + . 'received does not have the expected SHA-256 hash: %s.', + $updater->getNewVersion() + )); + } + } + /** * Retrieve the current version available remotely. * diff --git a/src/Strategy/ShaStrategy.php b/src/Strategy/ShaStrategy.php index a5a5db0..f23d7a5 100644 --- a/src/Strategy/ShaStrategy.php +++ b/src/Strategy/ShaStrategy.php @@ -19,8 +19,28 @@ /** * @deprecated 1.0.4 SHA-1 is increasingly susceptible to collision attacks; use SHA-256 */ -class ShaStrategy extends ShaStrategyAbstract +final class ShaStrategy extends ShaStrategyAbstract { + /** + * Download the remote Phar file. + * + * @param Updater $updater + * @return void + */ + public function download(Updater $updater) + { + parent::download($updater); + + $tmpVersion = sha1_file($updater->getTempPharFile()); + + if ($tmpVersion !== $updater->getNewVersion()) { + throw new HttpRequestException(sprintf( + 'Download file appears to be corrupted or outdated. The file ' + . 'received does not have the expected SHA-1 hash: %s.', + $updater->getNewVersion() + )); + } + } /** * Retrieve the current version available remotely. diff --git a/src/Strategy/ShaStrategyAbstract.php b/src/Strategy/ShaStrategyAbstract.php index 9392f14..f91b609 100644 --- a/src/Strategy/ShaStrategyAbstract.php +++ b/src/Strategy/ShaStrategyAbstract.php @@ -55,26 +55,6 @@ public function download(Updater $updater) } file_put_contents($updater->getTempPharFile(), $result); - - if ($this instanceof ShaStrategy - || $this instanceof Sha256Strategy - ) { - if ($this instanceof ShaStrategy) { - $tmpVersion = sha1_file($updater->getTempPharFile()); - $algo = 'SHA-1'; - } else { - $tmpVersion = hash_file('sha256', $updater->getTempPharFile()); - $algo = 'SHA-256'; - } - if ($tmpVersion !== $updater->getNewVersion()) { - throw new HttpRequestException(sprintf( - 'Download file appears to be corrupted or outdated. The file ' - . 'received does not have the expected %s hash: %s.', - $algo, - $updater->getNewVersion() - )); - } - } } /** From 2694f8475545deede252beec4e123cc36f76083e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Sat, 20 May 2017 14:07:43 +0100 Subject: [PATCH 7/7] CS: remove trailing space --- src/Updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Updater.php b/src/Updater.php index 5eb4810..bff8369 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -366,7 +366,7 @@ protected function downloadPhar() } catch (\Exception $e) { restore_error_handler(); $this->cleanupAfterError(); - throw $e; + throw $e; } if (!file_exists($this->getTempPharFile())) {