├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── res └── schema.json └── src ├── lib ├── Herrera │ └── Phar │ │ └── Update │ │ ├── Exception │ │ ├── Exception.php │ │ ├── ExceptionInterface.php │ │ ├── FileException.php │ │ ├── InvalidArgumentException.php │ │ └── LogicException.php │ │ ├── Manager.php │ │ ├── Manifest.php │ │ └── Update.php └── constants.php └── tests ├── Herrera └── Phar │ └── Update │ ├── Exception │ └── ExceptionTest.php │ ├── ManagerTest.php │ ├── ManifestTest.php │ └── UpdateTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /bin/ 3 | /coverage/ 4 | /src/vendors/ 5 | 6 | /*.iml 7 | /composer.lock 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | 7 | before_script: 8 | - composer self-update 9 | - composer install --dev 10 | 11 | script: bin/phpunit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Kevin Herrera 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Phar Update 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/herrera-io/php-phar-update.png?branch=master)](https://travis-ci.org/herrera-io/php-phar-update) 5 | 6 | A library for self-updating Phars. 7 | 8 | Summary 9 | ------- 10 | 11 | This library handles the updating of applications packaged as distributable Phars. The modular design allows for a more customizable update process. 12 | 13 | Installation 14 | ------------ 15 | 16 | Add it to your list of Composer dependencies: 17 | 18 | ```sh 19 | $ composer require herrera-io/phar-update=1.* 20 | ``` 21 | 22 | Usage 23 | ----- 24 | 25 | ```php 26 | update('1.0.0', true); 37 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "herrera-io/phar-update", 3 | "description": "A library for self-updating Phars.", 4 | "keywords": ["phar", "update"], 5 | "homepage": "http://herrera-io.github.com/php-phar-update", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Kevin Herrera", 10 | "email": "kevin@herrera.io", 11 | "homepage": "http://kevin.herrera.io" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/herrera-io/php-phar-update/issues" 16 | }, 17 | "require": { 18 | "php": ">=5.3.3", 19 | "herrera-io/json": "1.*", 20 | "herrera-io/version": "1.*" 21 | }, 22 | "require-dev": { 23 | "herrera-io/phpunit-test-case": "1.*", 24 | "mikey179/vfsStream": "1.1.0", 25 | "phpunit/phpunit": "3.7.*" 26 | }, 27 | "autoload": { 28 | "files": ["src/lib/constants.php"], 29 | "psr-0": { 30 | "Herrera\\Phar\\Update": "src/lib" 31 | } 32 | }, 33 | "config": { 34 | "bin-dir": "bin", 35 | "vendor-dir": "src/vendors" 36 | }, 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "2.0-dev" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | src/lib/ 15 | 16 | 17 | 18 | 19 | src/tests/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /res/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Update Manifest Schema", 3 | "type": "array", 4 | "items": { 5 | "title": "Update", 6 | "type": "object", 7 | "additionalProperties": false, 8 | "properties": { 9 | "name": { 10 | "description": "The name of the file that will be downloaded.", 11 | "type": "string", 12 | "pattern": "\\.phar$" 13 | }, 14 | "publicKey": { 15 | "description": "The location of the public key file.", 16 | "type": "string" 17 | }, 18 | "sha1": { 19 | "description": "The SHA1 file checksum.", 20 | "type": "string", 21 | "pattern": "^[a-f0-9]{40}$" 22 | }, 23 | "url": { 24 | "description": "The location of the update file.", 25 | "type": "string" 26 | }, 27 | "version": { 28 | "description": "The semantic version number.", 29 | "type": "string", 30 | "pattern": "^\\d+\\.\\d+\\.\\d+(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$" 31 | } 32 | }, 33 | "required": ["name", "sha1", "url", "version"] 34 | } 35 | } -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Exception extends \Exception implements ExceptionInterface 11 | { 12 | /** 13 | * Creates a new exception using a format and values. 14 | * 15 | * @param string $format The format. 16 | * @param mixed $value,... The value(s). 17 | * 18 | * @return Exception The exception. 19 | */ 20 | public static function create($format, $value = null) 21 | { 22 | if (0 < func_num_args()) { 23 | $format = vsprintf($format, array_slice(func_get_args(), 1)); 24 | } 25 | 26 | return new static($format); 27 | } 28 | 29 | /** 30 | * Creates an exception for the last error message. 31 | * 32 | * @return Exception The exception. 33 | */ 34 | public static function lastError() 35 | { 36 | $error = error_get_last(); 37 | 38 | return new static($error['message']); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Exception/FileException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidArgumentException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class LogicException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Manager.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Manager 15 | { 16 | /** 17 | * The update manifest. 18 | * 19 | * @var Manifest 20 | */ 21 | private $manifest; 22 | 23 | /** 24 | * The running file (the Phar that will be updated). 25 | * 26 | * @var string 27 | */ 28 | private $runningFile; 29 | 30 | /** 31 | * Sets the update manifest. 32 | * 33 | * @param Manifest $manifest The manifest. 34 | */ 35 | public function __construct(Manifest $manifest) 36 | { 37 | $this->manifest = $manifest; 38 | } 39 | 40 | /** 41 | * Returns the manifest. 42 | * 43 | * @return Manifest The manifest. 44 | */ 45 | public function getManifest() 46 | { 47 | return $this->manifest; 48 | } 49 | 50 | /** 51 | * Returns the running file (the Phar that will be updated). 52 | * 53 | * @return string The file. 54 | */ 55 | public function getRunningFile() 56 | { 57 | if (null === $this->runningFile) { 58 | $this->runningFile = realpath($_SERVER['argv'][0]); 59 | } 60 | 61 | return $this->runningFile; 62 | } 63 | 64 | /** 65 | * Sets the running file (the Phar that will be updated). 66 | * 67 | * @param string $file The file name or path. 68 | * 69 | * @throws Exception\Exception 70 | * @throws InvalidArgumentException If the file path is invalid. 71 | */ 72 | public function setRunningFile($file) 73 | { 74 | if (false === is_file($file)) { 75 | throw InvalidArgumentException::create( 76 | 'The file "%s" is not a file or it does not exist.', 77 | $file 78 | ); 79 | } 80 | 81 | $this->runningFile = $file; 82 | } 83 | 84 | /** 85 | * Updates the running Phar if any is available. 86 | * 87 | * @param string|Version $version The current version. 88 | * @param boolean $major Lock to current major version? 89 | * @param boolean $pre Allow pre-releases? 90 | * 91 | * @return boolean TRUE if an update was performed, FALSE if none available. 92 | */ 93 | public function update($version, $major = false, $pre = false) 94 | { 95 | if (false === ($version instanceof Version)) { 96 | $version = Parser::toVersion($version); 97 | } 98 | 99 | if (null !== ($update = $this->manifest->findRecent( 100 | $version, 101 | $major, 102 | $pre 103 | ))) { 104 | $update->getFile(); 105 | $update->copyTo($this->getRunningFile()); 106 | 107 | return true; 108 | } 109 | 110 | return false; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Manifest.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Manifest 16 | { 17 | /** 18 | * The list of updates in the manifest. 19 | * 20 | * @var Update[] 21 | */ 22 | private $updates; 23 | 24 | /** 25 | * Sets the list of updates from the manifest. 26 | * 27 | * @param Update[] $updates The updates. 28 | */ 29 | public function __construct(array $updates = array()) 30 | { 31 | $this->updates = $updates; 32 | } 33 | 34 | /** 35 | * Finds the most recent update and returns it. 36 | * 37 | * @param Version $version The current version. 38 | * @param boolean $major Lock to major version? 39 | * @param boolean $pre Allow pre-releases? 40 | * 41 | * @return Update The update. 42 | */ 43 | public function findRecent(Version $version, $major = false, $pre = false) 44 | { 45 | /** @var $current Update */ 46 | $current = null; 47 | $major = $major ? $version->getMajor() : null; 48 | 49 | foreach ($this->updates as $update) { 50 | if ($major && ($major !== $update->getVersion()->getMajor())) { 51 | continue; 52 | } 53 | 54 | if ((false === $pre) 55 | && !$update->getVersion()->isStable()) { 56 | continue; 57 | } 58 | 59 | $test = $current ? $current->getVersion() : $version; 60 | 61 | if (false === $update->isNewer($test)) { 62 | continue; 63 | } 64 | 65 | $current = $update; 66 | } 67 | 68 | return $current; 69 | } 70 | 71 | /** 72 | * Returns the list of updates in the manifest. 73 | * 74 | * @return Update[] The updates. 75 | */ 76 | public function getUpdates() 77 | { 78 | return $this->updates; 79 | } 80 | 81 | /** 82 | * Loads the manifest from a JSON encoded string. 83 | * 84 | * @param string $json The JSON encoded string. 85 | * 86 | * @return Manifest The manifest. 87 | */ 88 | public static function load($json) 89 | { 90 | $j = new Json(); 91 | 92 | return self::create($j->decode($json), $j); 93 | } 94 | 95 | /** 96 | * Loads the manifest from a JSON encoded file. 97 | * 98 | * @param string $file The JSON encoded file. 99 | * 100 | * @return Manifest The manifest. 101 | */ 102 | public static function loadFile($file) 103 | { 104 | $json = new Json(); 105 | 106 | return self::create($json->decodeFile($file), $json); 107 | } 108 | 109 | /** 110 | * Validates the data, processes it, and returns a new instance of Manifest. 111 | * 112 | * @param array $decoded The decoded JSON data. 113 | * @param Json $json The Json instance used to decode the data. 114 | * 115 | * @return Manifest The new instance. 116 | */ 117 | private static function create($decoded, Json $json) 118 | { 119 | $json->validate( 120 | $json->decodeFile(PHAR_UPDATE_MANIFEST_SCHEMA), 121 | $decoded 122 | ); 123 | 124 | $updates = array(); 125 | 126 | foreach ($decoded as $update) { 127 | $updates[] = new Update( 128 | $update->name, 129 | $update->sha1, 130 | $update->url, 131 | Parser::toVersion($update->version), 132 | isset($update->publicKey) ? $update->publicKey : null 133 | ); 134 | } 135 | 136 | usort( 137 | $updates, 138 | function (Update $a, Update $b) { 139 | return Comparator::isGreaterThan( 140 | $a->getVersion(), 141 | $b->getVersion() 142 | ); 143 | } 144 | ); 145 | 146 | return new static($updates); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/lib/Herrera/Phar/Update/Update.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Update 19 | { 20 | /** 21 | * The temporary file path. 22 | * 23 | * @var string 24 | */ 25 | private $file; 26 | 27 | /** 28 | * The name of the update file. 29 | * 30 | * @var string 31 | */ 32 | private $name; 33 | 34 | /** 35 | * The URL where the public key can be downloaded from. 36 | * 37 | * @var string 38 | */ 39 | private $publicKey; 40 | 41 | /** 42 | * The SHA1 file checksum. 43 | * 44 | * @var string 45 | */ 46 | private $sha1; 47 | 48 | /** 49 | * The URL where the update can be downloaded from. 50 | * 51 | * @var string 52 | */ 53 | private $url; 54 | 55 | /** 56 | * The version of the update. 57 | * 58 | * @var Version 59 | */ 60 | private $version; 61 | 62 | /** 63 | * Sets the update information. 64 | * 65 | * @param string $name The name of the update file. 66 | * @param string $sha1 The SHA1 file checksum. 67 | * @param string $url The URL where the update can be downloaded from. 68 | * @param Version $version The version of the update. 69 | * @param string $key The URL where the public key can be downloaded 70 | * from. 71 | */ 72 | public function __construct( 73 | $name, 74 | $sha1, 75 | $url, 76 | Version $version, 77 | $key = null 78 | ) { 79 | $this->name = $name; 80 | $this->publicKey = $key; 81 | $this->sha1 = $sha1; 82 | $this->url = $url; 83 | $this->version = $version; 84 | } 85 | 86 | /** 87 | * Copies the update file to the destination. 88 | * 89 | * @param string $file The target file. 90 | * 91 | * @throws Exception\Exception 92 | * @throws FileException If the file could not be replaced. 93 | */ 94 | public function copyTo($file) 95 | { 96 | if (null === $this->file) { 97 | throw LogicException::create( 98 | 'The update file has not been downloaded.' 99 | ); 100 | } 101 | 102 | $mode = 0755; 103 | 104 | if (file_exists($file)) { 105 | $mode = fileperms($file) & 511; 106 | } 107 | 108 | if (false === @copy($this->file, $file)) { 109 | throw FileException::lastError(); 110 | } 111 | 112 | if (false === @chmod($file, $mode)) { 113 | throw FileException::lastError(); 114 | } 115 | 116 | $key = $file . '.pubkey'; 117 | 118 | if (file_exists($this->file . '.pubkey')) { 119 | if (false === @copy($this->file . '.pubkey', $key)) { 120 | throw FileException::lastError(); 121 | } 122 | } elseif (file_exists($key)) { 123 | if (false === @unlink($key)) { 124 | throw FileException::lastError(); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Cleans up by deleting the temporary update file. 131 | * 132 | * @throws FileException If the file could not be deleted. 133 | */ 134 | public function deleteFile() 135 | { 136 | if ($this->file) { 137 | if (file_exists($this->file)) { 138 | if (false === @unlink($this->file)) { 139 | throw FileException::lastError(); 140 | } 141 | } 142 | 143 | if (file_exists($this->file . '.pubkey')) { 144 | if (false === @unlink($this->file . '.pubkey')) { 145 | throw FileException::lastError(); 146 | } 147 | } 148 | 149 | $dir = dirname($this->file); 150 | 151 | if (file_exists($dir)) { 152 | if (false === @rmdir($dir)) { 153 | throw FileException::lastError(); 154 | } 155 | } 156 | 157 | $this->file = null; 158 | } 159 | } 160 | 161 | /** 162 | * Downloads the update file to a temporary location. 163 | * 164 | * @return string The temporary file path. 165 | * 166 | * @throws Exception\Exception 167 | * @throws FileException If the SHA1 checksum differs. 168 | * @throws UnexpectedValueException If the Phar is corrupt. 169 | */ 170 | public function getFile() 171 | { 172 | if (null === $this->file) { 173 | unlink($this->file = tempnam(sys_get_temp_dir(), 'upd')); 174 | mkdir($this->file); 175 | 176 | $this->file .= DIRECTORY_SEPARATOR . $this->name; 177 | 178 | $in = new SplFileObject($this->url, 'rb', false); 179 | $out = new SplFileObject($this->file, 'wb', false); 180 | 181 | while (false === $in->eof()) { 182 | $out->fwrite($in->fgets()); 183 | } 184 | 185 | unset($in, $out); 186 | 187 | if ($this->publicKey) { 188 | $in = new SplFileObject($this->publicKey, 'r', false); 189 | $out = new SplFileObject($this->file . '.pubkey', 'w', false); 190 | 191 | while (false === $in->eof()) { 192 | $out->fwrite($in->fgets()); 193 | } 194 | 195 | unset($in, $out); 196 | } 197 | 198 | if ($this->sha1 !== ($sha1 = sha1_file($this->file))) { 199 | $this->deleteFile(); 200 | 201 | throw FileException::create( 202 | 'Mismatch of the SHA1 checksum (%s) of the downloaded file (%s).', 203 | $this->sha1, 204 | $sha1 205 | ); 206 | } 207 | 208 | // double check 209 | try { 210 | new Phar($this->file); 211 | } catch (UnexpectedValueException $exception) { 212 | $this->deleteFile(); 213 | 214 | throw $exception; 215 | } 216 | } 217 | 218 | return $this->file; 219 | } 220 | 221 | /** 222 | * Returns name of the update file. 223 | * 224 | * @return string The name. 225 | */ 226 | public function getName() 227 | { 228 | return $this->name; 229 | } 230 | 231 | /** 232 | * Returns the URL where the public key can be downloaded from. 233 | * 234 | * @return string The URL. 235 | */ 236 | public function getPublicKey() 237 | { 238 | return $this->publicKey; 239 | } 240 | 241 | /** 242 | * Returns the SHA1 file checksum. 243 | * 244 | * @return string The checksum. 245 | */ 246 | public function getSha1() 247 | { 248 | return $this->sha1; 249 | } 250 | 251 | /** 252 | * Returns the URL where the update can be downloaded from. 253 | * 254 | * @return string The URL. 255 | */ 256 | public function getUrl() 257 | { 258 | return $this->url; 259 | } 260 | 261 | /** 262 | * Returns the version of the update. 263 | * 264 | * @return Version The version. 265 | */ 266 | public function getVersion() 267 | { 268 | return $this->version; 269 | } 270 | 271 | /** 272 | * Checks if this update is newer than the version given. 273 | * 274 | * @param Version $version The current version. 275 | * 276 | * @return boolean TRUE if the update is newer, FALSE if not. 277 | */ 278 | public function isNewer(Version $version) 279 | { 280 | return Comparator::isGreaterThan($this->version, $version); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/lib/constants.php: -------------------------------------------------------------------------------- 1 | assertEquals('My message.', $exception->getMessage()); 15 | } 16 | 17 | public function testLastError() 18 | { 19 | @$test; 20 | 21 | $exception = Exception::lastError(); 22 | 23 | $this->assertEquals('Undefined variable: test', $exception->getMessage()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tests/Herrera/Phar/Update/ManagerTest.php: -------------------------------------------------------------------------------- 1 | assertSame($this->manifest, $this->manager->getManifest()); 22 | } 23 | 24 | public function testGetRunningFile() 25 | { 26 | $this->assertEquals( 27 | realpath($_SERVER['argv'][0]), 28 | $this->manager->getRunningFile() 29 | ); 30 | } 31 | 32 | /** 33 | * @depends testGetRunningFile 34 | */ 35 | public function testSetRunningFile() 36 | { 37 | $file = $this->createFile(); 38 | 39 | $this->manager->setRunningFile($file); 40 | 41 | $this->assertEquals($file, $this->manager->getRunningFile()); 42 | } 43 | 44 | public function testSetRunningFileNotExist() 45 | { 46 | $this->setExpectedException( 47 | 'Herrera\\Phar\\Update\\Exception\\InvalidArgumentException', 48 | 'The file "/does/not/exist" is not a file or it does not exist.' 49 | ); 50 | 51 | $this->manager->setRunningFile('/does/not/exist'); 52 | } 53 | 54 | /** 55 | * @depends testSetRunningFile 56 | */ 57 | public function testUpdate() 58 | { 59 | unlink($currentFile = $this->createFile('current.phar')); 60 | unlink($newFile = $this->createFile('new.phar')); 61 | 62 | $current = new Phar($currentFile); 63 | $current->addFromString('test.php', 'setStub($current->createDefaultStub('test.php')); 65 | 66 | $new = new Phar($newFile); 67 | $new->addFromString('test.php', 'setStub($new->createDefaultStub('test.php')); 69 | 70 | unset($current, $new); 71 | 72 | $manager = new Manager(new Manifest(array(new Update( 73 | 'new.phar', 74 | sha1_file($newFile), 75 | $newFile, 76 | Parser::toVersion('1.0.1') 77 | )))); 78 | 79 | $manager->setRunningFile($currentFile); 80 | 81 | $this->assertTrue($manager->update('1.0.0')); 82 | $this->assertEquals('new', exec('php ' . escapeshellarg($currentFile))); 83 | } 84 | 85 | public function testUpdateNone() 86 | { 87 | $manager = new Manager(new Manifest(array(new Update( 88 | 'new.phar', 89 | 'test', 90 | 'test', 91 | Parser::toVersion('2.0.1') 92 | )))); 93 | 94 | $manager->setRunningFile($this->createFile()); 95 | 96 | $this->assertFalse($manager->update('1.0.0', true)); 97 | } 98 | 99 | protected function setUp() 100 | { 101 | $this->manifest = new Manifest(); 102 | $this->manager = new Manager($this->manifest); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/tests/Herrera/Phar/Update/ManifestTest.php: -------------------------------------------------------------------------------- 1 | assertSame( 30 | $this->v1, 31 | $this->manifest->findRecent($version, true) 32 | ); 33 | $this->assertSame( 34 | $this->v1p, 35 | $this->manifest->findRecent( 36 | Parser::toVersion('2.0.0-alpha.1'), 37 | true, 38 | true 39 | ) 40 | ); 41 | $this->assertSame($this->v2, $this->manifest->findRecent($version)); 42 | } 43 | 44 | public function testFindRecentNone() 45 | { 46 | $this->assertNull( 47 | $this->manifest->findRecent(Parser::toVersion('5.0.0')) 48 | ); 49 | } 50 | 51 | public function testGetUpdates() 52 | { 53 | $this->assertSame( 54 | array($this->v1, $this->v1p, $this->v2), 55 | $this->manifest->getUpdates() 56 | ); 57 | } 58 | 59 | public function testLoad() 60 | { 61 | $data = json_encode(array( 62 | array( 63 | 'name' => 'test.phar', 64 | 'publicKey' => 'http://example.com/test-1.2.3.phar.pubkey', 65 | 'sha1' => 'abcdefabcdefabcdefabcdefabcdefabcdefabcd', 66 | 'url' => 'http://example.com/test-1.2.3.phar', 67 | 'version' => '1.2.3', 68 | ), 69 | array( 70 | 'name' => 'test.phar', 71 | 'publicKey' => 'http://example.com/test-4.5.6.phar.pubkey', 72 | 'sha1' => '0123456789012345678901234567890123456789', 73 | 'url' => 'http://example.com/test-4.5.6.phar', 74 | 'version' => '4.5.6' 75 | ) 76 | )); 77 | 78 | try { 79 | $updates = Manifest::load($data)->getUpdates(); 80 | } catch (JsonException $exception) { 81 | print_r($exception->getErrors()); 82 | throw $exception; 83 | } 84 | 85 | $this->assertEquals( 86 | 'http://example.com/test-1.2.3.phar.pubkey', 87 | $updates[0]->getPublicKey() 88 | ); 89 | $this->assertEquals( 90 | 'http://example.com/test-4.5.6.phar.pubkey', 91 | $updates[1]->getPublicKey() 92 | ); 93 | } 94 | 95 | public function testLoadFile() 96 | { 97 | file_put_contents($file = $this->createFile(), json_encode(array( 98 | array( 99 | 'name' => 'test.phar', 100 | 'sha1' => 'abcdefabcdefabcdefabcdefabcdefabcdefabcd', 101 | 'url' => 'http://example.com/test-1.2.3.phar', 102 | 'version' => '1.2.3' 103 | ), 104 | array( 105 | 'name' => 'test.phar', 106 | 'sha1' => '0123456789012345678901234567890123456789', 107 | 'url' => 'http://example.com/test-4.5.6.phar', 108 | 'version' => '4.5.6' 109 | ) 110 | ))); 111 | 112 | try { 113 | $updates = Manifest::loadFile($file)->getUpdates(); 114 | } catch (JsonException $exception) { 115 | print_r($exception->getErrors()); 116 | throw $exception; 117 | } 118 | 119 | $this->assertEquals( 120 | 'abcdefabcdefabcdefabcdefabcdefabcdefabcd', 121 | $updates[0]->getSha1() 122 | ); 123 | $this->assertEquals( 124 | '0123456789012345678901234567890123456789', 125 | $updates[1]->getSha1() 126 | ); 127 | } 128 | 129 | protected function setUp() 130 | { 131 | $this->v1 = new Update( 132 | 'test.phar', 133 | '0123456789012345678901234567890123456789', 134 | 'http://example.com/test.phar', 135 | Parser::toVersion('1.2.3') 136 | ); 137 | 138 | $this->v1p = new Update( 139 | 'test.phar', 140 | '0123456789012345678901234567890123456789', 141 | 'http://example.com/test.phar', 142 | Parser::toVersion('2.0.0-alpha.2') 143 | ); 144 | 145 | $this->v2 = new Update( 146 | 'test.phar', 147 | '0123456789012345678901234567890123456789', 148 | 'http://example.com/test.phar', 149 | Parser::toVersion('4.5.6') 150 | ); 151 | 152 | $this->manifest = new Manifest(array( 153 | $this->v1, 154 | $this->v1p, 155 | $this->v2 156 | )); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/tests/Herrera/Phar/Update/UpdateTest.php: -------------------------------------------------------------------------------- 1 | createFile('test.phar')); 24 | 25 | $key = $this->getKey(); 26 | $phar = new Phar($file); 27 | $phar->addFromString('test.php', 'setStub($phar->createDefaultStub('test.php')); 29 | $phar->setSignatureAlgorithm( 30 | Phar::OPENSSL, 31 | $key[0] 32 | ); 33 | 34 | file_put_contents($file . '.pubkey', $key[1]); 35 | 36 | unset($phar); 37 | 38 | $this->setPropertyValue($this->update, 'file', $file); 39 | 40 | return $file; 41 | } 42 | 43 | public function getKey() 44 | { 45 | return array( 46 | <<createPhar(); 94 | $target = $this->createFile('taco.phar'); 95 | 96 | chmod($file, 0755); 97 | 98 | $this->update->copyTo($target); 99 | 100 | $this->assertFileEquals($file, $target); 101 | $this->assertFileEquals($file . '.pubkey', $target . '.pubkey'); 102 | 103 | if (false === strpos(strtolower(PHP_OS), 'win')) { 104 | $this->assertEquals(0755, fileperms($file) & 511); 105 | } 106 | } 107 | 108 | public function testCopyToNotDownloaded() 109 | { 110 | $this->setExpectedException( 111 | 'Herrera\\Phar\\Update\\Exception\\LogicException', 112 | 'The update file has not been downloaded.' 113 | ); 114 | 115 | $this->update->copyTo($this->createFile()); 116 | } 117 | 118 | public function testCopyToCopyError() 119 | { 120 | $this->createPhar(); 121 | 122 | $root = vfsStream::newDirectory('test'); 123 | $root->addChild(vfsStream::newFile('test.phar', 0000)); 124 | 125 | $this->setExpectedException( 126 | 'Herrera\\Phar\\Update\\Exception\\FileException', 127 | 'failed to open stream' 128 | ); 129 | 130 | $this->update->copyTo('vfs://test/test.phar'); 131 | } 132 | 133 | public function testDeleteFile() 134 | { 135 | $file = $this->createFile('test.phar'); 136 | 137 | $this->setPropertyValue($this->update, 'file', $file); 138 | 139 | $this->update->deleteFile(); 140 | 141 | $this->assertFileNotExists(dirname($file)); 142 | } 143 | 144 | public function testDeleteFileUnlinkError() 145 | { 146 | $root = vfsStream::newDirectory('test'); 147 | $root->addChild(vfsStream::newFile('test.phar', 0000)); 148 | 149 | vfsStreamWrapper::setRoot($root); 150 | 151 | $this->setPropertyValue($this->update, 'file', 'vfs://test/test.phar'); 152 | 153 | // unlink() does not issue warning on streams, but does return false 154 | $this->setExpectedException( 155 | 'Herrera\\Phar\\Update\\Exception\\FileException' 156 | ); 157 | 158 | $this->update->deleteFile(); 159 | } 160 | 161 | public function testDeleteFileRmdirError() 162 | { 163 | $file = $this->createFile(); 164 | 165 | $this->setPropertyValue( 166 | $this->update, 167 | 'file', 168 | $file . DIRECTORY_SEPARATOR . 'test.phar' 169 | ); 170 | 171 | $this->setExpectedException( 172 | 'Herrera\\Phar\\Update\\Exception\\FileException', 173 | 'rmdir' 174 | ); 175 | 176 | $this->update->deleteFile(); 177 | } 178 | 179 | public function testGetFile() 180 | { 181 | unlink($file = $this->createFile('test.phar')); 182 | 183 | $key = $this->getKey(); 184 | $phar = new Phar($file); 185 | $phar->addFromString('test.php', 'setStub($phar->createDefaultStub('test.php')); 187 | $phar->setSignatureAlgorithm( 188 | Phar::OPENSSL, 189 | $key[0] 190 | ); 191 | 192 | file_put_contents($file . '.pubkey', $key[1]); 193 | 194 | unset($phar); 195 | 196 | $this->setPropertyValue($this->update, 'publicKey', $file . '.pubkey'); 197 | $this->setPropertyValue($this->update, 'sha1', sha1_file($file)); 198 | $this->setPropertyValue($this->update, 'url', $file); 199 | 200 | $this->assertFileEquals($file, $this->update->getFile()); 201 | } 202 | 203 | public function testGetFileCorrupt() 204 | { 205 | $file = $this->createFile('test.phar'); 206 | 207 | file_put_contents($file, 'setPropertyValue($this->update, 'publicKey', null); 210 | $this->setPropertyValue($this->update, 'sha1', sha1_file($file)); 211 | $this->setPropertyValue($this->update, 'url', $file); 212 | 213 | $this->setExpectedException('UnexpectedValueException'); 214 | 215 | $this->assertFileEquals($file, $this->update->getFile()); 216 | } 217 | 218 | public function testGetFileSha1Mismatch() 219 | { 220 | $file = $this->createFile(); 221 | 222 | file_put_contents($file, 'test'); 223 | 224 | $this->setPropertyValue($this->update, 'publicKey', null); 225 | $this->setPropertyValue($this->update, 'url', $file); 226 | 227 | $this->setExpectedException( 228 | 'Herrera\\Phar\\Update\\Exception\\FileException', 229 | 'Mismatch of the SHA1 checksum (1234567890123456789012345678901234567890) of the downloaded file (' . sha1_file($file) . ').' 230 | ); 231 | 232 | $this->update->getFile(); 233 | } 234 | 235 | public function testGetName() 236 | { 237 | $this->assertEquals('test.phar', $this->update->getName()); 238 | } 239 | 240 | public function testGetPublicKey() 241 | { 242 | $this->assertEquals( 243 | 'http://example.com/test-1.2.3.phar.pubkey', 244 | $this->update->getPublicKey() 245 | ); 246 | } 247 | 248 | public function testGetSha1() 249 | { 250 | $this->assertEquals( 251 | '1234567890123456789012345678901234567890', 252 | $this->update->getSha1() 253 | ); 254 | } 255 | 256 | public function testGetUrl() 257 | { 258 | $this->assertEquals( 259 | 'http://example.com/test.phar', 260 | $this->update->getUrl() 261 | ); 262 | } 263 | 264 | public function testGetVersion() 265 | { 266 | $this->assertSame($this->version, $this->update->getVersion()); 267 | } 268 | 269 | public function testIsNewer() 270 | { 271 | $this->assertTrue($this->update->isNewer(Parser::toVersion('1.0.0'))); 272 | } 273 | 274 | protected function setUp() 275 | { 276 | $this->update = new Update( 277 | 'test.phar', 278 | '1234567890123456789012345678901234567890', 279 | 'http://example.com/test.phar', 280 | $this->version = Parser::toVersion('1.2.3'), 281 | 'http://example.com/test-1.2.3.phar.pubkey' 282 | ); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |