Skip to content

Commit

Permalink
feat: Add a info:signature command
Browse files Browse the repository at this point in the history
  • Loading branch information
theofidry committed Nov 27, 2023
1 parent 724096c commit 1e21b37
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 3 deletions.
4 changes: 2 additions & 2 deletions doc/reproducible-builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ Deterministic builds are a highly desirable property to prevent targeted malware
detect if there is any real change. As non-exhaustive examples in the wild: [Composer][composer] and [PHPStan][phpstan].

Another benefit of such builds is that it makes it easier to know if there was any change. You can know if two PHARs are
identical by using the `box diff` command, or extract the signature out of the `box info` command:
identical by using the `box diff` command, or extract the signature out of the `box info:signature` command:

```shell
box info app.phar | grep 'Signature Hash' | cut -d ' ' -f3
box info:signature app.phar
```

And re-use that signature later for comparison. You will loose the ability to do a detailed diff between the two PHARs,
Expand Down
2 changes: 2 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public function getCommands(): array
new Compile($this->getHeader()),
new Diff(),
new Info(),
new Info('info:general'),
new Info\Signature(),
new Process(),
new Extract(),
new Validate(),
Expand Down
6 changes: 5 additions & 1 deletion src/Console/Command/Info.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ final class Info implements Command
'flat',
];

public function __construct(private string $commandName = 'info')
{
}

public function getConfiguration(): Configuration
{
return new Configuration(
'info',
$this->commandName,
'🔍 Displays information about the PHAR extension or file',
<<<'HELP'
The <info>%command.name%</info> command will display information about the Phar extension,
Expand Down
79 changes: 79 additions & 0 deletions src/Console/Command/Info/Signature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

/*
* This file is part of the box project.
*
* (c) Kevin Herrera <[email protected]>
* Théo Fidry <[email protected]>
*
* 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\Info;

use Fidry\Console\Command\Command;
use Fidry\Console\Command\Configuration;
use Fidry\Console\ExitCode;
use Fidry\Console\IO;
use KevinGH\Box\Phar\PharInfo;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Filesystem\Path;
use function realpath;
use function sprintf;

/**
* @private
*/
final class Signature implements Command
{
private const PHAR_ARG = 'phar';

public function getConfiguration(): Configuration
{
return new Configuration(
'info:signature',
'🔍 Displays the hash of the signature',
<<<'HELP'
The <info>%command.name%</info> command will display the hash of the signature. This may
be useful for some external tools that wishes to act base on the signature, e.g. to invalidate
some cache or compare signatures.
HELP,
[
new InputArgument(
self::PHAR_ARG,
InputArgument::REQUIRED,
'The PHAR file.',
),
],
);
}

public function execute(IO $io): int
{
$file = $io->getTypedArgument(self::PHAR_ARG)->asNonEmptyString();

$file = Path::canonicalize($file);
$fileRealPath = realpath($file);

$pharInfo = new PharInfo($file);
$signature = $pharInfo->getSignature();

if (null === $signature) {
$io->error(
sprintf(
'The file "%s" is not a PHAR.',
$file,
),
);

return ExitCode::FAILURE;
}

$io->writeln($signature['hash']);

return ExitCode::SUCCESS;
}
}
109 changes: 109 additions & 0 deletions tests/Console/Command/Info/SignatureTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

/*
* This file is part of the box project.
*
* (c) Kevin Herrera <[email protected]>
* Théo Fidry <[email protected]>
*
* 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\Info;

use Fidry\Console\Command\Command;
use Fidry\Console\ExitCode;
use Fidry\Console\Test\OutputAssertions;
use KevinGH\Box\Phar\InvalidPhar;
use KevinGH\Box\Test\CommandTestCase;
use Symfony\Component\Filesystem\Path;

/**
* @covers \KevinGH\Box\Console\Command\Info\Signature
*
* @internal
*/
class SignatureTest extends CommandTestCase
{
private const FIXTURES = __DIR__.'/../../../../fixtures/phar';

protected function getCommand(): Command
{
return new Signature();
}

public function test_it_provides_the_phar_signature(): void
{
$pharPath = self::FIXTURES.'/simple-phar.phar';

$this->commandTester->execute([
'command' => 'info:signature',
'phar' => $pharPath,
]);

OutputAssertions::assertSameOutput(
<<<'EOF'
55AE0CCD6D3A74BE41E19CD070A655A73FEAEF8342084A0801954943FBF219ED

EOF,
ExitCode::SUCCESS,
$this->commandTester,
);
}

public function test_it_cannot_provide_info_about_a_non_existent_phar(): void
{
$file = self::FIXTURES.'/foo';

$this->expectException(InvalidPhar::class);

$this->commandTester->execute(
[
'command' => 'info:signature',
'phar' => $file,
],
);
}

public function test_it_cannot_provide_info_about_an_non_phar_archive(): void
{
$file = self::FIXTURES.'/simple.zip';
$expectedFilePath = Path::canonicalize($file);

$this->commandTester->execute(
[
'command' => 'info:signature',
'phar' => $file,
],
);

OutputAssertions::assertSameOutput(
<<<EOF
[ERROR] The file "{$expectedFilePath}" is
not a PHAR.
EOF,
ExitCode::FAILURE,
$this->commandTester,
);
}

public function test_it_cannot_provide_info_about_an_invalid_phar(): void
{
$file = self::FIXTURES.'/empty-pdf.pdf';

$this->expectException(InvalidPhar::class);

$this->commandTester->execute(
[
'command' => 'info:signature',
'phar' => $file,
],
);
}
}

0 comments on commit 1e21b37

Please sign in to comment.