From 924fa40677fecdd727d5a2d7ecc9460810b6f5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Wed, 30 May 2018 18:58:38 +0200 Subject: [PATCH] Add Pharaoh to Box (#238) Add [Pharaoh](https://github.com/paragonie/pharaoh) to provide a new `diff` command Closes #209 --- README.md | 2 +- TODO | 11 -- bar.php | 4 + composer.json | 1 + composer.lock | 291 ++++++++++++++++++++++++++++- fixtures/diff/not-a-phar.phar | 2 + fixtures/diff/simple-phar | Bin 0 -> 6765 bytes fixtures/diff/simple-phar-bar.phar | Bin 0 -> 6802 bytes fixtures/diff/simple-phar-foo.phar | Bin 0 -> 6802 bytes fixtures/verify/simple-phar.phar | Bin 6765 -> 6802 bytes scoper.inc.php | 36 ++++ src/Console/Application.php | 1 + src/Console/Command/Build.php | 25 +-- src/Console/Command/Diff.php | 144 ++++++++++++++ tests/Console/ApplicationTest.php | 3 +- tests/Console/Command/DiffTest.php | 262 ++++++++++++++++++++++++++ 16 files changed, 752 insertions(+), 30 deletions(-) delete mode 100644 TODO create mode 100644 bar.php create mode 100644 fixtures/diff/not-a-phar.phar create mode 100644 fixtures/diff/simple-phar create mode 100644 fixtures/diff/simple-phar-bar.phar create mode 100644 fixtures/diff/simple-phar-foo.phar create mode 100644 src/Console/Command/Diff.php create mode 100644 tests/Console/Command/DiffTest.php diff --git a/README.md b/README.md index d3433096e..e120daeb8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ great things: - βš™οΈ Zero configuration by default - πŸš” [Requirements checker](doc/requirement-checker.md#requirements-checker) - 🚨 Friendly error logging experience -- πŸ” Retrieve information about the PHAR extension or a PHAR file and its contents (`box info`) +- πŸ” Retrieve information about the PHAR extension or a PHAR file and its contents (`box info` or `box diff`) - πŸ•΅οΈβ€β™€οΈ Verify the signature of an existing PHAR (`box verify`) - πŸ“ Use Git tags and short commit hashes for versioning diff --git a/TODO b/TODO deleted file mode 100644 index a6e5f1ac1..000000000 --- a/TODO +++ /dev/null @@ -1,11 +0,0 @@ -L2517: vendor/beberlei/assert/lib/Assert/Assertion.php -if (\strlen($val) > 100) { - $val = \substr($val, 0, 97) . '...'; - } - -This is silly, should rely on log_errors_max_len instead - - -add validation in stub generator -check that the options make sense -more user-friendly errors when the config file is invalid diff --git a/bar.php b/bar.php new file mode 100644 index 000000000..290849f76 --- /dev/null +++ b/bar.php @@ -0,0 +1,4 @@ +=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2018-04-04T21:24:14+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "22f564d068c093c3775552c700553209f9af60f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/22f564d068c093c3775552c700553209f9af60f8", + "reference": "22f564d068c093c3775552c700553209f9af60f8", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1|^2", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "time": "2018-05-22T20:15:01+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0.1", @@ -2206,6 +2450,51 @@ ], "time": "2018-04-04T05:10:37+00:00" }, + { + "name": "ulrichsg/getopt-php", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/getopt-php/getopt-php.git", + "reference": "f1af8cc92c337490c3070b2de19e25376a4e8e50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getopt-php/getopt-php/zipball/f1af8cc92c337490c3070b2de19e25376a4e8e50", + "reference": "f1af8cc92c337490c3070b2de19e25376a4e8e50", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GetOpt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ulrich Schmidt-Goertz", + "email": "ulrich@schmidt-goertz.de" + }, + { + "name": "Thomas Flori", + "email": "thflori@gmail.com" + } + ], + "description": "Command line arguments parser for PHP 5.4 - 7.1", + "homepage": "http://getopt-php.github.io/getopt-php", + "time": "2018-05-15T09:15:11+00:00" + }, { "name": "webmozart/assert", "version": "1.3.0", diff --git a/fixtures/diff/not-a-phar.phar b/fixtures/diff/not-a-phar.phar new file mode 100644 index 000000000..2eef53aaa --- /dev/null +++ b/fixtures/diff/not-a-phar.phar @@ -0,0 +1,2 @@ +This file is not a real PHAR file! Fake! + diff --git a/fixtures/diff/simple-phar b/fixtures/diff/simple-phar new file mode 100644 index 0000000000000000000000000000000000000000..a8bfeb3d49ff52a2e69e266cbda1893e833709e0 GIT binary patch literal 6765 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 eA3DrPm6O*W}&v0wv=Nbw#+jPMEo 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 literal 0 HcmV?d00001 diff --git a/fixtures/verify/simple-phar.phar b/fixtures/verify/simple-phar.phar index a8bfeb3d49ff52a2e69e266cbda1893e833709e0..32b8ea251113db6711d3c3ecf9c102bd7cb174b9 100644 GIT binary patch delta 119 zcmaEBGRbtqZ%Mx>1_lO3AQlAEsz5R=#mvmYz{JSJ+$7mBE!iy5BGJ&q)Wpm(IW;ZS z%-l?`AS1B|sE&ahq&Pnx$S;ruGVe^_ik_?@r7QFF`I(+{n^%mcIl2>Go(?<^zhTx5 LCZGa$CtoK3%y}U~ delta 82 zcmbPa`qpH_Z%Iv41_lO3AQptuKne)hfjBKcU#}peKo-bc-2XpvvZs`;jHI~oJJy_} Z$KvzvNmptuFmU+uei9Q06tw^V diff --git a/scoper.inc.php b/scoper.inc.php index 7bbea8e42..9679c8b53 100644 --- a/scoper.inc.php +++ b/scoper.inc.php @@ -98,6 +98,42 @@ function (string $filePath, string $prefix, string $contents): string { $contents ); }, + function (string $filePath, string $prefix, string $contents): string { + if ('vendor/paragonie/sodium_compat/autoload.php' !== $filePath) { + return $contents; + } + + return preg_replace( + '/\'sodiumCompatAutoloader\'/', + sprintf( + '\'%s\\%s\'', + $prefix, + 'sodiumCompatAutoloader' + ), + preg_replace( + '/\$namespace = \'ParagonIE_Sodium_\';/', + sprintf( + '$namespace = \'%s\\ParagonIE_Sodium_\';', + $prefix + ), + $contents + ) + ); + }, + function (string $filePath, string $prefix, string $contents): string { + if ('vendor/paragonie/sodium_compat/lib/php72compat.php' !== $filePath) { + return $contents; + } + + return preg_replace( + '/\\\\define\\("SODIUM_{\\$constant}", \\\\constant\\("ParagonIE_Sodium_Compat::{\\$constant}"\\)\\);/', + sprintf( + '\define("SODIUM_{$constant}", \constant("%s\\ParagonIE_Sodium_Compat::{$constant}"));', + $prefix + ), + $contents + ); + }, ], 'whitelist' => [ \Composer\Autoload\ClassLoader::class, diff --git a/src/Console/Application.php b/src/Console/Application.php index 8fb763e83..af764fbed 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -76,6 +76,7 @@ protected function getDefaultCommands(): array $commands[] = new Command\Build(); $commands[] = new Command\Compile(); + $commands[] = new Command\Diff(); $commands[] = new Command\Info(); $commands[] = new Command\Validate(); $commands[] = new Command\Verify(); diff --git a/src/Console/Command/Build.php b/src/Console/Command/Build.php index ab3495374..f4bd00115 100644 --- a/src/Console/Command/Build.php +++ b/src/Console/Command/Build.php @@ -17,8 +17,6 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use const E_USER_DEPRECATED; -use function trigger_error; /** * @deprecated @@ -29,28 +27,23 @@ final class Build extends Compile /** * {@inheritdoc} */ - public function run(InputInterface $input, OutputInterface $output) + protected function configure(): void { - $io = new SymfonyStyle($input, $output); - - @trigger_error( - $deprecationMessage = 'The command "build" is deprecated. Use "compile" instead.', - E_USER_DEPRECATED - ); - - $io->warning($deprecationMessage); + parent::configure(); - return parent::run($input, $output); + $this->setName('build'); + $this->setDescription('Builds a new PHAR (deprecated, use "compile" instead)'); } /** * {@inheritdoc} */ - protected function configure(): void + public function run(InputInterface $input, OutputInterface $output) { - parent::configure(); + $io = new SymfonyStyle($input, $output); - $this->setName('build'); - $this->setDescription('Builds a new PHAR'); + $io->warning('The command "build" is deprecated. Use "compile" instead.'); + + return parent::run($input, $output); } } diff --git a/src/Console/Command/Diff.php b/src/Console/Command/Diff.php new file mode 100644 index 000000000..4e29768c4 --- /dev/null +++ b/src/Console/Command/Diff.php @@ -0,0 +1,144 @@ + + * ThΓ©o Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command; + +use Assert\Assertion; +use KevinGH\Box\PhpSettingsHandler; +use ParagonIE\Pharaoh\Pharaoh; +use ParagonIE\Pharaoh\PharDiff; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Throwable; +use function array_map; +use function KevinGH\Box\FileSystem\remove; + +/** + * @private + */ +final class Diff extends Command +{ + use CreateTemporaryPharFile; + + private const FIRST_PHAR_ARG = 'pharA'; + private const SECOND_PHAR_ARG = 'pharB'; + + private const GNU_DIFF_OPTION = 'gnu-diff'; + private const CHECK_OPTION = 'check'; + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + parent::configure(); + + $this->setName('diff'); + $this->setDescription('Display the differences between all of the files in two PHARs'); + + $this->addArgument( + self::FIRST_PHAR_ARG, + InputArgument::REQUIRED, + 'The first PHAR' + ); + $this->addArgument( + self::SECOND_PHAR_ARG, + InputArgument::REQUIRED, + 'The second PHAR' + ); + + $this->addOption( + self::GNU_DIFF_OPTION, + 'd', + InputOption::VALUE_NONE, + 'Displays a GNU diff instead of the default git diff' + ); + $this->addOption( + self::CHECK_OPTION, + 'c', + InputOption::VALUE_OPTIONAL, + 'Verify the authenticity of the contents between the two PHARs with the given hash function.', + 'sha384' + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + (new PhpSettingsHandler(new ConsoleLogger($output)))->check(); + + $paths = [ + $input->getArgument(self::FIRST_PHAR_ARG), + $input->getArgument(self::SECOND_PHAR_ARG), + ]; + + Assertion::allFile($paths); + + $tmpFiles = []; + + try { + $diff = new PharDiff( + ...array_map( + function (string $path) use (&$tmpFiles): Pharaoh { + $path = false !== realpath($path) ? realpath($path) : $path; + + $tmpPath = $this->createTemporaryPhar($path); + + if ($path !== $tmpPath) { + $tmpFiles[] = $tmpPath; + } + + return new Pharaoh($tmpPath); + }, + $paths + ) + ); + $diff->setVerbose(true); + } catch (Throwable $throwable) { + if ($output->isDebug()) { + throw $throwable; + } + + $io->writeln( + sprintf( + 'Could not check the PHARs: %s', + $throwable->getMessage() + ) + ); + + return 1; + } finally { + remove($tmpFiles); + } + + if ($input->hasParameterOption(['-c', '--check'])) { + return $diff->listChecksums($input->getOption(self::CHECK_OPTION) ?? 'sha384'); + } + + if ($input->getOption(self::GNU_DIFF_OPTION)) { + return $diff->printGnuDiff(); + } + + return $diff->printGitDiff(); + } +} diff --git a/tests/Console/ApplicationTest.php b/tests/Console/ApplicationTest.php index 18b40a162..b1a5c5888 100644 --- a/tests/Console/ApplicationTest.php +++ b/tests/Console/ApplicationTest.php @@ -104,8 +104,9 @@ public function test_get_helper_menu(): void -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: - build Builds a new PHAR + build Builds a new PHAR (deprecated, use "compile" instead) compile Compile an application into a PHAR + diff Display the differences between all of the files in two PHARs help Displays help for a command info Displays information about the PHAR extension or file list Lists commands diff --git a/tests/Console/Command/DiffTest.php b/tests/Console/Command/DiffTest.php new file mode 100644 index 000000000..060e2ee88 --- /dev/null +++ b/tests/Console/Command/DiffTest.php @@ -0,0 +1,262 @@ + + * ThΓ©o Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box\Console\Command; + +use InvalidArgumentException; +use KevinGH\Box\Console\DisplayNormalizer; +use KevinGH\Box\Test\CommandTestCase; +use KevinGH\Box\Test\RequiresPharReadonlyOff; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Output\OutputInterface; +use UnexpectedValueException; +use function ob_get_clean; +use function ob_start; +use function realpath; + +/** + * @covers \KevinGH\Box\Console\Command\Diff + * + * @runTestsInSeparateProcesses + */ +class DiffTest extends CommandTestCase +{ + use RequiresPharReadonlyOff; + + private const FIXTURES_DIR = __DIR__.'/../../../fixtures/diff'; + + /** + * {@inheritdoc} + */ + public function setUp(): void + { + $this->markAsSkippedIfPharReadonlyIsOn(); + + parent::setUp(); + } + + /** + * {@inheritdoc} + */ + protected function getCommand(): Command + { + return new Diff(); + } + + public function test_it_displays_the_git_diff_by_default(): void + { + $pharPath = realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => $pharPath, + 'pharB' => $pharPath, + ] + ); + $actual = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $expected = <<<'OUTPUT' +No differences encountered. + +OUTPUT; + + $this->assertSame($expected, $actual); + $this->assertSame(0, $this->commandTester->getStatusCode()); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'), + 'pharB' => realpath(self::FIXTURES_DIR.'/simple-phar-bar.phar'), + ] + ); + $output = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $this->assertRegExp( + '/^diff --git a\/.+\/foo.php b\/.+\/bar.php\nsimilarity index 100%\nrename from \/.+\/foo.php\nrename to \/.+\/bar.php$/m', + $output + ); + $this->assertSame(1, $this->commandTester->getStatusCode()); + } + + public function test_it_can_display_the_GNU_diff_of_two_PHAR_files(): void + { + $pharPath = realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => $pharPath, + 'pharB' => $pharPath, + '--gnu-diff' => null, + ] + ); + $actual = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $expected = <<<'OUTPUT' +No differences encountered. + +OUTPUT; + + $this->assertSame($expected, $actual); + $this->assertSame(0, $this->commandTester->getStatusCode()); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'), + 'pharB' => realpath(self::FIXTURES_DIR.'/simple-phar-bar.phar'), + '--gnu-diff' => null, + ] + ); + $output = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $this->assertRegExp( + '/^Only in \/.+: bar\.php\nOnly in \/.+: foo\.php$/', + $output + ); + $this->assertSame(1, $this->commandTester->getStatusCode()); + } + + public function test_it_can_check_the_sum_of_two_PHAR_files(): void + { + $pharPath = realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => $pharPath, + 'pharB' => $pharPath, + '--check' => null, + ] + ); + $actual = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $expected = <<<'OUTPUT' +No differences encountered. + +OUTPUT; + + $this->assertSame($expected, $actual); + $this->assertSame(0, $this->commandTester->getStatusCode()); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'), + 'pharB' => realpath(self::FIXTURES_DIR.'/simple-phar-bar.phar'), + '--check' => null, + ] + ); + $output = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $expected = <<<'OUTPUT' +No differences encountered. + +OUTPUT; + + $this->assertSame($expected, $actual); + $this->assertSame(0, $this->commandTester->getStatusCode()); + } + + public function test_it_cannot_compare_non_existent_files(): void + { + try { + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => 'unknown', + 'pharB' => 'unknown', + ] + ); + + $this->fail('Expected exception to be thrown.'); + } catch (InvalidArgumentException $exception) { + $this->assertSame( + 'File "unknown" was expected to exist.', + $exception->getMessage() + ); + } + } + + public function test_it_cannot_compare_a_non_PHAR_files(): void + { + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'), + 'pharB' => $pharB = realpath(self::FIXTURES_DIR.'/not-a-phar.phar'), + ] + ); + + $expected = <<assertSame($expected, $this->commandTester->getDisplay(true)); + $this->assertSame(1, $this->commandTester->getStatusCode()); + } + + public function test_it_can_compare_PHAR_files_without_the_PHAR_extension(): void + { + $pharPath = realpath(self::FIXTURES_DIR.'/simple-phar'); + + ob_start(); + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => $pharPath, + 'pharB' => $pharPath, + ] + ); + $actual = DisplayNormalizer::removeTrailingSpaces(ob_get_clean()); + + $expected = <<<'OUTPUT' +No differences encountered. + +OUTPUT; + + $this->assertSame($expected, $actual); + $this->assertSame(0, $this->commandTester->getStatusCode()); + } + + public function test_it_does_not_swallow_exceptions_in_debug_mode(): void + { + try { + $this->commandTester->execute( + [ + 'command' => 'diff', + 'pharA' => realpath(self::FIXTURES_DIR.'/simple-phar-foo.phar'), + 'pharB' => $pharB = realpath(self::FIXTURES_DIR.'/not-a-phar.phar'), + ], + ['verbosity' => OutputInterface::VERBOSITY_DEBUG] + ); + + $this->fail('Expected exception to be thrown.'); + } catch (UnexpectedValueException $exception) { + $this->assertSame( + "internal corruption of phar \"$pharB\" (__HALT_COMPILER(); not found)", + $exception->getMessage() + ); + } + } +}