├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── res ├── empty.phar ├── example.phar ├── invalid.phar ├── md5.phar ├── missing.phar ├── mixed.phar ├── openssl.phar ├── openssl.phar.pubkey ├── sha1.phar ├── sha256.phar └── sha512.phar └── src ├── lib └── Herrera │ └── Box │ ├── Box.php │ ├── Compactor │ ├── Compactor.php │ ├── CompactorInterface.php │ ├── Composer.php │ ├── Javascript.php │ ├── Json.php │ └── Php.php │ ├── Exception │ ├── Exception.php │ ├── ExceptionInterface.php │ ├── FileException.php │ ├── InvalidArgumentException.php │ ├── OpenSslException.php │ ├── SignatureException.php │ └── UnexpectedValueException.php │ ├── Extract.php │ ├── Signature.php │ ├── Signature │ ├── AbstractBufferedHash.php │ ├── AbstractPublicKey.php │ ├── Hash.php │ ├── OpenSsl.php │ ├── PhpSecLib.php │ ├── PublicKeyDelegate.php │ └── VerifyInterface.php │ └── StubGenerator.php └── tests ├── Herrera └── Box │ └── Tests │ ├── BoxTest.php │ ├── Compactor.php │ ├── Compactor │ ├── BaseCompactor.php │ ├── CompactorTest.php │ ├── JavascriptTest.php │ ├── JsonTest.php │ └── PhpTest.php │ ├── Exception │ ├── ExceptionTest.php │ └── OpenSslExceptionTest.php │ ├── ExtractTest.php │ ├── Signature │ ├── AbstractBufferedHashTest.php │ ├── AbstractPublicKeyTest.php │ ├── BufferedHash.php │ ├── HashTest.php │ ├── OpenSslTest.php │ ├── PhpSecLibTest.php │ ├── PublicKey.php │ └── PublicKeyDelegateTest.php │ ├── SignatureTest.php │ └── StubGeneratorTest.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 | Box 2 | === 3 | 4 | [![Build Status][]](https://travis-ci.org/box-project/box2-lib) 5 | 6 | Box is a library built on the [`Phar`][] class. It is designed to make it 7 | easier to create new phars and modifying existing ones. Features include 8 | compacting source files, better custom stub generation, and better OpenSSL 9 | signing handling. 10 | 11 | Example 12 | ------- 13 | 14 | ```php 15 | use Herrera\Box\Box; 16 | use Herrera\Box\StubGenerator; 17 | 18 | $box = Box::create('test.phar'); 19 | 20 | $box->buildFromDirectory('/path/to/dir'); 21 | 22 | $box->getPhar()->setStub( 23 | StubGenerator::create() 24 | ->index('path/to/script.php') 25 | ->generate() 26 | ); 27 | ``` 28 | 29 | Installation 30 | ------------ 31 | 32 | Add it to your list of Composer dependencies: 33 | 34 | ```sh 35 | $ composer require herrera-io/box=1.* 36 | ``` 37 | 38 | Usage 39 | ----- 40 | 41 | The Box library includes many features and some are designed so that they can 42 | be used independently of each other. This is done to allow the phar builder 43 | better control of the phar building process. 44 | 45 | ### Compacting Source Files 46 | 47 | Box makes uses of source file "compactors". A compactor is simply a class 48 | that checks if the given file is supported, then manipulates its contents 49 | to make it smaller. I will later cover how to actually use them in 50 | **Finally, Building Phars**. 51 | 52 | There are two ways of creating a compactor class: implement the 53 | `CompactorInterface` interface, or extend the `Compactor` abstract class. 54 | 55 | #### Implementing `CompactorInterface` 56 | 57 | The [`CompactorInterface`][] interface only requires that you implement two 58 | methods in your class: `compact($contents)` and `support($file)`. The 59 | `$contents` argument is the contents of the source file, and the `$file` 60 | argument is the full path to the file. How you determine which file types 61 | are supported is entirely up to you. 62 | 63 | In this example, this custom compactor will only modify files that end in 64 | `.php`, and remove whitespace from the end of each line: 65 | 66 | ```php 67 | namespace Example\Compactor; 68 | 69 | use Herrera\Box\Compactor\CompactorInterface; 70 | 71 | /** 72 | * My example compactor. 73 | */ 74 | class RemoveWhitespace implements CompactorInterface 75 | { 76 | /** 77 | * Seek and destroy (whitespaces). 78 | * 79 | * @param string $source The source code. 80 | * 81 | * @return string The compacted source code. 82 | */ 83 | public function compact($source) 84 | { 85 | return preg_replace('/[ \t]+$/m', '', $source); 86 | } 87 | 88 | /** 89 | * Make sure we support it. 90 | * 91 | * @param string $file The file path. 92 | * 93 | * @return boolean Returns TRUE if supported, FALSE if not. 94 | */ 95 | public function supports($file) 96 | { 97 | return ('php' === pathinfo($file, PATHINFO_EXTENSION)); 98 | } 99 | } 100 | ``` 101 | 102 | #### Extending `Compactor` 103 | 104 | An abstract compactor class is included that handles file type checking for 105 | you. You simply need to provide the default list of file extensions supported. 106 | These extension can be overwritten later if necessary, by the developer using 107 | them. 108 | 109 | This example is a variation of the previous example compactor: 110 | 111 | ```php 112 | namespace Example\Compactor; 113 | 114 | use Herrera\Box\Compactor\Compactor; 115 | 116 | /** 117 | * My example compactor. 118 | */ 119 | class RemoveWhitespace extends Compactor 120 | { 121 | /** 122 | * The default supported file extensions. 123 | * 124 | * @var array 125 | */ 126 | protected $extensions = array('php'); 127 | 128 | /** 129 | * Seek and destroy (whitespaces). 130 | * 131 | * @param string $source The source code. 132 | * 133 | * @return string The compacted source code. 134 | */ 135 | public function compact($source) 136 | { 137 | return preg_replace('/[ \t]+$/m', '', $source); 138 | } 139 | } 140 | ``` 141 | 142 | Developers can later change the supported file extensions by calling the 143 | `Compactor->setExtensions()` method: 144 | 145 | ```php 146 | $example = new Example\Compactor\RemoveWhitespace(); 147 | 148 | $example->setExtensions( 149 | array( 150 | 'inc', 151 | 'php' 152 | ) 153 | ); 154 | ``` 155 | 156 | #### Bundled Compactors 157 | 158 | The library has two compactors bundled for your convenience. 159 | 160 | ##### Compacting JavaScript 161 | 162 | The `JavaScript` compactor will minify JavaScript files, but requires the 163 | [`tedivm/jshrink`][] packages to work. This is included when you install 164 | the Box library. 165 | 166 | ```php 167 | use Herrera\Box\Compactor\Javascript; 168 | 169 | $compactor = new Javascript(); 170 | ``` 171 | 172 | ##### Compacting JSON 173 | 174 | The `JSON` compactor is very simple to use as there are no options to 175 | configure. However, the `json` extension is required to use it. All extra 176 | whitespace is removed from `.json` files. 177 | 178 | ```php 179 | use Herrera\Box\Compactor\Json; 180 | 181 | $compactor = new Json(); 182 | ``` 183 | 184 | ##### Compacting PHP 185 | 186 | The `PHP` compactor will strip all comments whitespace from `.php` files. 187 | Comments that are removed will be removed with an the same number of line 188 | breaks as the original comment. This is done in order to preserve the line 189 | number that is reported when errors occur in the phar. 190 | 191 | ```php 192 | use Herrera\Box\Compactor\Php; 193 | 194 | $compactor = new Php(); 195 | ``` 196 | 197 | If you make use of Doctrine formatted annotations, you can also make use 198 | of a special feature within the `Php` compactor. To compact comments and 199 | preserve annotations, you will need to install the [`herrera-io/annotations`][] 200 | library and create an instance of `Tokenizer`. 201 | 202 | ```php 203 | use Herrera\Annotations\Tokenizer; 204 | 205 | $compactor->setTokenizer(new Tokenizer()); 206 | ``` 207 | 208 | Both line count and annotation data is preserved. 209 | 210 | ### Managing Signatures 211 | 212 | The `Phar` class provides an easy way of extracting and verifying a phar's 213 | signature. Simply instantiating the class will verify the phar in question. 214 | However, the `phar` extension is required to do either task. The Box library 215 | includes a way to extract and verify signatures without the use of the 216 | extension. 217 | 218 | ```php 219 | use Herrera\Box\Exception\SignatureException; 220 | use Herrera\Box\Signature; 221 | 222 | $sig = new Signature('/path/to/my.phar'); 223 | 224 | 225 | // same output as Phar->getSignature() 226 | $signature = $sig->get(); 227 | 228 | try { 229 | // TRUE if passed, FALSE if failed 230 | $result = $sig->verify(); 231 | } catch (SignatureException $exception) { 232 | // the signature could not be verified 233 | } 234 | ``` 235 | 236 | The `Signature::create()` method is an alias to `Signature::__construct()` 237 | which allows for a shorthand version of the above example: 238 | 239 | ```php 240 | if (Signature::create('/path/to/my.phar')->verify()) { 241 | // do the do 242 | } 243 | ``` 244 | 245 | The purpose of being able to verify a phar without having the extension 246 | available is more prudent in nature. In sensitive environments without the 247 | extension available, a dev or sys admin may want to verify the integrity of 248 | a phar they are using before making it available on the system. 249 | 250 | ### Extracting Phars 251 | 252 | In addition to being able to verify a phar's signature without the extension, 253 | you can also extract its contents. This feature is primarily designed to be 254 | embedded as part of a custom stub, but it can also be used to extract any 255 | phar. 256 | 257 | ```php 258 | use Herrera\Box\Extract; 259 | 260 | $extract = new Extract('/path/to/my.phar', 65538); 261 | 262 | $extract->go('/path/to/extract/dir'); 263 | ``` 264 | 265 | The first argument for the constructor is the path to the existing phar. The 266 | second being the length of the stub. This second argument is required in order 267 | for the `Extract` class to know where the phar's manifest begins. Usually, this 268 | value is generated by the `Phar` class when the default stub is used. 269 | 270 | If the value is unknown, the `Extract` class can be used to make a best guess 271 | effort by calling the `Extract::findStubLength()` method. If the stub length 272 | is incorrectly guessed, the `Extract` class will thrown an exception at some 273 | point during the extraction process. 274 | 275 | By default, the `Extract->go()` method will create a temporary directory path 276 | and extract the contents of the phar there. The directory path specified in 277 | the example is optional. 278 | 279 | In order to reduce overhead, the `Extract` class will not re-extract the phar 280 | if a special file exists in the target directory. This is used to speed up the 281 | execution process for phars that were executed without the phar extension. 282 | 283 | Note that if any of the files within the phar were compressed using either 284 | gzip or bzip2, their respective extensions will be required for decompression. 285 | If the required extension is not installed, the `Extract` class will throw an 286 | exception. 287 | 288 | ### Generating Stubs 289 | 290 | If appropriate for the project, a custom stub can be generated by the Box 291 | library. You will have control over the following functions in the stub: 292 | 293 | - Setting an alias. 294 | - Setting a "banner" comment (such as a copyright statement). 295 | - Embed the `Extract` class to support self-extraction. 296 | - Setting the CLI index script. 297 | - Enabling the use of `Phar::interceptFileFuncs()`. 298 | - Setting the file mimetypes. 299 | - Setting the list of server variables to "mung". 300 | - Setting the 404 script. 301 | - Setting the "shebang" line. 302 | - Opting the user of `Phar::webPhar()` over `Phar::mapPhar()`. 303 | 304 | The following is an example of a stub generated with all of the settings used: 305 | 306 | ```php 307 | use Herrera\Box\StubGenerator; 308 | 309 | $generator = new StubGenerator(); 310 | 311 | $banner = << Phar::PHP 319 | ); 320 | 321 | $rewrite = <<alias('test.phar') 330 | ->banner($banner) 331 | ->extract(true) 332 | ->index('bin/cli.php') 333 | ->intercept(true) 334 | ->mimetypes($mimetypes) 335 | ->mung(array('REQUEST_URI')) 336 | ->notFound('lib/404.php') 337 | ->rewrite($rewrite) 338 | ->shebang('/home/dude/.local/php/bin/php') 339 | ->web(true) 340 | ->generate(); 341 | ``` 342 | 343 | And the resulting stub: 344 | 345 | ```php 346 | '); 353 | define('BOX_EXTRACT_PATTERN_OPEN', "__HALT" . "_COMPILER(); ?>\r\n"); 354 | if (class_exists('Phar')) { 355 | Phar::webPhar('test.phar', 'bin/cli.php', 'lib/404.php', array ( 356 | 'phps' => 0, 357 | ), 'function rewrite_url($uri) 358 | { 359 | return $rewritten; 360 | }'); 361 | Phar::interceptFileFuncs(); 362 | Phar::mungServer(array ( 363 | 0 => 'REQUEST_URI', 364 | )); 365 | } else { 366 | $extract = new Extract(__FILE__, Extract::findStubLength(__FILE__)); 367 | $dir = $extract->go(); 368 | set_include_path($dir . PATH_SEPARATOR . get_include_path()); 369 | require "$dir/bin/cli.php"; 370 | } 371 | // ... snip ... 372 | 373 | __HALT_COMPILER(); 374 | ``` 375 | 376 | > For the sake of brevity, the embedded `Extract` class was replaced with 377 | > "... snip ...". 378 | 379 | The example stub is likely overkill for what you need. By not using the 380 | `extract()` method, you can easily shave a few hundred lines of code from 381 | your stub, reducing its size, but you will lose the ability to execute the 382 | phar in an environment without the `phar` extension. 383 | 384 | ### Finally, Building Phars 385 | 386 | All these features are great, but they're even better when used together in 387 | the `Box` class. The `Box` class is designed to automatically integrate all 388 | of these features in a (hopefully) simple to use interface. 389 | 390 | There are two ways of instantiating the class: 391 | 392 | ```php 393 | user Herrera\Box\Box; 394 | 395 | // use an existing Phar instance 396 | $box = new Box($phar); 397 | 398 | // or create one 399 | $box = Box::create('/path/to/my.phar'); 400 | ``` 401 | 402 | > Note that the `Box::create()` method accepts the same arguments as the 403 | > `Phar::__construct()` method. 404 | 405 | #### Registering Compactors 406 | 407 | Whether you are using the bundled compactors or your own, you will need to 408 | call the `Box->addCompactor()` method to register your class with `Box`. 409 | All files added to the phar using `Box` will be automatically run through 410 | the supported compactors. 411 | 412 | ```php 413 | use Herrera\Box\Compactor\Json; 414 | use Herrera\Box\Compactor\Php; 415 | 416 | $box->addCompactor(new Json()); 417 | $box->addCompactor(new Php()); 418 | $box->addCompactor($custom); 419 | ``` 420 | 421 | #### Using Placeholder Values 422 | 423 | The `Box` class offers the ability to search and replace placeholder values 424 | in added files. Keep in mind that only scalar values are supported for any 425 | replacements. 426 | 427 | ```php 428 | $box->setValues( 429 | array( 430 | '@match@' => 'replace' 431 | ) 432 | ); 433 | ``` 434 | 435 | With the above value to match, the following code: 436 | 437 | ```php 438 | $myCode = 'This @match@ is now "replace".'; 439 | ``` 440 | 441 | will be added to the phar as: 442 | 443 | ```php 444 | $myCode = 'This replace is now "replace".'; 445 | ``` 446 | 447 | #### Adding Files 448 | 449 | To actually make use of the registered compactors and set placeholder value 450 | replacements, you will need to use the `Box` class's methods for adding files. 451 | The methods are identical to that of the `Phar` class, but automatically apply 452 | the appropriate compactors and replacements: 453 | 454 | - `Box->addFile()` 455 | - `Box->addFromString()` 456 | - `Box->buildFromDirectory()` 457 | - `Box->buildFromIterator()` 458 | 459 | Note that if you need you need to add a file without any alterations (such as 460 | a binary file), you may need to add the file directly using the `Phar` instance: 461 | 462 | ```php 463 | $phar = $box->getPhar(); 464 | 465 | $phar->addFile('...'); 466 | ``` 467 | 468 | #### Setting the Stub 469 | 470 | The `Box` class offers a simple way of adding a stub sourced from a file, and 471 | also applying the placeholder replacements at the same time: 472 | 473 | ```php 474 | $box->setStubUsingFile('/path/to/stub.php', true); 475 | ``` 476 | 477 | The second argument indicates that replacements should be performed on the 478 | stub. If you leave it out, it will default to `false` which means that no 479 | replacements will be performed and the stub will be used as is. 480 | 481 | #### Private Key Signing 482 | 483 | The `Box` class offers two simple ways of signing the phar using a private 484 | key. Either method will, however, require the availability of the `openssl` 485 | extension. 486 | 487 | ##### With a String 488 | 489 | If you have already loaded the private key as a string variable, you can use 490 | the `Box->sign()` method. 491 | 492 | ```php 493 | $box->sign($key, $pass); 494 | ``` 495 | 496 | The password is only required if the key was generated with a password. 497 | 498 | ##### With a File 499 | 500 | You can also sign the phar using a private key contained with a file. 501 | 502 | ```php 503 | $box->signUsingFile($file, $pass); 504 | ``` 505 | 506 | [Build Status]: https://travis-ci.org/box-project/box2-lib.png?branch=master 507 | [`Phar`]: http://us3.php.net/manual/en/class.phar.php 508 | [`CompactorInterface`]: src/lib/Herrera/Box/Compactor/CompactorInterface.php 509 | [`tedivm/jshrink`]: https://github.com/tedious/JShrink 510 | [`herrera-io/annotations`]: https://github.com/herrera-io/php-annotations 511 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "herrera-io/box", 3 | "description": "A library for simplifying the PHAR build process.", 4 | "keywords": ["phar"], 5 | "homepage": "https://github.com/box-project/box2-lib", 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/box-project/box2-lib/issues" 16 | }, 17 | "require": { 18 | "php": ">=5.3.3", 19 | "ext-phar": "*", 20 | "tedivm/jshrink": "~1.0", 21 | "phine/path": "~1.0" 22 | }, 23 | "require-dev": { 24 | "herrera-io/annotations": "~1.0", 25 | "herrera-io/phpunit-test-case": "1.*", 26 | "mikey179/vfsStream": "1.1.0", 27 | "phpunit/phpunit": "3.7.*", 28 | "phpseclib/phpseclib": "~0.3" 29 | }, 30 | "suggest": { 31 | "herrera-io/annotations": "For compacting annotated docblocks.", 32 | "phpseclib/phpseclib": "For verifying OpenSSL signed phars without the phar extension." 33 | }, 34 | "autoload": { 35 | "psr-0": { 36 | "Herrera\\Box": "src/lib" 37 | } 38 | }, 39 | "config": { 40 | "bin-dir": "bin", 41 | "vendor-dir": "src/vendors" 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "1.0-dev" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | src/lib/ 15 | 16 | 17 | 18 | 19 | src/tests/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /res/empty.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/empty.phar -------------------------------------------------------------------------------- /res/example.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/example.phar -------------------------------------------------------------------------------- /res/invalid.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/invalid.phar -------------------------------------------------------------------------------- /res/md5.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/md5.phar -------------------------------------------------------------------------------- /res/missing.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/missing.phar -------------------------------------------------------------------------------- /res/mixed.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/mixed.phar -------------------------------------------------------------------------------- /res/openssl.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/openssl.phar -------------------------------------------------------------------------------- /res/openssl.phar.pubkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKuZkrHT54KtuBCTrR36+4tibd+2un9b 3 | aLFs3X+RHc/jDCXL8pJATz049ckfcfd2ZCMIzH1PHew8H+EMhy4CbSECAwEAAQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /res/sha1.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/sha1.phar -------------------------------------------------------------------------------- /res/sha256.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/sha256.phar -------------------------------------------------------------------------------- /res/sha512.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/box-project/box2-lib/1c5d528d0c44af9d084c2e68ed8b5863db9abe0c/res/sha512.phar -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Box.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class Box 26 | { 27 | /** 28 | * The source code compactors. 29 | * 30 | * @var SplObjectStorage 31 | */ 32 | private $compactors; 33 | 34 | /** 35 | * The path to the Phar file. 36 | * 37 | * @var string 38 | */ 39 | private $file; 40 | 41 | /** 42 | * The Phar instance. 43 | * 44 | * @var Phar 45 | */ 46 | private $phar; 47 | 48 | /** 49 | * The placeholder values. 50 | * 51 | * @var array 52 | */ 53 | private $values = array(); 54 | 55 | /** 56 | * Sets the Phar instance. 57 | * 58 | * @param Phar $phar The instance. 59 | * @param string $file The path to the Phar file. 60 | */ 61 | public function __construct(Phar $phar, $file) 62 | { 63 | $this->compactors = new SplObjectStorage(); 64 | $this->file = $file; 65 | $this->phar = $phar; 66 | } 67 | 68 | /** 69 | * Adds a file contents compactor. 70 | * 71 | * @param CompactorInterface $compactor The compactor. 72 | */ 73 | public function addCompactor(CompactorInterface $compactor) 74 | { 75 | $this->compactors->attach($compactor); 76 | } 77 | 78 | /** 79 | * Adds a file to the Phar, after compacting it and replacing its 80 | * placeholders. 81 | * 82 | * @param string $file The file name. 83 | * @param string $local The local file name. 84 | * 85 | * @throws Exception\Exception 86 | * @throws FileException If the file could not be used. 87 | */ 88 | public function addFile($file, $local = null) 89 | { 90 | if (null === $local) { 91 | $local = $file; 92 | } 93 | 94 | if (false === is_file($file)) { 95 | throw FileException::create( 96 | 'The file "%s" does not exist or is not a file.', 97 | $file 98 | ); 99 | } 100 | 101 | if (false === ($contents = @file_get_contents($file))) { 102 | throw FileException::lastError(); 103 | } 104 | 105 | $this->addFromString($local, $contents); 106 | } 107 | 108 | /** 109 | * Adds the contents from a file to the Phar, after compacting it and 110 | * replacing its placeholders. 111 | * 112 | * @param string $local The local name. 113 | * @param string $contents The contents. 114 | */ 115 | public function addFromString($local, $contents) 116 | { 117 | $this->phar->addFromString( 118 | $local, 119 | $this->replaceValues($this->compactContents($local, $contents)) 120 | ); 121 | } 122 | 123 | /** 124 | * Similar to Phar::buildFromDirectory(), except the files will be 125 | * compacted and their placeholders replaced. 126 | * 127 | * @param string $dir The directory. 128 | * @param string $regex The regular expression filter. 129 | */ 130 | public function buildFromDirectory($dir, $regex = null) 131 | { 132 | $iterator = new RecursiveIteratorIterator( 133 | new RecursiveDirectoryIterator( 134 | $dir, 135 | FilesystemIterator::KEY_AS_PATHNAME 136 | | FilesystemIterator::CURRENT_AS_FILEINFO 137 | | FilesystemIterator::SKIP_DOTS 138 | ) 139 | ); 140 | 141 | if ($regex) { 142 | $iterator = new RegexIterator($iterator, $regex); 143 | } 144 | 145 | $this->buildFromIterator($iterator, $dir); 146 | } 147 | 148 | /** 149 | * Similar to Phar::buildFromIterator(), except the files will be compacted 150 | * and their placeholders replaced. 151 | * 152 | * @param Traversable $iterator The iterator. 153 | * @param string $base The base directory path. 154 | * 155 | * @throws Exception\Exception 156 | * @throws UnexpectedValueException If the iterator value is unexpected. 157 | */ 158 | public function buildFromIterator(Traversable $iterator, $base = null) 159 | { 160 | if ($base) { 161 | $base = Path::canonical($base . DIRECTORY_SEPARATOR); 162 | } 163 | 164 | foreach ($iterator as $key => $value) { 165 | if (is_string($value)) { 166 | if (false === is_string($key)) { 167 | throw UnexpectedValueException::create( 168 | 'The key returned by the iterator (%s) is not a string.', 169 | gettype($key) 170 | ); 171 | } 172 | 173 | $key = Path::canonical($key); 174 | $value = Path::canonical($value); 175 | 176 | if (is_dir($value)) { 177 | $this->phar->addEmptyDir($key); 178 | } else { 179 | $this->addFile($value, $key); 180 | } 181 | } elseif ($value instanceof SplFileInfo) { 182 | if (null === $base) { 183 | throw InvalidArgumentException::create( 184 | 'The $base argument is required for SplFileInfo values.' 185 | ); 186 | } 187 | 188 | /** @var $value SplFileInfo */ 189 | $real = $value->getRealPath(); 190 | 191 | if (0 !== strpos($real, $base)) { 192 | throw UnexpectedValueException::create( 193 | 'The file "%s" is not in the base directory.', 194 | $real 195 | ); 196 | } 197 | 198 | $local = str_replace($base, '', $real); 199 | 200 | if ($value->isDir()) { 201 | $this->phar->addEmptyDir($local); 202 | } else { 203 | $this->addFile($real, $local); 204 | } 205 | } else { 206 | throw UnexpectedValueException::create( 207 | 'The iterator value "%s" was not expected.', 208 | gettype($value) 209 | ); 210 | } 211 | } 212 | } 213 | 214 | /** 215 | * Compacts the file contents using the supported compactors. 216 | * 217 | * @param string $file The file name. 218 | * @param string $contents The file contents. 219 | * 220 | * @return string The compacted contents. 221 | */ 222 | public function compactContents($file, $contents) 223 | { 224 | foreach ($this->compactors as $compactor) { 225 | /** @var $compactor CompactorInterface */ 226 | if ($compactor->supports($file)) { 227 | $contents = $compactor->compact($contents); 228 | } 229 | } 230 | 231 | return $contents; 232 | } 233 | 234 | /** 235 | * Creates a new Phar and Box instance. 236 | * 237 | * @param string $file The file name. 238 | * @param integer $flags The RecursiveDirectoryIterator flags. 239 | * @param string $alias The Phar alias. 240 | * 241 | * @return Box The Box instance. 242 | */ 243 | public static function create($file, $flags = null, $alias = null) 244 | { 245 | return new Box(new Phar($file, $flags, $alias), $file); 246 | } 247 | 248 | /** 249 | * Returns the Phar instance. 250 | * 251 | * @return Phar The instance. 252 | */ 253 | public function getPhar() 254 | { 255 | return $this->phar; 256 | } 257 | 258 | /** 259 | * Returns the signature of the phar. 260 | * 261 | * This method does not use the extension to extract the phar's signature. 262 | * 263 | * @param string $path The phar file path. 264 | * 265 | * @return array The signature. 266 | */ 267 | public static function getSignature($path) 268 | { 269 | return Signature::create($path)->get(); 270 | } 271 | 272 | /** 273 | * Replaces the placeholders with their values. 274 | * 275 | * @param string $contents The contents. 276 | * 277 | * @return string The replaced contents. 278 | */ 279 | public function replaceValues($contents) 280 | { 281 | return str_replace( 282 | array_keys($this->values), 283 | array_values($this->values), 284 | $contents 285 | ); 286 | } 287 | 288 | /** 289 | * Sets the bootstrap loader stub using a file. 290 | * 291 | * @param string $file The file path. 292 | * @param boolean $replace Replace placeholders? 293 | * 294 | * @throws Exception\Exception 295 | * @throws FileException If the stub file could not be used. 296 | */ 297 | public function setStubUsingFile($file, $replace = false) 298 | { 299 | if (false === is_file($file)) { 300 | throw FileException::create( 301 | 'The file "%s" does not exist or is not a file.', 302 | $file 303 | ); 304 | } 305 | 306 | if (false === ($contents = @file_get_contents($file))) { 307 | throw FileException::lastError(); 308 | } 309 | 310 | if ($replace) { 311 | $contents = $this->replaceValues($contents); 312 | } 313 | 314 | $this->phar->setStub($contents); 315 | } 316 | 317 | /** 318 | * Sets the placeholder values. 319 | * 320 | * @param array $values The values. 321 | * 322 | * @throws Exception\Exception 323 | * @throws InvalidArgumentException If a non-scalar value is used. 324 | */ 325 | public function setValues(array $values) 326 | { 327 | foreach ($values as $value) { 328 | if (false === is_scalar($value)) { 329 | throw InvalidArgumentException::create( 330 | 'Non-scalar values (such as %s) are not supported.', 331 | gettype($value) 332 | ); 333 | } 334 | } 335 | 336 | $this->values = $values; 337 | } 338 | 339 | /** 340 | * Signs the Phar using a private key. 341 | * 342 | * @param string $key The private key. 343 | * @param string $password The private key password. 344 | * 345 | * @throws Exception\Exception 346 | * @throws OpenSslException If the "openssl" extension could not be used 347 | * or has generated an error. 348 | */ 349 | public function sign($key, $password = null) 350 | { 351 | OpenSslException::reset(); 352 | 353 | if (false === extension_loaded('openssl')) { 354 | // @codeCoverageIgnoreStart 355 | throw OpenSslException::create( 356 | 'The "openssl" extension is not available.' 357 | ); 358 | // @codeCoverageIgnoreEnd 359 | } 360 | 361 | if (false === ($resource = openssl_pkey_get_private($key, $password))) { 362 | // @codeCoverageIgnoreStart 363 | throw OpenSslException::lastError(); 364 | // @codeCoverageIgnoreEnd 365 | } 366 | 367 | if (false === openssl_pkey_export($resource, $private)) { 368 | // @codeCoverageIgnoreStart 369 | throw OpenSslException::lastError(); 370 | // @codeCoverageIgnoreEnd 371 | } 372 | 373 | if (false === ($details = openssl_pkey_get_details($resource))) { 374 | // @codeCoverageIgnoreStart 375 | throw OpenSslException::lastError(); 376 | // @codeCoverageIgnoreEnd 377 | } 378 | 379 | $this->phar->setSignatureAlgorithm(Phar::OPENSSL, $private); 380 | 381 | if (false === @file_put_contents($this->file . '.pubkey', $details['key'])) { 382 | throw FileException::lastError(); 383 | } 384 | } 385 | 386 | /** 387 | * Signs the Phar using a private key file. 388 | * 389 | * @param string $file The private key file name. 390 | * @param string $password The private key password. 391 | * 392 | * @throws Exception\Exception 393 | * @throws FileException If the private key file could not be read. 394 | */ 395 | public function signUsingFile($file, $password = null) 396 | { 397 | if (false === is_file($file)) { 398 | throw FileException::create( 399 | 'The file "%s" does not exist or is not a file.', 400 | $file 401 | ); 402 | } 403 | 404 | if (false === ($key = @file_get_contents($file))) { 405 | throw FileException::lastError(); 406 | } 407 | 408 | $this->sign($key, $password); 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/Compactor.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | abstract class Compactor implements CompactorInterface 11 | { 12 | /** 13 | * The list of supported file extensions. 14 | * 15 | * @var array 16 | */ 17 | protected $extensions; 18 | 19 | /** 20 | * Sets the list of supported file extensions. 21 | * 22 | * @param array $extensions The list. 23 | */ 24 | public function setExtensions(array $extensions) 25 | { 26 | $this->extensions = $extensions; 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function supports($file) 33 | { 34 | return in_array(pathinfo($file, PATHINFO_EXTENSION), $this->extensions); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/CompactorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface CompactorInterface 11 | { 12 | /** 13 | * Compacts the file contents. 14 | * 15 | * @param string $contents The contents. 16 | * 17 | * @return string The compacted file contents. 18 | */ 19 | public function compact($contents); 20 | 21 | /** 22 | * Checks if the file is supported. 23 | * 24 | * @param string $file The file name. 25 | * 26 | * @return boolean TRUE if it is supported, FALSE if not. 27 | */ 28 | public function supports($file); 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/Composer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Composer extends Php 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/Javascript.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Javascript extends Compactor 13 | { 14 | /** 15 | * The default list of supported file extensions. 16 | * 17 | * @var array 18 | */ 19 | protected $extensions = array('js'); 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function compact($contents) 25 | { 26 | try { 27 | return Minifier::minify($contents); 28 | } catch (\Exception $e) { 29 | 30 | return $contents; 31 | } 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public function supports($file) 38 | { 39 | if (!parent::supports($file)) { 40 | return false; 41 | } 42 | 43 | return !(substr($file, -7) == '.min.js'); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/Json.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Json extends Compactor 11 | { 12 | /** 13 | * The default list of supported file extensions. 14 | * 15 | * @var array 16 | */ 17 | protected $extensions = array('json'); 18 | 19 | /** 20 | * {@inheritDoc} 21 | */ 22 | public function compact($contents) 23 | { 24 | return json_encode(json_decode($contents)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Compactor/Php.php: -------------------------------------------------------------------------------- 1 | 14 | * @author Fabien Potencier 15 | * @author Jordi Boggiano 16 | */ 17 | class Php extends Compactor 18 | { 19 | /** 20 | * The annotation tokens converter. 21 | * 22 | * @var ToString 23 | */ 24 | private $converter; 25 | 26 | /** 27 | * The default list of supported file extensions. 28 | * 29 | * @var array 30 | */ 31 | protected $extensions = array('php'); 32 | 33 | /** 34 | * The annotations tokenizer. 35 | * 36 | * @var Tokenizer 37 | */ 38 | private $tokenizer; 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | public function compact($contents) 44 | { 45 | $output = ''; 46 | foreach (token_get_all($contents) as $token) { 47 | if (is_string($token)) { 48 | $output .= $token; 49 | } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { 50 | if ($this->tokenizer && (false !== strpos($token[1], '@'))) { 51 | $output .= $this->compactAnnotations($token[1]); 52 | } else { 53 | $output .= str_repeat("\n", substr_count($token[1], "\n")); 54 | } 55 | } elseif (T_WHITESPACE === $token[0]) { 56 | // reduce wide spaces 57 | $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); 58 | // normalize newlines to \n 59 | $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); 60 | // trim leading spaces 61 | $whitespace = preg_replace('{\n +}', "\n", $whitespace); 62 | $output .= $whitespace; 63 | } else { 64 | $output .= $token[1]; 65 | } 66 | } 67 | 68 | return $output; 69 | } 70 | 71 | /** 72 | * Sets the annotations tokenizer. 73 | * 74 | * @param Tokenizer $tokenizer The tokenizer. 75 | */ 76 | public function setTokenizer(Tokenizer $tokenizer) 77 | { 78 | if (null === $this->converter) { 79 | $this->converter = new ToString(); 80 | } 81 | 82 | $this->tokenizer = $tokenizer; 83 | } 84 | 85 | /** 86 | * Compacts the docblock and its annotations. 87 | * 88 | * @param string $docblock The docblock. 89 | * 90 | * @return string The compacted docblock. 91 | */ 92 | private function compactAnnotations($docblock) 93 | { 94 | $annotations = array(); 95 | $index = -1; 96 | $inside = 0; 97 | $tokens = $this->tokenizer->parse($docblock); 98 | 99 | if (empty($tokens)) { 100 | return str_repeat("\n", substr_count($docblock, "\n")); 101 | } 102 | 103 | foreach ($tokens as $token) { 104 | if ((0 === $inside) && (DocLexer::T_AT === $token[0])) { 105 | $index++; 106 | } elseif (DocLexer::T_OPEN_PARENTHESIS === $token[0]) { 107 | $inside++; 108 | } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token[0]) { 109 | $inside--; 110 | } 111 | 112 | if (!isset($annotations[$index])) { 113 | $annotations[$index] = array(); 114 | } 115 | 116 | $annotations[$index][] = $token; 117 | } 118 | 119 | $breaks = substr_count($docblock, "\n"); 120 | $docblock = "/**"; 121 | 122 | foreach ($annotations as $annotation) { 123 | $annotation = new Tokens($annotation); 124 | $docblock .= "\n" . $this->converter->convert($annotation); 125 | } 126 | 127 | $breaks -= count($annotations); 128 | 129 | if ($breaks > 0) { 130 | $docblock .= str_repeat("\n", $breaks - 1); 131 | $docblock .= "\n*/"; 132 | } else { 133 | $docblock .= ' */'; 134 | } 135 | 136 | return $docblock; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/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/Box/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface ExceptionInterface 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Exception/FileException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class InvalidArgumentException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Exception/OpenSslException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OpenSslException extends Exception 11 | { 12 | /** 13 | * Creates an exception for the last OpenSSL error. 14 | * 15 | * @return OpenSslException The exception. 16 | */ 17 | public static function lastError() 18 | { 19 | return new static(openssl_error_string()); 20 | } 21 | 22 | /** 23 | * Clears the error buffer, preventing unassociated error messages from 24 | * being used by the lastError() method. This is required for lsatError() 25 | * to function properly. If the clearing loop continues beyond a certain 26 | * number, a warning will be triggered before the loop is broken. 27 | * 28 | * @param integer $count The maximum number of rounds. 29 | */ 30 | public static function reset($count = 100) 31 | { 32 | $counter = 0; 33 | 34 | while (openssl_error_string()) { 35 | if ($count < ++$counter) { 36 | trigger_error( 37 | "The OpenSSL error clearing loop has exceeded $count rounds.", 38 | E_USER_WARNING 39 | ); 40 | 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Exception/SignatureException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class SignatureException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Exception/UnexpectedValueException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class UnexpectedValueException extends Exception 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Extract.php: -------------------------------------------------------------------------------- 1 | '); 16 | 17 | /** 18 | * The open-ended stub pattern. 19 | * 20 | * @var string 21 | */ 22 | define('BOX_EXTRACT_PATTERN_OPEN', "__HALT" . "_COMPILER(); ?>\r\n"); 23 | 24 | /** 25 | * Extracts a phar without the extension. 26 | * 27 | * This class is a rewrite of the `Extract_Phar` class that is included 28 | * in the default stub for all phars. The class is designed to work from 29 | * inside and outside of a phar. Unlike the original class, the stub 30 | * length must be specified. 31 | * 32 | * @author Kevin Herrera 33 | * 34 | * @link https://github.com/php/php-src/blob/master/ext/phar/shortarc.php 35 | */ 36 | class Extract 37 | { 38 | /** 39 | * The default stub pattern. 40 | * 41 | * @var string 42 | */ 43 | const PATTERN_DEFAULT = BOX_EXTRACT_PATTERN_DEFAULT; 44 | 45 | /** 46 | * The open-ended stub pattern. 47 | * 48 | * @var string 49 | */ 50 | const PATTERN_OPEN = BOX_EXTRACT_PATTERN_OPEN; 51 | 52 | /** 53 | * The gzip compression flag. 54 | * 55 | * @var integer 56 | */ 57 | const GZ = 0x1000; 58 | 59 | /** 60 | * The bzip2 compression flag. 61 | * 62 | * @var integer 63 | */ 64 | const BZ2 = 0x2000; 65 | 66 | /** 67 | * @var integer 68 | */ 69 | const MASK = 0x3000; 70 | 71 | /** 72 | * The phar file path to extract. 73 | * 74 | * @var string 75 | */ 76 | private $file; 77 | 78 | /** 79 | * The open file handle. 80 | * 81 | * @var resource 82 | */ 83 | private $handle; 84 | 85 | /** 86 | * The length of the stub in the phar. 87 | * 88 | * @var integer 89 | */ 90 | private $stub; 91 | 92 | /** 93 | * Sets the file to extract and the stub length. 94 | * 95 | * @param string $file The file path. 96 | * @param integer $stub The stub length. 97 | * 98 | * @throws InvalidArgumentException If the file does not exist. 99 | */ 100 | public function __construct($file, $stub) 101 | { 102 | if (!is_file($file)) { 103 | throw new InvalidArgumentException( 104 | sprintf( 105 | 'The path "%s" is not a file or does not exist.', 106 | $file 107 | ) 108 | ); 109 | } 110 | 111 | $this->file = $file; 112 | $this->stub = $stub; 113 | } 114 | 115 | /** 116 | * Finds the phar's stub length using the end pattern. 117 | * 118 | * A "pattern" is a sequence of characters that indicate the end of a 119 | * stub, and the beginning of a manifest. This determines the complete 120 | * size of a stub, and is used as an offset to begin parsing the data 121 | * contained in the phar's manifest. 122 | * 123 | * The stub generated included with the Box library uses what I like 124 | * to call an open-ended pattern. This pattern uses the function 125 | * "__HALT_COMPILER();" at the end, with no following whitespace or 126 | * closing PHP tag. By default, this method will use that pattern, 127 | * defined as `Extract::PATTERN_OPEN`. 128 | * 129 | * The Phar class generates its own default stub. The pattern for the 130 | * default stub is slightly different than the one used by Box. This 131 | * pattern is defined as `Extract::PATTERN_DEFAULT`. 132 | * 133 | * If you have used your own custom stub, you will need to specify its 134 | * pattern as the `$pattern` argument, if you cannot use either of the 135 | * pattern constants defined. 136 | * 137 | * @param string $file The phar file path. 138 | * @param string $pattern The stub end pattern. 139 | * 140 | * @return integer The stub length. 141 | * 142 | * @throws InvalidArgumentException If the pattern could not be found. 143 | * @throws RuntimeException If the phar could not be read. 144 | */ 145 | public static function findStubLength( 146 | $file, 147 | $pattern = self::PATTERN_OPEN 148 | ) { 149 | if (!($fp = fopen($file, 'rb'))) { 150 | throw new RuntimeException( 151 | sprintf( 152 | 'The phar "%s" could not be opened for reading.', 153 | $file 154 | ) 155 | ); 156 | } 157 | 158 | $stub = null; 159 | $offset = 0; 160 | $combo = str_split($pattern); 161 | 162 | while (!feof($fp)) { 163 | if (fgetc($fp) === $combo[$offset]) { 164 | $offset++; 165 | 166 | if (!isset($combo[$offset])) { 167 | $stub = ftell($fp); 168 | 169 | break; 170 | } 171 | } else { 172 | $offset = 0; 173 | } 174 | } 175 | 176 | fclose($fp); 177 | 178 | if (null === $stub) { 179 | throw new InvalidArgumentException( 180 | sprintf( 181 | 'The pattern could not be found in "%s".', 182 | $file 183 | ) 184 | ); 185 | } 186 | 187 | return $stub; 188 | } 189 | 190 | /** 191 | * Extracts the phar to the directory path. 192 | * 193 | * If no directory path is given, a temporary one will be generated and 194 | * returned. If a directory path is given, the returned directory path 195 | * will be the same. 196 | * 197 | * @param string $dir The directory to extract to. 198 | * 199 | * @return string The directory extracted to. 200 | * 201 | * @throws LengthException 202 | * @throws RuntimeException 203 | */ 204 | public function go($dir = null) 205 | { 206 | // set up the output directory 207 | if (null === $dir) { 208 | $dir = rtrim(sys_get_temp_dir(), '\\/') 209 | . DIRECTORY_SEPARATOR 210 | . 'pharextract' 211 | . DIRECTORY_SEPARATOR 212 | . basename($this->file, '.phar'); 213 | } else { 214 | $dir = realpath($dir); 215 | } 216 | 217 | // skip if already extracted 218 | $md5 = $dir . DIRECTORY_SEPARATOR . md5_file($this->file); 219 | 220 | if (file_exists($md5)) { 221 | return $dir; 222 | } 223 | 224 | if (!is_dir($dir)) { 225 | $this->createDir($dir); 226 | } 227 | 228 | // open the file and skip stub 229 | $this->open(); 230 | 231 | if (-1 === fseek($this->handle, $this->stub)) { 232 | throw new RuntimeException( 233 | sprintf( 234 | 'Could not seek to %d in the file "%s".', 235 | $this->stub, 236 | $this->file 237 | ) 238 | ); 239 | } 240 | 241 | // read the manifest 242 | $info = $this->readManifest(); 243 | 244 | if ($info['flags'] & self::GZ) { 245 | if (!function_exists('gzinflate')) { 246 | throw new RuntimeException( 247 | 'The zlib extension is (gzinflate()) is required for "%s.', 248 | $this->file 249 | ); 250 | } 251 | } 252 | 253 | if ($info['flags'] & self::BZ2) { 254 | if (!function_exists('bzdecompress')) { 255 | throw new RuntimeException( 256 | 'The bzip2 extension (bzdecompress()) is required for "%s".', 257 | $this->file 258 | ); 259 | } 260 | } 261 | 262 | self::purge($dir); 263 | $this->createDir($dir); 264 | $this->createFile($md5); 265 | 266 | foreach ($info['files'] as $info) { 267 | $path = $dir . DIRECTORY_SEPARATOR . $info['path']; 268 | $parent = dirname($path); 269 | 270 | if (!is_dir($parent)) { 271 | $this->createDir($parent); 272 | } 273 | 274 | if (preg_match('{/$}', $info['path'])) { 275 | $this->createDir($path, 0777, false); 276 | } else { 277 | $this->createFile( 278 | $path, 279 | $this->extractFile($info) 280 | ); 281 | } 282 | } 283 | 284 | return $dir; 285 | } 286 | 287 | /** 288 | * Recursively deletes the directory or file path. 289 | * 290 | * @param string $path The path to delete. 291 | * 292 | * @throws RuntimeException If the path could not be deleted. 293 | */ 294 | public static function purge($path) 295 | { 296 | if (is_dir($path)) { 297 | foreach (scandir($path) as $item) { 298 | if (('.' === $item) || ('..' === $item)) { 299 | continue; 300 | } 301 | 302 | self::purge($path . DIRECTORY_SEPARATOR . $item); 303 | } 304 | 305 | if (!rmdir($path)) { 306 | throw new RuntimeException( 307 | sprintf( 308 | 'The directory "%s" could not be deleted.', 309 | $path 310 | ) 311 | ); 312 | } 313 | } else { 314 | if (!unlink($path)) { 315 | throw new RuntimeException( 316 | sprintf( 317 | 'The file "%s" could not be deleted.', 318 | $path 319 | ) 320 | ); 321 | } 322 | } 323 | } 324 | 325 | /** 326 | * Creates a new directory. 327 | * 328 | * @param string $path The directory path. 329 | * @param integer $chmod The file mode. 330 | * @param boolean $recursive Recursively create path? 331 | * 332 | * @throws RuntimeException If the path could not be created. 333 | */ 334 | private function createDir($path, $chmod = 0777, $recursive = true) 335 | { 336 | if (!mkdir($path, $chmod, $recursive)) { 337 | throw new RuntimeException( 338 | sprintf( 339 | 'The directory path "%s" could not be created.', 340 | $path 341 | ) 342 | ); 343 | } 344 | } 345 | 346 | /** 347 | * Creates a new file. 348 | * 349 | * @param string $path The file path. 350 | * @param string $contents The file contents. 351 | * @param integer $mode The file mode. 352 | * 353 | * @throws RuntimeException If the file could not be created. 354 | */ 355 | private function createFile($path, $contents = '', $mode = 0666) 356 | { 357 | if (false === file_put_contents($path, $contents)) { 358 | throw new RuntimeException( 359 | sprintf( 360 | 'The file "%s" could not be written.', 361 | $path 362 | ) 363 | ); 364 | } 365 | 366 | if (!chmod($path, $mode)) { 367 | throw new RuntimeException( 368 | sprintf( 369 | 'The file "%s" could not be chmodded to %o.', 370 | $path, 371 | $mode 372 | ) 373 | ); 374 | } 375 | } 376 | 377 | /** 378 | * Extracts a single file from the phar. 379 | * 380 | * @param array $info The file information. 381 | * 382 | * @return string The file data. 383 | * 384 | * @throws RuntimeException If the file could not be extracted. 385 | * @throws UnexpectedValueException If the crc32 checksum does not 386 | * match the expected value. 387 | */ 388 | private function extractFile($info) 389 | { 390 | if (0 === $info['size']) { 391 | return ''; 392 | } 393 | 394 | $data = $this->read($info['compressed_size']); 395 | 396 | if ($info['flags'] & self::GZ) { 397 | if (false === ($data = gzinflate($data))) { 398 | throw new RuntimeException( 399 | sprintf( 400 | 'The "%s" file could not be inflated (gzip) from "%s".', 401 | $info['path'], 402 | $this->file 403 | ) 404 | ); 405 | } 406 | } elseif ($info['flags'] & self::BZ2) { 407 | if (false === ($data = bzdecompress($data))) { 408 | throw new RuntimeException( 409 | sprintf( 410 | 'The "%s" file could not be inflated (bzip2) from "%s".', 411 | $info['path'], 412 | $this->file 413 | ) 414 | ); 415 | } 416 | } 417 | 418 | if (($actual = strlen($data)) !== $info['size']) { 419 | throw new UnexpectedValueException( 420 | sprintf( 421 | 'The size of "%s" (%d) did not match what was expected (%d) in "%s".', 422 | $info['path'], 423 | $actual, 424 | $info['size'], 425 | $this->file 426 | ) 427 | ); 428 | } 429 | 430 | $crc32 = sprintf('%u', crc32($data) & 0xffffffff); 431 | 432 | if ($info['crc32'] != $crc32) { 433 | throw new UnexpectedValueException( 434 | sprintf( 435 | 'The crc32 checksum (%s) for "%s" did not match what was expected (%s) in "%s".', 436 | $crc32, 437 | $info['path'], 438 | $info['crc32'], 439 | $this->file 440 | ) 441 | ); 442 | } 443 | 444 | return $data; 445 | } 446 | 447 | /** 448 | * Opens the file for reading. 449 | * 450 | * @throws RuntimeException If the file could not be opened. 451 | */ 452 | private function open() 453 | { 454 | if (null === ($this->handle = fopen($this->file, 'rb'))) { 455 | $this->handle = null; 456 | 457 | throw new RuntimeException( 458 | sprintf( 459 | 'The file "%s" could not be opened for reading.', 460 | $this->file 461 | ) 462 | ); 463 | } 464 | } 465 | 466 | /** 467 | * Reads the number of bytes from the file. 468 | * 469 | * @param integer $bytes The number of bytes. 470 | * 471 | * @return string The binary string read. 472 | * 473 | * @throws RuntimeException If the read fails. 474 | */ 475 | private function read($bytes) 476 | { 477 | $read = ''; 478 | $total = $bytes; 479 | 480 | while (!feof($this->handle) && $bytes) { 481 | if (false === ($chunk = fread($this->handle, $bytes))) { 482 | throw new RuntimeException( 483 | sprintf( 484 | 'Could not read %d bytes from "%s".', 485 | $bytes, 486 | $this->file 487 | ) 488 | ); 489 | } 490 | 491 | $read .= $chunk; 492 | $bytes -= strlen($chunk); 493 | } 494 | 495 | if (($actual = strlen($read)) !== $total) { 496 | throw new RuntimeException( 497 | sprintf( 498 | 'Only read %d of %d in "%s".', 499 | $actual, 500 | $total, 501 | $this->file 502 | ) 503 | ); 504 | } 505 | 506 | return $read; 507 | } 508 | 509 | /** 510 | * Reads and unpacks the manifest data from the phar. 511 | * 512 | * @return array The manifest. 513 | */ 514 | private function readManifest() 515 | { 516 | $size = unpack('V', $this->read(4)); 517 | $size = $size[1]; 518 | 519 | $raw = $this->read($size); 520 | 521 | // ++ start skip: API version, global flags, alias, and metadata 522 | $count = unpack('V', substr($raw, 0, 4)); 523 | $count = $count[1]; 524 | 525 | $aliasSize = unpack('V', substr($raw, 10, 4)); 526 | $aliasSize = $aliasSize[1]; 527 | $raw = substr($raw, 14 + $aliasSize); 528 | 529 | $metaSize = unpack('V', substr($raw, 0, 4)); 530 | $metaSize = $metaSize[1]; 531 | 532 | $offset = 0; 533 | $start = 4 + $metaSize; 534 | // -- end skip 535 | 536 | $manifest = array( 537 | 'files' => array(), 538 | 'flags' => 0, 539 | ); 540 | 541 | for ($i = 0; $i < $count; $i++) { 542 | $length = unpack('V', substr($raw, $start, 4)); 543 | $length = $length[1]; 544 | $start += 4; 545 | 546 | $path = substr($raw, $start, $length); 547 | $start += $length; 548 | 549 | $file = unpack( 550 | 'Vsize/Vtimestamp/Vcompressed_size/Vcrc32/Vflags/Vmetadata_length', 551 | substr($raw, $start, 24) 552 | ); 553 | 554 | $file['path'] = $path; 555 | $file['crc32'] = sprintf('%u', $file['crc32'] & 0xffffffff); 556 | $file['offset'] = $offset; 557 | 558 | $offset += $file['compressed_size']; 559 | $start += 24 + $file['metadata_length']; 560 | 561 | $manifest['flags'] |= $file['flags'] & self::MASK; 562 | 563 | $manifest['files'][] = $file; 564 | } 565 | 566 | return $manifest; 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Signature 21 | { 22 | /** 23 | * The phar file path. 24 | * 25 | * @var string 26 | */ 27 | private $file; 28 | 29 | /** 30 | * The file handle. 31 | * 32 | * @var resource 33 | */ 34 | private $handle; 35 | 36 | /** 37 | * The size of the file. 38 | * 39 | * @var integer 40 | */ 41 | private $size; 42 | 43 | /** 44 | * The recognized signature types. 45 | * 46 | * @var array 47 | */ 48 | private static $types = array( 49 | array( 50 | 'name' => 'MD5', 51 | 'flag' => 0x01, 52 | 'size' => 16, 53 | 'class' => 'Herrera\\Box\\Signature\\Hash' 54 | ), 55 | array( 56 | 'name' => 'SHA-1', 57 | 'flag' => 0x02, 58 | 'size' => 20, 59 | 'class' => 'Herrera\\Box\\Signature\\Hash' 60 | ), 61 | array( 62 | 'name' => 'SHA-256', 63 | 'flag' => 0x03, 64 | 'size' => 32, 65 | 'class' => 'Herrera\\Box\\Signature\\Hash' 66 | ), 67 | array( 68 | 'name' => 'SHA-512', 69 | 'flag' => 0x04, 70 | 'size' => 64, 71 | 'class' => 'Herrera\\Box\\Signature\\Hash' 72 | ), 73 | array( 74 | 'name' => 'OpenSSL', 75 | 'flag' => 0x10, 76 | 'size' => null, 77 | 'class' => 'Herrera\\Box\\Signature\\PublicKeyDelegate' 78 | ), 79 | ); 80 | 81 | /** 82 | * Sets the phar file path. 83 | * 84 | * @param string $path The phar file path. 85 | * 86 | * @throws Exception 87 | * @throws FileException If the file does not exist. 88 | */ 89 | public function __construct($path) 90 | { 91 | if (!is_file($path)) { 92 | throw FileException::create( 93 | 'The path "%s" does not exist or is not a file.', 94 | $path 95 | ); 96 | } 97 | 98 | $this->file = realpath($path); 99 | 100 | if (false === ($this->size = @filesize($path))) { 101 | throw FileException::lastError(); 102 | } 103 | } 104 | 105 | /** 106 | * Closes the open file handle. 107 | */ 108 | public function __destruct() 109 | { 110 | $this->close(); 111 | } 112 | 113 | /** 114 | * Creates a new instance of Signature. 115 | * 116 | * @param string $path The phar file path. 117 | * 118 | * @return Signature The new instance. 119 | */ 120 | public static function create($path) 121 | { 122 | return new self($path); 123 | } 124 | 125 | /** 126 | * Returns the signature for the phar. 127 | * 128 | * The value returned is identical to that of `Phar->getSignature()`. If 129 | * $required is not given, it will default to the `phar.require_hash` 130 | * current value. 131 | * 132 | * @param boolean $required Is the signature required? 133 | * 134 | * @return array The signature. 135 | * 136 | * @throws PharException If the phar is not valid. 137 | */ 138 | public function get($required = null) 139 | { 140 | if (null === $required) { 141 | $required = (bool) ini_get('phar.require_hash'); 142 | } 143 | 144 | $this->seek(-4, SEEK_END); 145 | 146 | if ('GBMB' !== $this->read(4)) { 147 | if ($required) { 148 | throw new PharException( 149 | sprintf( 150 | 'The phar "%s" is not signed.', 151 | $this->file 152 | ) 153 | ); 154 | } 155 | 156 | return null; 157 | } 158 | 159 | $this->seek(-8, SEEK_END); 160 | 161 | $flag = unpack('V', $this->read(4)); 162 | $flag = $flag[1]; 163 | 164 | foreach (self::$types as $type) { 165 | if ($flag === $type['flag']) { 166 | break; 167 | } 168 | 169 | unset($type); 170 | } 171 | 172 | if (!isset($type)) { 173 | throw new PharException( 174 | sprintf( 175 | 'The signature type (%x) is not recognized for the phar "%s".', 176 | $flag, 177 | $this->file 178 | ) 179 | ); 180 | } 181 | 182 | $offset = -8; 183 | 184 | if (0x10 === $type['flag']) { 185 | $offset = -12; 186 | 187 | $this->seek(-12, SEEK_END); 188 | 189 | $type['size'] = unpack('V', $this->read(4)); 190 | $type['size'] = $type['size'][1]; 191 | } 192 | 193 | $this->seek($offset - $type['size'], SEEK_END); 194 | 195 | $hash = $this->read($type['size']); 196 | $hash = unpack('H*', $hash); 197 | 198 | return array( 199 | 'hash_type' => $type['name'], 200 | 'hash' => strtoupper($hash[1]) 201 | ); 202 | } 203 | 204 | /** 205 | * Verifies the signature of the phar. 206 | * 207 | * @return boolean TRUE if verified, FALSE if not. 208 | * 209 | * @throws Exception 210 | * @throws FileException If the private key could not be read. 211 | * @throws OpenSslException If there is an OpenSSL error. 212 | */ 213 | public function verify() 214 | { 215 | $signature = $this->get(); 216 | 217 | $size = $this->size; 218 | $type = null; 219 | 220 | foreach (self::$types as $type) { 221 | if ($type['name'] === $signature['hash_type']) { 222 | if (0x10 === $type['flag']) { 223 | $this->seek(-12, SEEK_END); 224 | 225 | $less = $this->read(4); 226 | $less = unpack('V', $less); 227 | $less = $less[1]; 228 | 229 | $size -= 12 + $less; 230 | } else { 231 | $size -= 8 + $type['size']; 232 | } 233 | 234 | break; 235 | } 236 | } 237 | 238 | $this->seek(0); 239 | 240 | /** @var $verify VerifyInterface */ 241 | $verify = new $type['class'](); 242 | $verify->init($type['name'], $this->file); 243 | 244 | $buffer = 64; 245 | 246 | while (0 < $size) { 247 | if ($size < $buffer) { 248 | $buffer = $size; 249 | $size = 0; 250 | } 251 | 252 | $verify->update($this->read($buffer)); 253 | 254 | $size -= $buffer; 255 | } 256 | 257 | return $verify->verify($signature['hash']); 258 | } 259 | 260 | /** 261 | * Closes the open file handle. 262 | */ 263 | private function close() 264 | { 265 | if ($this->handle) { 266 | @fclose($this->handle); 267 | 268 | $this->handle = null; 269 | } 270 | } 271 | 272 | /** 273 | * Returns the file handle. 274 | * 275 | * If the file handle is not opened, it will be automatically opened. 276 | * 277 | * @return resource The file handle. 278 | * 279 | * @throws Exception 280 | * @throws FileException If the file could not be opened. 281 | */ 282 | private function handle() 283 | { 284 | if (!$this->handle) { 285 | if (!($this->handle = @fopen($this->file, 'rb'))) { 286 | throw FileException::lastError(); 287 | } 288 | } 289 | 290 | return $this->handle; 291 | } 292 | 293 | /** 294 | * Reads a number of bytes from the file. 295 | * 296 | * @param integer $bytes The number of bytes. 297 | * 298 | * @return string The read bytes. 299 | * 300 | * @throws Exception 301 | * @throws FileException If the file could not be read. 302 | */ 303 | private function read($bytes) 304 | { 305 | if (false === ($read = @fread($this->handle(), $bytes))) { 306 | throw FileException::lastError(); 307 | } 308 | 309 | if (($actual = strlen($read)) !== $bytes) { 310 | throw FileException::create( 311 | 'Only read %d of %d bytes from "%s".', 312 | $actual, 313 | $bytes, 314 | $this->file 315 | ); 316 | } 317 | 318 | return $read; 319 | } 320 | 321 | /** 322 | * Seeks to a specific point in the file. 323 | * 324 | * @param integer $offset The offset to seek. 325 | * @param integer $whence The direction. 326 | * 327 | * @throws Exception 328 | * @throws FileException If the file could not be seeked. 329 | */ 330 | private function seek($offset, $whence = SEEK_SET) 331 | { 332 | if (-1 === @fseek($this->handle(), $offset, $whence)) { 333 | throw FileException::lastError(); 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/AbstractBufferedHash.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | abstract class AbstractBufferedHash implements VerifyInterface 11 | { 12 | /** 13 | * The buffered data. 14 | * 15 | * @var string 16 | */ 17 | private $data; 18 | 19 | /** 20 | * @see VerifyInterface::update 21 | */ 22 | public function update($data) 23 | { 24 | $this->data .= $data; 25 | } 26 | 27 | /** 28 | * Returns the buffered data. 29 | * 30 | * @return string The data. 31 | */ 32 | protected function getData() 33 | { 34 | return $this->data; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/AbstractPublicKey.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | abstract class AbstractPublicKey extends AbstractBufferedHash 13 | { 14 | /** 15 | * The private key. 16 | * 17 | * @var string 18 | */ 19 | private $key; 20 | 21 | /** 22 | * @see VerifyInterface::init 23 | */ 24 | public function init($algorithm, $path) 25 | { 26 | if (false === ($this->key = @file_get_contents($path . '.pubkey'))) { 27 | throw FileException::lastError(); 28 | } 29 | } 30 | 31 | /** 32 | * Returns the private key. 33 | * 34 | * @return string The private key. 35 | */ 36 | protected function getKey() 37 | { 38 | return $this->key; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/Hash.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Hash implements VerifyInterface 13 | { 14 | /** 15 | * The hash context. 16 | * 17 | * @var resource 18 | */ 19 | private $context; 20 | 21 | /** 22 | * @see VerifyInterface::init 23 | */ 24 | public function init($algorithm, $path) 25 | { 26 | $algorithm = strtolower( 27 | preg_replace( 28 | '/[^A-Za-z0-9]+/', 29 | '', 30 | $algorithm 31 | ) 32 | ); 33 | 34 | if (false === ($this->context = @hash_init($algorithm))) { 35 | $this->context = null; 36 | 37 | throw SignatureException::lastError(); 38 | } 39 | } 40 | 41 | /** 42 | * @see VerifyInterface::update 43 | */ 44 | public function update($data) 45 | { 46 | hash_update($this->context, $data); 47 | } 48 | 49 | /** 50 | * @see VerifyInterface::verify 51 | */ 52 | public function verify($signature) 53 | { 54 | return ($signature === strtoupper(hash_final($this->context))); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/OpenSsl.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class OpenSsl extends AbstractPublicKey 13 | { 14 | /** 15 | * @see VerifyInterface::verify 16 | */ 17 | public function verify($signature) 18 | { 19 | OpenSslException::reset(); 20 | 21 | ob_start(); 22 | 23 | $result = openssl_verify( 24 | $this->getData(), 25 | @pack('H*', $signature), 26 | $this->getKey() 27 | ); 28 | 29 | $error = trim(ob_get_clean()); 30 | 31 | if (-1 === $result) { 32 | throw OpenSslException::lastError(); 33 | } elseif (!empty($error)) { 34 | throw new OpenSslException($error); 35 | } 36 | 37 | return (1 === $result); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/PhpSecLib.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PhpSecLib extends AbstractPublicKey 13 | { 14 | /** 15 | * @see VerifyInterface::verify 16 | */ 17 | public function verify($signature) 18 | { 19 | $rsa = new Crypt_RSA(); 20 | $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); 21 | $rsa->loadKey($this->getKey()); 22 | 23 | return $rsa->verify($this->getData(), pack('H*', $signature)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/PublicKeyDelegate.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class PublicKeyDelegate implements VerifyInterface 13 | { 14 | /** 15 | * The hashing class. 16 | * 17 | * @var VerifyInterface 18 | */ 19 | private $hash; 20 | 21 | /** 22 | * Selects the appropriate hashing class. 23 | */ 24 | public function __construct() 25 | { 26 | if (extension_loaded('openssl')) { 27 | $this->hash = new OpenSsl(); 28 | } elseif (class_exists('Crypt_RSA')) { 29 | $this->hash = new PhpSeclib(); 30 | } else { 31 | throw SignatureException::create( 32 | 'The "openssl" extension and "phpseclib" libraries are not available.' 33 | ); 34 | } 35 | } 36 | 37 | /** 38 | * @see VerifyInterface::init 39 | */ 40 | public function init($algorithm, $path) 41 | { 42 | $this->hash->init($algorithm, $path); 43 | } 44 | 45 | /** 46 | * @see VerifyInterface::update 47 | */ 48 | public function update($data) 49 | { 50 | $this->hash->update($data); 51 | } 52 | 53 | /** 54 | * @see VerifyInterface::verify 55 | */ 56 | public function verify($signature) 57 | { 58 | return $this->hash->verify($signature); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/Signature/VerifyInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface VerifyInterface 14 | { 15 | /** 16 | * Initializes the hash. 17 | * 18 | * @param string $algorithm The algorithm to use. 19 | * @param string $path The path to the phar. 20 | * 21 | * @throws Exception 22 | * @throws SignatureException If the hash could not be initialized. 23 | */ 24 | public function init($algorithm, $path); 25 | 26 | /** 27 | * Updates the hash with more data. 28 | * 29 | * @param string $data The data. 30 | * 31 | * @throws Exception 32 | * @throws SignatureException If the hash could not be updated. 33 | */ 34 | public function update($data); 35 | 36 | /** 37 | * Verifies the final hash against the given signature. 38 | * 39 | * @param string $signature The signature. 40 | * 41 | * @return boolean TRUE if verified, FALSE if not. 42 | * 43 | * @throws Exception 44 | * @throws SignatureException If the hash could not be verified. 45 | */ 46 | public function verify($signature); 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/Herrera/Box/StubGenerator.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StubGenerator 14 | { 15 | /** 16 | * The list of server variables that are allowed to be modified. 17 | * 18 | * @var array 19 | */ 20 | private static $allowedMung = array( 21 | 'PHP_SELF', 22 | 'REQUEST_URI', 23 | 'SCRIPT_FILENAME', 24 | 'SCRIPT_NAME' 25 | ); 26 | 27 | /** 28 | * The alias to be used in "phar://" URLs. 29 | * 30 | * @var string 31 | */ 32 | private $alias; 33 | 34 | /** 35 | * The top header comment banner text. 36 | * 37 | * @var string. 38 | */ 39 | private $banner = 'Generated by Box. 40 | 41 | @link https://github.com/herrera-io/php-box/'; 42 | 43 | /** 44 | * Embed the Extract class in the stub? 45 | * 46 | * @var boolean 47 | */ 48 | private $extract = false; 49 | 50 | /** 51 | * The processed extract code. 52 | * 53 | * @var array 54 | */ 55 | private $extractCode = array(); 56 | 57 | /** 58 | * Force the use of the Extract class? 59 | * 60 | * @var boolean 61 | */ 62 | private $extractForce = false; 63 | 64 | /** 65 | * The location within the Phar of index script. 66 | * 67 | * @var string 68 | */ 69 | private $index; 70 | 71 | /** 72 | * Use the Phar::interceptFileFuncs() method? 73 | * 74 | * @var boolean 75 | */ 76 | private $intercept = false; 77 | 78 | /** 79 | * The map for file extensions and their mimetypes. 80 | * 81 | * @var array 82 | */ 83 | private $mimetypes = array(); 84 | 85 | /** 86 | * The list of server variables to modify. 87 | * 88 | * @var array 89 | */ 90 | private $mung = array(); 91 | 92 | /** 93 | * The location of the script to run when a file is not found. 94 | * 95 | * @var string 96 | */ 97 | private $notFound; 98 | 99 | /** 100 | * The rewrite function. 101 | * 102 | * @var string 103 | */ 104 | private $rewrite; 105 | 106 | /** 107 | * The shebang line. 108 | * 109 | * @var string 110 | */ 111 | private $shebang = '#!/usr/bin/env php'; 112 | 113 | /** 114 | * Use Phar::webPhar() instead of Phar::mapPhar()? 115 | * 116 | * @var boolean 117 | */ 118 | private $web = false; 119 | 120 | /** 121 | * Sets the alias to be used in "phar://" URLs. 122 | * 123 | * @param string $alias The alias. 124 | * 125 | * @return StubGenerator The stub generator. 126 | */ 127 | public function alias($alias) 128 | { 129 | $this->alias = $alias; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Sets the top header comment banner text. 136 | * 137 | * @param string $banner The banner text. 138 | * 139 | * @return StubGenerator The stub generator. 140 | */ 141 | public function banner($banner) 142 | { 143 | $this->banner = $banner; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Creates a new instance of the stub generator. 150 | * 151 | * @return StubGenerator The stub generator. 152 | */ 153 | public static function create() 154 | { 155 | return new static(); 156 | } 157 | 158 | /** 159 | * Embed the Extract class in the stub? 160 | * 161 | * @param boolean $extract Embed the class? 162 | * @param boolean $force Force the use of the class? 163 | * 164 | * @return StubGenerator The stub generator. 165 | */ 166 | public function extract($extract, $force = false) 167 | { 168 | $this->extract = $extract; 169 | $this->extractForce = $force; 170 | 171 | if ($extract) { 172 | $this->extractCode = array( 173 | 'constants' => array(), 174 | 'class' => array(), 175 | ); 176 | 177 | $compactor = new Php(); 178 | $code = file_get_contents(__DIR__ . '/Extract.php'); 179 | $code = $compactor->compact($code); 180 | $code = preg_replace('/\n+/', "\n", $code); 181 | $code = explode("\n", $code); 182 | $code = array_slice($code, 2); 183 | 184 | foreach ($code as $i => $line) { 185 | if ((0 === strpos($line, 'use')) 186 | && (false === strpos($line, '\\')) 187 | ) { 188 | unset($code[$i]); 189 | } elseif (0 === strpos($line, 'define')) { 190 | $this->extractCode['constants'][] = $line; 191 | } else { 192 | $this->extractCode['class'][] = $line; 193 | } 194 | } 195 | } 196 | 197 | return $this; 198 | } 199 | 200 | /** 201 | * Sets location within the Phar of index script. 202 | * 203 | * @param string $index The index file. 204 | * 205 | * @return StubGenerator The stub generator. 206 | */ 207 | public function index($index) 208 | { 209 | $this->index = $index; 210 | 211 | return $this; 212 | } 213 | 214 | /** 215 | * Use the Phar::interceptFileFuncs() method in the stub? 216 | * 217 | * @param boolean $intercept Use interceptFileFuncs()? 218 | * 219 | * @return StubGenerator The stub generator. 220 | */ 221 | public function intercept($intercept) 222 | { 223 | $this->intercept = $intercept; 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * Generates the stub. 230 | * 231 | * @return string The stub. 232 | */ 233 | public function generate() 234 | { 235 | $stub = array(); 236 | 237 | if ('' !== $this->shebang) { 238 | $stub[] = $this->shebang; 239 | } 240 | 241 | $stub[] = 'banner) { 244 | $stub[] = $this->getBanner(); 245 | } 246 | 247 | if ($this->extract) { 248 | $stub[] = join("\n", $this->extractCode['constants']); 249 | 250 | if ($this->extractForce) { 251 | $stub = array_merge($stub, $this->getExtractSections()); 252 | } 253 | } 254 | 255 | $stub = array_merge($stub, $this->getPharSections()); 256 | 257 | if ($this->extract) { 258 | if ($this->extractForce) { 259 | if ($this->index && !$this->web) { 260 | $stub[] = "require \"\$dir/{$this->index}\";"; 261 | } 262 | } else { 263 | end($stub); 264 | 265 | $stub[key($stub)] .= ' else {'; 266 | 267 | $stub = array_merge($stub, $this->getExtractSections()); 268 | 269 | if ($this->index) { 270 | $stub[] = "require \"\$dir/{$this->index}\";"; 271 | } 272 | 273 | $stub[] = '}'; 274 | } 275 | 276 | $stub[] = join("\n", $this->extractCode['class']); 277 | } 278 | 279 | $stub[] = "__HALT_COMPILER();"; 280 | 281 | return join("\n", $stub); 282 | } 283 | 284 | /** 285 | * Sets the map for file extensions and their mimetypes. 286 | * 287 | * @param array $mimetypes The map. 288 | * 289 | * @return StubGenerator The stub generator. 290 | */ 291 | public function mimetypes(array $mimetypes) 292 | { 293 | $this->mimetypes = $mimetypes; 294 | 295 | return $this; 296 | } 297 | 298 | /** 299 | * Sets the list of server variables to modify. 300 | * 301 | * @param array $list The list. 302 | * 303 | * @return StubGenerator The stub generator. 304 | * 305 | * @throws Exception\Exception 306 | * @throws InvalidArgumentException If the list contains an invalid value. 307 | */ 308 | public function mung(array $list) 309 | { 310 | foreach ($list as $value) { 311 | if (false === in_array($value, self::$allowedMung)) { 312 | throw InvalidArgumentException::create( 313 | 'The $_SERVER variable "%s" is not allowed.', 314 | $value 315 | ); 316 | } 317 | } 318 | 319 | $this->mung = $list; 320 | 321 | return $this; 322 | } 323 | 324 | /** 325 | * Sets the location of the script to run when a file is not found. 326 | * 327 | * @param string $script The script. 328 | * 329 | * @return StubGenerator The stub generator. 330 | */ 331 | public function notFound($script) 332 | { 333 | $this->notFound = $script; 334 | 335 | return $this; 336 | } 337 | 338 | /** 339 | * Sets the rewrite function. 340 | * 341 | * @param string $function The function. 342 | * 343 | * @return StubGenerator The stub generator. 344 | */ 345 | public function rewrite($function) 346 | { 347 | $this->rewrite = $function; 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * Sets the shebang line. 354 | * 355 | * @param string $shebang The shebang line. 356 | * 357 | * @return StubGenerator The stub generator. 358 | */ 359 | public function shebang($shebang) 360 | { 361 | $this->shebang = $shebang; 362 | 363 | return $this; 364 | } 365 | 366 | /** 367 | * Use Phar::webPhar() instead of Phar::mapPhar()? 368 | * 369 | * @param boolean $web Use Phar::webPhar()? 370 | * 371 | * @return StubGenerator The stub generator. 372 | */ 373 | public function web($web) 374 | { 375 | $this->web = $web; 376 | 377 | return $this; 378 | } 379 | 380 | /** 381 | * Escapes an argument so it can be written as a string in a call. 382 | * 383 | * @param string $arg The argument. 384 | * @param string $quote The quote. 385 | * 386 | * @return string The escaped argument. 387 | */ 388 | private function arg($arg, $quote = "'") 389 | { 390 | return $quote . addcslashes($arg, $quote) . $quote; 391 | } 392 | 393 | /** 394 | * Returns the alias map. 395 | * 396 | * @return string The alias map. 397 | */ 398 | private function getAlias() 399 | { 400 | $stub = ''; 401 | $prefix = ''; 402 | 403 | if ($this->extractForce) { 404 | $prefix = '$dir/'; 405 | } 406 | 407 | if ($this->web) { 408 | $stub .= 'Phar::webPhar(' . $this->arg($this->alias); 409 | 410 | if ($this->index) { 411 | $stub .= ', ' . $this->arg($prefix . $this->index, '"'); 412 | 413 | if ($this->notFound) { 414 | $stub .= ', ' . $this->arg($prefix . $this->notFound, '"'); 415 | 416 | if ($this->mimetypes) { 417 | $stub .= ', ' . var_export( 418 | $this->mimetypes, 419 | true 420 | ); 421 | 422 | if ($this->rewrite) { 423 | $stub .= ', ' . $this->arg($this->rewrite); 424 | } 425 | } 426 | } 427 | } 428 | 429 | $stub .= ');'; 430 | } else { 431 | $stub .= 'Phar::mapPhar(' . $this->arg($this->alias) . ');'; 432 | } 433 | 434 | return $stub; 435 | } 436 | 437 | /** 438 | * Returns the banner after it has been processed. 439 | * 440 | * @return string The processed banner. 441 | */ 442 | private function getBanner() 443 | { 444 | $banner = "/**\n * "; 445 | $banner .= str_replace( 446 | " \n", 447 | "\n", 448 | str_replace("\n", "\n * ", $this->banner) 449 | ); 450 | 451 | $banner .= "\n */"; 452 | 453 | return $banner; 454 | } 455 | 456 | /** 457 | * Returns the self extracting sections of the stub. 458 | * 459 | * @return array The stub sections. 460 | */ 461 | private function getExtractSections() 462 | { 463 | return array( 464 | '$extract = new Extract(__FILE__, Extract::findStubLength(__FILE__));', 465 | '$dir = $extract->go();', 466 | 'set_include_path($dir . PATH_SEPARATOR . get_include_path());', 467 | ); 468 | } 469 | 470 | /** 471 | * Returns the sections of the stub that use the Phar class. 472 | * 473 | * @return array The stub sections. 474 | */ 475 | private function getPharSections() 476 | { 477 | $stub = array( 478 | 'if (class_exists(\'Phar\')) {', 479 | $this->getAlias(), 480 | ); 481 | 482 | if ($this->intercept) { 483 | $stub[] = "Phar::interceptFileFuncs();"; 484 | } 485 | 486 | if ($this->mung) { 487 | $stub[] = 'Phar::mungServer(' . var_export($this->mung, true) . ");"; 488 | } 489 | 490 | if ($this->index && !$this->web && !$this->extractForce) { 491 | $stub[] = "require 'phar://' . __FILE__ . '/{$this->index}';"; 492 | } 493 | 494 | $stub[] = '}'; 495 | 496 | return $stub; 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/BoxTest.php: -------------------------------------------------------------------------------- 1 | box->addCompactor($compactor); 68 | 69 | $this->assertTrue( 70 | $this->getPropertyValue($this->box, 'compactors') 71 | ->contains($compactor) 72 | ); 73 | } 74 | 75 | public function testAddFile() 76 | { 77 | $file = $this->createFile(); 78 | 79 | file_put_contents($file, 'test'); 80 | 81 | $this->box->addFile($file, 'test/test.php'); 82 | 83 | $this->assertEquals( 84 | 'test', 85 | file_get_contents('phar://test.phar/test/test.php') 86 | ); 87 | } 88 | 89 | public function testAddFileNotExist() 90 | { 91 | $this->setExpectedException( 92 | 'Herrera\\Box\\Exception\\FileException', 93 | 'The file "/does/not/exist" does not exist or is not a file.' 94 | ); 95 | 96 | $this->box->addFile('/does/not/exist'); 97 | } 98 | 99 | public function testAddFileReadError() 100 | { 101 | vfsStreamWrapper::setRoot($root = vfsStream::newDirectory('test')); 102 | 103 | $root->addChild(vfsStream::newFile('test.php', 0000)); 104 | 105 | $this->setExpectedException( 106 | 'Herrera\\Box\\Exception\\FileException', 107 | 'failed to open stream' 108 | ); 109 | 110 | $this->box->addFile('vfs://test/test.php'); 111 | } 112 | 113 | public function testAddFromString() 114 | { 115 | $original = <<box->addCompactor(new Php()); 150 | $this->box->setValues( 151 | array( 152 | '@thing@' => 'MyClass', 153 | '@other_thing@' => 'myMethod' 154 | ) 155 | ); 156 | 157 | $this->box->addFromString('test/test.php', $original); 158 | 159 | $this->assertEquals( 160 | $expected, 161 | file_get_contents('phar://test.phar/test/test.php') 162 | ); 163 | } 164 | 165 | public function testBuildFromDirectory() 166 | { 167 | mkdir('test/sub', 0755, true); 168 | touch('test/sub.txt'); 169 | 170 | file_put_contents( 171 | 'test/sub/test.php', 172 | 'box->setValues(array('@name@' => 'world')); 176 | $this->box->buildFromDirectory($this->cwd, '/\.php$/'); 177 | 178 | $this->assertFalse(isset($this->phar['test/sub.txt'])); 179 | $this->assertEquals( 180 | 'cwd, 197 | FilesystemIterator::KEY_AS_PATHNAME 198 | | FilesystemIterator::CURRENT_AS_FILEINFO 199 | | FilesystemIterator::SKIP_DOTS 200 | ) 201 | ); 202 | 203 | $this->box->setValues(array('@name@' => 'world')); 204 | $this->box->buildFromIterator($iterator, $this->cwd); 205 | 206 | $this->assertEquals( 207 | 'box->buildFromIterator( 224 | new ArrayIterator( 225 | array( 226 | 'object' => new SplFileInfo($this->cwd . '/object'), 227 | 'string' => $this->cwd . '/string', 228 | 'object.php' => new SplFileInfo($this->cwd . '/object.php'), 229 | 'string.php' => $this->cwd . '/string.php', 230 | ) 231 | ), 232 | $this->cwd 233 | ); 234 | 235 | /** @var $phar SplFileInfo[] */ 236 | $phar = $this->phar; 237 | 238 | $this->assertTrue($phar['object']->isDir()); 239 | $this->assertTrue($phar['string']->isDir()); 240 | $this->assertTrue($phar['object.php']->isFile()); 241 | $this->assertTrue($phar['string.php']->isFile()); 242 | } 243 | 244 | public function testBuildFromIteratorBaseRequired() 245 | { 246 | $this->setExpectedException( 247 | 'Herrera\\Box\\Exception\\InvalidArgumentException', 248 | 'The $base argument is required for SplFileInfo values.' 249 | ); 250 | 251 | $this->box->buildFromIterator( 252 | new ArrayIterator(array(new SplFileInfo($this->cwd))) 253 | ); 254 | } 255 | 256 | public function testBuildFromIteratorOutsideBase() 257 | { 258 | $this->setExpectedException( 259 | 'Herrera\\Box\\Exception\\UnexpectedValueException', 260 | "The file \"{$this->cwd}\" is not in the base directory." 261 | ); 262 | 263 | $this->box->buildFromIterator( 264 | new ArrayIterator(array(new SplFileInfo($this->cwd))), 265 | __DIR__ 266 | ); 267 | } 268 | 269 | public function testBuildFromIteratorInvalidKey() 270 | { 271 | $this->setExpectedException( 272 | 'Herrera\\Box\\Exception\\UnexpectedValueException', 273 | 'The key returned by the iterator (integer) is not a string.' 274 | ); 275 | 276 | $this->box->buildFromIterator(new ArrayIterator(array('test'))); 277 | } 278 | 279 | public function testBuildFromIteratorInvalid() 280 | { 281 | $this->setExpectedException( 282 | 'Herrera\\Box\\Exception\\UnexpectedValueException', 283 | 'The iterator value "resource" was not expected.' 284 | ); 285 | 286 | $this->box->buildFromIterator( 287 | new ArrayIterator(array('stream' => STDOUT)) 288 | ); 289 | } 290 | 291 | /** 292 | * @depends testAddCompactor 293 | */ 294 | public function testCompactContents() 295 | { 296 | $compactor = new Compactor(); 297 | 298 | $this->box->addCompactor($compactor); 299 | 300 | $this->assertEquals( 301 | 'my value', 302 | $this->box->compactContents('test.php', ' my value ') 303 | ); 304 | } 305 | 306 | public function testCreate() 307 | { 308 | $box = Box::create('test2.phar'); 309 | 310 | $this->assertInstanceOf('Herrera\\Box\\Box', $box); 311 | $this->assertEquals( 312 | 'test2.phar', 313 | $this->getPropertyValue($box, 'file') 314 | ); 315 | } 316 | 317 | public function testGetPhar() 318 | { 319 | $this->assertSame($this->phar, $this->box->getPhar()); 320 | } 321 | 322 | public function testGetSignature() 323 | { 324 | $path = RES_DIR . '/example.phar'; 325 | $phar = new Phar($path); 326 | 327 | $this->assertEquals( 328 | $phar->getSignature(), 329 | Box::getSignature($path) 330 | ); 331 | } 332 | 333 | public function testReplaceValues() 334 | { 335 | $this->setPropertyValue( 336 | $this->box, 337 | 'values', 338 | array( 339 | '@1@' => 'a', 340 | '@2@' => 'b' 341 | ) 342 | ); 343 | 344 | $this->assertEquals('ab@3@', $this->box->replaceValues('@1@@2@@3@')); 345 | } 346 | 347 | public function testSetStubUsingFileNotExist() 348 | { 349 | $this->setExpectedException( 350 | 'Herrera\\Box\\Exception\\FileException', 351 | 'The file "/does/not/exist" does not exist or is not a file.' 352 | ); 353 | 354 | $this->box->setStubUsingFile('/does/not/exist'); 355 | } 356 | 357 | public function testSetStubUsingFileReadError() 358 | { 359 | vfsStreamWrapper::setRoot($root = vfsStream::newDirectory('test')); 360 | 361 | $root->addChild(vfsStream::newFile('test.php', 0000)); 362 | 363 | $this->setExpectedException( 364 | 'Herrera\\Box\\Exception\\FileException', 365 | 'failed to open stream' 366 | ); 367 | 368 | $this->box->setStubUsingFile('vfs://test/test.php'); 369 | } 370 | 371 | public function testSetStubUsingFile() 372 | { 373 | $file = $this->createFile(); 374 | 375 | file_put_contents( 376 | $file, 377 | <<box->setValues(array('@replace_me@' => 'replaced')); 386 | $this->box->setStubUsingFile($file, true); 387 | 388 | $this->assertEquals( 389 | 'replaced', 390 | exec('php test.phar') 391 | ); 392 | } 393 | 394 | public function testSetValues() 395 | { 396 | $rand = rand(); 397 | 398 | $this->box->setValues(array('@rand@' => $rand)); 399 | 400 | $this->assertEquals( 401 | array('@rand@' => $rand), 402 | $this->getPropertyValue($this->box, 'values') 403 | ); 404 | } 405 | 406 | public function testSetValuesNonScalar() 407 | { 408 | $this->setExpectedException( 409 | 'Herrera\\Box\\Exception\\InvalidArgumentException', 410 | 'Non-scalar values (such as resource) are not supported.' 411 | ); 412 | 413 | $this->box->setValues(array('stream' => STDOUT)); 414 | } 415 | 416 | /** 417 | * @depends testGetPhar 418 | */ 419 | public function testSign() 420 | { 421 | if (false === extension_loaded('openssl')) { 422 | $this->markTestSkipped('The "openssl" extension is not available.'); 423 | } 424 | 425 | list($key, $password) = $this->getPrivateKey(); 426 | 427 | $this->box->getPhar()->addFromString( 428 | 'test.php', 429 | 'box->getPhar()->setStub( 433 | StubGenerator::create() 434 | ->index('test.php') 435 | ->generate() 436 | ); 437 | 438 | $this->box->sign($key, $password); 439 | 440 | $this->assertEquals( 441 | 'Hello, world!', 442 | exec('php test.phar') 443 | ); 444 | } 445 | 446 | /** 447 | * @depends testSign 448 | */ 449 | public function testSignWriteError() 450 | { 451 | list($key, $password) = $this->getPrivateKey(); 452 | 453 | mkdir('test.phar.pubkey'); 454 | 455 | $this->box->getPhar()->addFromString('test.php', 'setExpectedException( 458 | 'Herrera\\Box\\Exception\\FileException', 459 | 'failed to open stream' 460 | ); 461 | 462 | $this->box->sign($key, $password); 463 | } 464 | 465 | /** 466 | * @depends testSign 467 | */ 468 | public function testSignUsingFile() 469 | { 470 | if (false === extension_loaded('openssl')) { 471 | $this->markTestSkipped('The "openssl" extension is not available.'); 472 | } 473 | 474 | list($key, $password) = $this->getPrivateKey(); 475 | 476 | $file = $this->createFile(); 477 | 478 | file_put_contents($file, $key); 479 | 480 | $this->box->getPhar()->addFromString( 481 | 'test.php', 482 | 'box->getPhar()->setStub( 486 | StubGenerator::create() 487 | ->index('test.php') 488 | ->generate() 489 | ); 490 | 491 | $this->box->signUsingFile($file, $password); 492 | 493 | $this->assertEquals( 494 | 'Hello, world!', 495 | exec('php test.phar') 496 | ); 497 | } 498 | 499 | public function testSignUsingFileNotExist() 500 | { 501 | $this->setExpectedException( 502 | 'Herrera\\Box\\Exception\\FileException', 503 | 'The file "/does/not/exist" does not exist or is not a file.' 504 | ); 505 | 506 | $this->box->signUsingFile('/does/not/exist'); 507 | } 508 | 509 | public function testSignUsingFileReadError() 510 | { 511 | $root = vfsStream::newDirectory('test'); 512 | $root->addChild(vfsStream::newFile('private.key', 0000)); 513 | 514 | vfsStreamWrapper::setRoot($root); 515 | 516 | $this->setExpectedException( 517 | 'Herrera\\Box\\Exception\\FileException', 518 | 'failed to open stream' 519 | ); 520 | 521 | $this->box->signUsingFile('vfs://test/private.key'); 522 | } 523 | 524 | protected function tearDown() 525 | { 526 | unset($this->box, $this->phar); 527 | 528 | parent::tearDown(); 529 | } 530 | 531 | protected function setUp() 532 | { 533 | chdir($this->cwd = $this->createDir()); 534 | 535 | $this->phar = new Phar('test.phar'); 536 | $this->box = new Box($this->phar, 'test.phar'); 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Compactor.php: -------------------------------------------------------------------------------- 1 | compactor->setExtensions(array('php')); 17 | 18 | $this->assertTrue($this->compactor->supports('test.php')); 19 | $this->assertFalse($this->compactor->supports('test')); 20 | 21 | } 22 | 23 | protected function setUp() 24 | { 25 | $this->compactor = new BaseCompactor(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Compactor/JavascriptTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($compactor->supports('test.js')); 16 | $this->assertFalse($compactor->supports('test')); 17 | $this->assertFalse($compactor->supports('test.min.js')); 18 | } 19 | 20 | /** 21 | * @dataProvider javascriptProvider 22 | */ 23 | public function testCompact($input, $output) 24 | { 25 | $compactor = new Javascript(); 26 | $this->assertEquals($compactor->compact($input), $output); 27 | } 28 | 29 | public function javascriptProvider() 30 | { 31 | return array( 32 | 33 | array('new Array();', 'new Array();'), 34 | 35 | array('(function(){ 36 | var Array = function(){}; 37 | return new Array(1, 2, 3, 4); 38 | })();', '(function(){var Array=function(){};return new Array(1,2,3,4);})();') 39 | 40 | 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Compactor/JsonTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $compactor->compact($original)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Compactor/PhpTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($expected, $this->php->compact($original)); 55 | } 56 | 57 | public function testConvertWithAnnotations() 58 | { 59 | $tokenizer = new Tokenizer(); 60 | $tokenizer->ignore(array('ignored')); 61 | 62 | $this->php->setTokenizer($tokenizer); 63 | 64 | $original = <<assertEquals($expected, $this->php->compact($original)); 153 | } 154 | 155 | public function testIssue14() 156 | { 157 | $original = << 164 | */ 165 | class ComposerAutoloaderInitc22fe6e3e5ad79bad24655b3e52999df 166 | { 167 | private static \$loader; 168 | 169 | /** @inline annotation */ 170 | public static function loadClassLoader(\$class) 171 | { 172 | if ('Composer\Autoload\ClassLoader' === \$class) { 173 | require __DIR__ . '/ClassLoader.php'; 174 | } 175 | } 176 | 177 | public static function getLoader() 178 | { 179 | if (null !== self::\$loader) { 180 | return self::\$loader; 181 | } 182 | 183 | spl_autoload_register(array('ComposerAutoloaderInitc22fe6e3e5ad79bad24655b3e52999df', 'loadClassLoader'), true, true); 184 | self::\$loader = \$loader = new \Composer\Autoload\ClassLoader(); 185 | spl_autoload_unregister(array('ComposerAutoloaderInitc22fe6e3e5ad79bad24655b3e52999df', 'loadClassLoader')); 186 | 187 | \$vendorDir = dirname(__DIR__); 188 | \$baseDir = dirname(\$vendorDir); 189 | 190 | \$includePaths = require __DIR__ . '/include_paths.php'; 191 | array_push(\$includePaths, get_include_path()); 192 | set_include_path(join(PATH_SEPARATOR, \$includePaths)); 193 | 194 | \$map = require __DIR__ . '/autoload_namespaces.php'; 195 | foreach (\$map as \$namespace => \$path) { 196 | \$loader->set(\$namespace, \$path); 197 | } 198 | 199 | \$map = require __DIR__ . '/autoload_psr4.php'; 200 | foreach (\$map as \$namespace => \$path) { 201 | \$loader->setPsr4(\$namespace, \$path); 202 | } 203 | 204 | \$classMap = require __DIR__ . '/autoload_classmap.php'; 205 | if (\$classMap) { 206 | \$loader->addClassMap(\$classMap); 207 | } 208 | 209 | \$loader->register(true); 210 | 211 | return \$loader; 212 | } 213 | } 214 | 215 | CODE; 216 | 217 | $expected = << \$path) { 256 | \$loader->set(\$namespace, \$path); 257 | } 258 | 259 | \$map = require __DIR__ . '/autoload_psr4.php'; 260 | foreach (\$map as \$namespace => \$path) { 261 | \$loader->setPsr4(\$namespace, \$path); 262 | } 263 | 264 | \$classMap = require __DIR__ . '/autoload_classmap.php'; 265 | if (\$classMap) { 266 | \$loader->addClassMap(\$classMap); 267 | } 268 | 269 | \$loader->register(true); 270 | 271 | return \$loader; 272 | } 273 | } 274 | 275 | CODE; 276 | 277 | $tokenizer = new Tokenizer(); 278 | $tokenizer->ignore(array('author', 'inline')); 279 | 280 | $this->php->setTokenizer($tokenizer); 281 | 282 | $this->assertEquals( 283 | $expected, 284 | $this->php->compact($original) 285 | ); 286 | } 287 | 288 | public function testSetTokenizer() 289 | { 290 | $tokenizer = new Tokenizer(); 291 | 292 | $this->php->setTokenizer($tokenizer); 293 | 294 | $this->assertInstanceOf( 295 | 'Herrera\\Annotations\\Convert\\ToString', 296 | $this->getPropertyValue($this->php, 'converter') 297 | ); 298 | 299 | $this->assertSame( 300 | $tokenizer, 301 | $this->getPropertyValue($this->php, 'tokenizer') 302 | ); 303 | } 304 | 305 | public function testSupports() 306 | { 307 | $this->assertTrue($this->php->supports('test.php')); 308 | } 309 | 310 | protected function setUp() 311 | { 312 | $this->php = new Php(); 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Exception/ExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('My message.', $exception->getMessage()); 15 | } 16 | 17 | public function testLastError() 18 | { 19 | /** @noinspection PhpExpressionResultUnusedInspection */ 20 | /** @noinspection PhpUndefinedVariableInspection */ 21 | @$test; 22 | 23 | $exception = Exception::lastError(); 24 | 25 | $this->assertEquals('Undefined variable: test', $exception->getMessage()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Exception/OpenSslExceptionTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 14 | 'The "openssl" extension is required to test the exception.' 15 | ); 16 | } 17 | 18 | OpenSslException::reset(); 19 | 20 | openssl_pkey_get_private('test', 'test'); 21 | 22 | $exception = OpenSslException::lastError(); 23 | 24 | $this->assertRegExp('/PEM routines/', $exception->getMessage()); 25 | } 26 | 27 | public function testReset() 28 | { 29 | openssl_pkey_get_private('test', 'test'); 30 | 31 | OpenSslException::reset(); 32 | 33 | $this->assertEmpty(openssl_error_string()); 34 | } 35 | 36 | public function testResetWarning() 37 | { 38 | openssl_pkey_get_private('test' . rand(), 'test' . rand()); 39 | 40 | restore_error_handler(); 41 | 42 | @OpenSslException::reset(0); 43 | 44 | $error = error_get_last(); 45 | 46 | $this->assertEquals( 47 | 'The OpenSSL error clearing loop has exceeded 0 rounds.', 48 | $error['message'] 49 | ); 50 | } 51 | 52 | protected function setUp() 53 | { 54 | if (false === extension_loaded('openssl')) { 55 | $this->markTestSkipped('The "openssl" extension is not available.'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/ExtractTest.php: -------------------------------------------------------------------------------- 1 | "), 18 | ); 19 | } 20 | 21 | public function testConstruct() 22 | { 23 | $extract = new Extract(__FILE__, 123); 24 | 25 | $this->assertEquals( 26 | __FILE__, 27 | $this->getPropertyValue($extract, 'file') 28 | ); 29 | 30 | $this->assertSame( 31 | 123, 32 | $this->getPropertyValue($extract, 'stub') 33 | ); 34 | } 35 | 36 | public function testConstructNotExist() 37 | { 38 | $this->setExpectedException( 39 | 'InvalidArgumentException', 40 | 'The path "/does/not/exist" is not a file or does not exist.' 41 | ); 42 | 43 | new Extract('/does/not/exist', 123); 44 | } 45 | 46 | /** 47 | * @dataProvider getStubLengths 48 | */ 49 | public function testFindStubLength($file, $length, $pattern) 50 | { 51 | if ($pattern) { 52 | $this->assertSame( 53 | $length, 54 | Extract::findStubLength($file, $pattern) 55 | ); 56 | } else { 57 | $this->assertSame($length, Extract::findStubLength($file)); 58 | } 59 | } 60 | 61 | public function testFindStubLengthInvalid() 62 | { 63 | $path = RES_DIR . '/example.phar'; 64 | 65 | $this->setExpectedException( 66 | 'InvalidArgumentException', 67 | 'The pattern could not be found in "' . $path . '".' 68 | ); 69 | 70 | Extract::findStubLength($path, 'bad pattern'); 71 | } 72 | 73 | public function testFindStubLengthOpenError() 74 | { 75 | PHPUnit_Framework_Error_Warning::$enabled = false; 76 | 77 | $this->setExpectedException( 78 | 'RuntimeException', 79 | 'The phar "/does/not/exist" could not be opened for reading.' 80 | ); 81 | 82 | $this->expectOutputRegex( 83 | '/No such file or directory/' 84 | ); 85 | 86 | Extract::findStubLength('/does/not/exist'); 87 | } 88 | 89 | public function testGo() 90 | { 91 | $extract = new Extract(RES_DIR . '/mixed.phar', 6683); 92 | 93 | $dir = $extract->go(); 94 | 95 | $this->assertFileExists("$dir/test"); 96 | 97 | $this->assertEquals( 98 | "assertEquals( 103 | "assertEquals( 108 | "createDir(); 117 | 118 | $extract->go($dir); 119 | 120 | $this->assertFileExists("$dir/test"); 121 | 122 | $this->assertEquals( 123 | "assertEquals( 128 | "assertEquals( 133 | "setExpectedException( 145 | 'RuntimeException', 146 | 'Could not seek to -123 in the file "' . $path . '".' 147 | ); 148 | 149 | $extract->go(); 150 | } 151 | 152 | /** 153 | * Issue #7 154 | * 155 | * Files with no content would trigger an exception when extracted. 156 | */ 157 | public function testGoEmptyFile() 158 | { 159 | $path = RES_DIR . '/empty.phar'; 160 | 161 | $extract = new Extract($path, Extract::findStubLength($path)); 162 | 163 | $dir = $extract->go(); 164 | 165 | $this->assertFileExists($dir . '/empty.php'); 166 | 167 | $this->assertEquals('', file_get_contents($dir . '/empty.php')); 168 | } 169 | 170 | public function testPurge() 171 | { 172 | $dir = $this->createDir(); 173 | 174 | mkdir("$dir/a/b/c", 0755, true); 175 | touch("$dir/a/b/c/d"); 176 | 177 | Extract::purge($dir); 178 | 179 | $this->assertFileNotExists($dir); 180 | } 181 | 182 | public function testPurgeUnlinkError() 183 | { 184 | $root = vfsStream::newDirectory('test', 0444); 185 | $root->addChild(vfsStream::newFile('test', 0000)); 186 | 187 | vfsStreamWrapper::setRoot($root); 188 | 189 | $this->setExpectedException( 190 | 'RuntimeException', 191 | 'The file "vfs://test/test" could not be deleted.' 192 | ); 193 | 194 | Extract::purge('vfs://test/test'); 195 | } 196 | 197 | protected function setUp() 198 | { 199 | $paths = array( 200 | sys_get_temp_dir() . '/pharextract/mixed' 201 | ); 202 | 203 | foreach ($paths as $path) { 204 | if (file_exists($path)) { 205 | $this->purgePath($path); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/AbstractBufferedHashTest.php: -------------------------------------------------------------------------------- 1 | hash->update('a'); 17 | $this->hash->update('b'); 18 | $this->hash->update('c'); 19 | 20 | $this->assertEquals( 21 | 'abc', 22 | $this->getPropertyValue($this->hash, 'data') 23 | ); 24 | } 25 | 26 | public function testGetData() 27 | { 28 | $this->setPropertyValue($this->hash, 'data', 'abc'); 29 | 30 | $this->assertEquals('abc', $this->callMethod($this->hash, 'getData')); 31 | } 32 | 33 | protected function setUp() 34 | { 35 | $this->hash = new BufferedHash(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/AbstractPublicKeyTest.php: -------------------------------------------------------------------------------- 1 | createFile()); 17 | 18 | file_put_contents($file . '.pubkey', 'abc'); 19 | 20 | $this->hash->init('abc', $file); 21 | 22 | $this->assertEquals( 23 | 'abc', 24 | $this->getPropertyValue($this->hash, 'key') 25 | ); 26 | } 27 | 28 | public function testInitNotExist() 29 | { 30 | $this->setExpectedException( 31 | 'Herrera\\Box\\Exception\\FileException', 32 | 'No such file or directory' 33 | ); 34 | 35 | $this->hash->init('abc', '/does/not/exist'); 36 | } 37 | 38 | public function testGetKey() 39 | { 40 | $this->setPropertyValue($this->hash, 'key', 'abc'); 41 | 42 | $this->assertEquals('abc', $this->callMethod($this->hash, 'getKey')); 43 | } 44 | 45 | protected function setUp() 46 | { 47 | $this->hash = new PublicKey(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/BufferedHash.php: -------------------------------------------------------------------------------- 1 | hash->init('md5', ''); 18 | 19 | $this->assertInternalType( 20 | 'resource', 21 | $this->getPropertyValue($this->hash, 'context') 22 | ); 23 | } 24 | 25 | public function testInitBadAlgorithm() 26 | { 27 | $this->setExpectedException( 28 | 'Herrera\\Box\\Exception\\Exception', 29 | 'Unknown hashing algorithm' 30 | ); 31 | 32 | $this->hash->init('bad algorithm', ''); 33 | } 34 | 35 | /** 36 | * @depends testInit 37 | */ 38 | public function testUpdate() 39 | { 40 | $this->hash->init('md5', ''); 41 | $this->hash->update('test'); 42 | 43 | $this->assertEquals( 44 | md5('test'), 45 | hash_final($this->getPropertyValue($this->hash, 'context')) 46 | ); 47 | } 48 | 49 | /** 50 | * @depends testInit 51 | * @depends testUpdate 52 | */ 53 | public function testVerify() 54 | { 55 | $this->hash->init('md5', ''); 56 | $this->hash->update('test'); 57 | 58 | $this->assertTrue( 59 | $this->hash->verify(strtoupper(md5('test'))) 60 | ); 61 | } 62 | 63 | protected function setUp() 64 | { 65 | $this->hash = new Hash(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/OpenSslTest.php: -------------------------------------------------------------------------------- 1 | hash->init('openssl', $path); 20 | $this->hash->update( 21 | file_get_contents($path, null, null, 0, filesize($path) - 76) 22 | ); 23 | 24 | $this->assertTrue( 25 | $this->hash->verify( 26 | '54AF1D4E5459D3A77B692E46FDB9C965D1C7579BD1F2AD2BECF4973677575444FE21E104B7655BA3D088090C28DF63D14876B277C423C8BFBCDB9E3E63F9D61A' 27 | ) 28 | ); 29 | } 30 | 31 | public function testVerifyErrorHandlingBug() 32 | { 33 | $dir = $this->createDir(); 34 | $path = "$dir/openssl.phar"; 35 | 36 | copy(RES_DIR . '/openssl.phar', $path); 37 | touch("$path.pubkey"); 38 | 39 | $this->hash->init('openssl', $path); 40 | $this->hash->update( 41 | file_get_contents($path, null, null, 0, filesize($path) - 76) 42 | ); 43 | 44 | $this->setExpectedException( 45 | 'Herrera\\Box\\Exception\\OpenSslException', 46 | 'cannot be coerced' 47 | ); 48 | 49 | $this->hash->verify('it dont matter, aight'); 50 | } 51 | 52 | protected function setUp() 53 | { 54 | $this->hash = new OpenSsl(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/PhpSecLibTest.php: -------------------------------------------------------------------------------- 1 | hash->init('openssl', $path); 20 | $this->hash->update( 21 | file_get_contents($path, null, null, 0, filesize($path) - 76) 22 | ); 23 | 24 | $this->assertTrue( 25 | $this->hash->verify( 26 | '54AF1D4E5459D3A77B692E46FDB9C965D1C7579BD1F2AD2BECF4973677575444FE21E104B7655BA3D088090C28DF63D14876B277C423C8BFBCDB9E3E63F9D61A' 27 | ) 28 | ); 29 | } 30 | 31 | protected function setUp() 32 | { 33 | $this->hash = new PhpSecLib(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/Signature/PublicKey.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 20 | 'Herrera\\Box\\Signature\\OpenSsl', 21 | $this->getPropertyValue($hash, 'hash') 22 | ); 23 | } else { 24 | $this->assertInstanceOf( 25 | 'Herrera\\Box\\Signature\\PhpSecLib', 26 | $this->getPropertyValue($hash, 'hash') 27 | ); 28 | } 29 | } 30 | 31 | public function testFunctional() 32 | { 33 | $path = RES_DIR . '/openssl.phar'; 34 | $hash = new PublicKeyDelegate(); 35 | 36 | $hash->init('openssl', $path); 37 | $hash->update( 38 | file_get_contents($path, null, null, 0, filesize($path) - 76) 39 | ); 40 | 41 | $this->assertTrue( 42 | $hash->verify( 43 | '54AF1D4E5459D3A77B692E46FDB9C965D1C7579BD1F2AD2BECF4973677575444FE21E104B7655BA3D088090C28DF63D14876B277C423C8BFBCDB9E3E63F9D61A' 44 | ) 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/SignatureTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 33 | realpath($path), 34 | $this->getPropertyValue($sig, 'file') 35 | ); 36 | 37 | $this->assertSame( 38 | filesize($path), 39 | $this->getPropertyValue($sig, 'size') 40 | ); 41 | } 42 | 43 | public function testConstructNotExist() 44 | { 45 | $this->setExpectedException( 46 | 'Herrera\\Box\\Exception\\FileException', 47 | 'The path "/does/not/exist" does not exist or is not a file.' 48 | ); 49 | 50 | new Signature('/does/not/exist'); 51 | } 52 | 53 | public function testCreate() 54 | { 55 | $this->assertInstanceOf( 56 | 'Herrera\\Box\\Signature', 57 | Signature::create(RES_DIR . '/example.phar') 58 | ); 59 | } 60 | 61 | public function testCreateNoGbmb() 62 | { 63 | $path = realpath(RES_DIR . '/missing.phar'); 64 | $sig = new Signature($path); 65 | 66 | $this->setExpectedException( 67 | 'PharException', 68 | "The phar \"$path\" is not signed." 69 | ); 70 | 71 | $sig->get(); 72 | } 73 | 74 | public function testCreateInvalid() 75 | { 76 | $path = realpath(RES_DIR . '/invalid.phar'); 77 | $sig = new Signature($path); 78 | 79 | $this->setExpectedException( 80 | 'PharException', 81 | "The signature type (ffffffff) is not recognized for the phar \"$path\"." 82 | ); 83 | 84 | $sig->get(true); 85 | } 86 | 87 | public function testCreateMissingNoRequire() 88 | { 89 | $path = realpath(RES_DIR . '/missing.phar'); 90 | $sig = new Signature($path); 91 | 92 | $this->assertNull($sig->get(false)); 93 | } 94 | 95 | /** 96 | * @dataProvider getPhars 97 | */ 98 | public function testGet($path) 99 | { 100 | $phar = new Phar($path); 101 | $sig = new Signature($path); 102 | 103 | $this->assertEquals( 104 | $phar->getSignature(), 105 | $sig->get() 106 | ); 107 | } 108 | 109 | /** 110 | * @dataProvider getPhars 111 | */ 112 | public function testVerify($path) 113 | { 114 | $sig = new Signature($path); 115 | 116 | $this->assertTrue($sig->verify()); 117 | } 118 | 119 | // private methods 120 | 121 | public function testHandle() 122 | { 123 | $sig = new Signature(__FILE__); 124 | 125 | $this->setPropertyValue($sig, 'file', '/does/not/exist'); 126 | 127 | $this->setExpectedException( 128 | 'Herrera\\Box\\Exception\\FileException', 129 | 'No such file or directory' 130 | ); 131 | 132 | $this->callMethod($sig, 'handle'); 133 | } 134 | 135 | public function testRead() 136 | { 137 | $sig = new Signature(__FILE__); 138 | 139 | $this->setPropertyValue($sig, 'handle', true); 140 | 141 | $this->setExpectedException( 142 | 'Herrera\\Box\\Exception\\FileException', 143 | 'boolean given' 144 | ); 145 | 146 | $this->callMethod($sig, 'read', array(123)); 147 | } 148 | 149 | public function testReadShort() 150 | { 151 | $file = $this->createFile(); 152 | $sig = new Signature($file); 153 | 154 | $this->setExpectedException( 155 | 'Herrera\\Box\\Exception\\FileException', 156 | "Only read 0 of 1 bytes from \"$file\"." 157 | ); 158 | 159 | $this->callMethod($sig, 'read', array(1)); 160 | } 161 | 162 | public function testSeek() 163 | { 164 | $file = $this->createFile(); 165 | $sig = new Signature($file); 166 | 167 | $this->setExpectedException( 168 | 'Herrera\\Box\\Exception\\FileException' 169 | ); 170 | 171 | $this->callMethod($sig, 'seek', array(-1)); 172 | } 173 | 174 | protected function setUp() 175 | { 176 | $this->types = $this->getPropertyValue( 177 | 'Herrera\\Box\\Signature', 178 | 'types' 179 | ); 180 | } 181 | 182 | protected function tearDown() 183 | { 184 | $this->setPropertyValue( 185 | 'Herrera\\Box\\Signature', 186 | 'types', 187 | $this->types 188 | ); 189 | 190 | parent::tearDown(); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/tests/Herrera/Box/Tests/StubGeneratorTest.php: -------------------------------------------------------------------------------- 1 | generator->alias('test.phar'); 20 | 21 | $this->assertEquals( 22 | 'test.phar', 23 | $this->getPropertyValue($this->generator, 'alias') 24 | ); 25 | } 26 | 27 | public function testBanner() 28 | { 29 | $this->generator->banner( 30 | 'Phar creation 31 | 32 | does work 33 | 34 | 35 | indented' 36 | ); 37 | 38 | $this->assertEquals( 39 | 'Phar creation 40 | 41 | does work 42 | 43 | 44 | indented', 45 | $this->getPropertyValue($this->generator, 'banner') 46 | ); 47 | 48 | $this->assertEquals( 49 | <<generator->generate() 67 | ); 68 | } 69 | 70 | public function testCreate() 71 | { 72 | $this->assertInstanceOf( 73 | 'Herrera\\Box\\StubGenerator', 74 | StubGenerator::create() 75 | ); 76 | } 77 | 78 | public function testExtract() 79 | { 80 | $this->generator->extract(true); 81 | 82 | $this->assertTrue( 83 | $this->getPropertyValue( 84 | $this->generator, 85 | 'extract' 86 | ) 87 | ); 88 | 89 | $this->assertEquals( 90 | $this->getExtractCode(), 91 | $this->getPropertyValue($this->generator, 'extractCode') 92 | ); 93 | } 94 | 95 | public function testIndex() 96 | { 97 | $this->generator->index('index.php'); 98 | 99 | $this->assertEquals( 100 | 'index.php', 101 | $this->getPropertyValue($this->generator, 'index') 102 | ); 103 | } 104 | 105 | public function testIntercept() 106 | { 107 | $this->generator->intercept(true); 108 | 109 | $this->assertTrue( 110 | $this->getPropertyValue( 111 | $this->generator, 112 | 'intercept' 113 | ) 114 | ); 115 | } 116 | 117 | public function testGenerate() 118 | { 119 | $code = $this->getExtractCode(); 120 | $code['constants'] = join("\n", $code['constants']); 121 | $code['class'] = join("\n", $code['class']); 122 | 123 | $this->generator 124 | ->alias('test.phar') 125 | ->extract(true) 126 | ->index('index.php') 127 | ->intercept(true) 128 | ->mimetypes(array('phtml' => Phar::PHPS)) 129 | ->mung(array('REQUEST_URI')) 130 | ->notFound('not_found.php') 131 | ->rewrite('rewrite') 132 | ->web(true); 133 | 134 | $phps = Phar::PHPS; 135 | 136 | $this->assertEquals( 137 | << $phps, 149 | ), 'rewrite'); 150 | Phar::interceptFileFuncs(); 151 | Phar::mungServer(array ( 152 | 0 => 'REQUEST_URI', 153 | )); 154 | } else { 155 | \$extract = new Extract(__FILE__, Extract::findStubLength(__FILE__)); 156 | \$dir = \$extract->go(); 157 | set_include_path(\$dir . PATH_SEPARATOR . get_include_path()); 158 | require "\$dir/index.php"; 159 | } 160 | {$code['class']} 161 | __HALT_COMPILER(); 162 | STUB 163 | , 164 | $this->generator->generate() 165 | ); 166 | } 167 | 168 | public function testGenerateExtractForced() 169 | { 170 | $code = $this->getExtractCode(); 171 | $code['constants'] = join("\n", $code['constants']); 172 | $code['class'] = join("\n", $code['class']); 173 | 174 | $this->generator 175 | ->alias('test.phar') 176 | ->extract(true, true) 177 | ->index('index.php') 178 | ->intercept(true) 179 | ->mimetypes(array('phtml' => Phar::PHPS)) 180 | ->mung(array('REQUEST_URI')) 181 | ->notFound('not_found.php') 182 | ->rewrite('rewrite'); 183 | 184 | $this->assertEquals( 185 | <<go(); 196 | set_include_path(\$dir . PATH_SEPARATOR . get_include_path()); 197 | if (class_exists('Phar')) { 198 | Phar::mapPhar('test.phar'); 199 | Phar::interceptFileFuncs(); 200 | Phar::mungServer(array ( 201 | 0 => 'REQUEST_URI', 202 | )); 203 | } 204 | require "\$dir/index.php"; 205 | {$code['class']} 206 | __HALT_COMPILER(); 207 | STUB 208 | , 209 | $this->generator->generate() 210 | ); 211 | } 212 | 213 | /** 214 | * @depends testGenerate 215 | */ 216 | public function testGenerateMap() 217 | { 218 | $this->generator->alias('test.phar'); 219 | 220 | $this->assertEquals( 221 | <<generator->generate() 236 | ); 237 | } 238 | 239 | /** 240 | * @depends testGenerate 241 | */ 242 | public function testGenerateNoShebang() 243 | { 244 | $this 245 | ->generator 246 | ->alias('test.phar') 247 | ->shebang(''); 248 | 249 | $this->assertEquals( 250 | <<generator->generate() 264 | ); 265 | } 266 | 267 | public function testMimetypes() 268 | { 269 | $map = array('php' => Phar::PHPS); 270 | 271 | $this->generator->mimetypes($map); 272 | 273 | $this->assertEquals( 274 | $map, 275 | $this->getPropertyValue($this->generator, 'mimetypes') 276 | ); 277 | } 278 | 279 | public function testMung() 280 | { 281 | $list = array('REQUEST_URI'); 282 | 283 | $this->generator->mung($list); 284 | 285 | $this->assertEquals( 286 | $list, 287 | $this->getPropertyValue($this->generator, 'mung') 288 | ); 289 | } 290 | 291 | public function testMungInvalid() 292 | { 293 | $this->setExpectedException( 294 | 'Herrera\\Box\\Exception\\InvalidArgumentException', 295 | 'The $_SERVER variable "test" is not allowed.' 296 | ); 297 | 298 | $this->generator->mung(array('test')); 299 | } 300 | 301 | public function testNotFound() 302 | { 303 | $this->generator->notFound('not_found.php'); 304 | 305 | $this->assertEquals( 306 | 'not_found.php', 307 | $this->getPropertyValue($this->generator, 'notFound') 308 | ); 309 | } 310 | 311 | public function testRewrite() 312 | { 313 | $this->generator->rewrite('rewrite()'); 314 | 315 | $this->assertEquals( 316 | 'rewrite()', 317 | $this->getPropertyValue($this->generator, 'rewrite') 318 | ); 319 | } 320 | 321 | public function testShebang() 322 | { 323 | $this->generator->shebang('#!/bin/php'); 324 | 325 | $this->assertEquals( 326 | '#!/bin/php', 327 | $this->getPropertyValue($this->generator, 'shebang') 328 | ); 329 | } 330 | 331 | public function testWeb() 332 | { 333 | $this->generator->web(true); 334 | 335 | $this->assertTrue($this->getPropertyValue($this->generator, 'web')); 336 | } 337 | 338 | protected function setUp() 339 | { 340 | $this->generator = new StubGenerator(); 341 | } 342 | 343 | private function getExtractCode() 344 | { 345 | $extractCode = array( 346 | 'constants' => array(), 347 | 'class' => array(), 348 | ); 349 | 350 | $compactor = new Php(); 351 | $code = file_get_contents(__DIR__ . '/../../../../lib/Herrera/Box/Extract.php'); 352 | $code = $compactor->compact($code); 353 | $code = preg_replace('/\n+/', "\n", $code); 354 | $code = explode("\n", $code); 355 | $code = array_slice($code, 2); 356 | 357 | foreach ($code as $i => $line) { 358 | if ((0 === strpos($line, 'use')) 359 | && (false === strpos($line, '\\')) 360 | ) { 361 | unset($code[$i]); 362 | } elseif (0 === strpos($line, 'define')) { 363 | $extractCode['constants'][] = $line; 364 | } else { 365 | $extractCode['class'][] = $line; 366 | } 367 | } 368 | 369 | return $extractCode; 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add(null, __DIR__); 7 | 8 | org\bovigo\vfs\vfsStreamWrapper::register(); 9 | --------------------------------------------------------------------------------