diff --git a/bin/pie b/bin/pie index 947208fa..dbf10420 100755 --- a/bin/pie +++ b/bin/pie @@ -14,6 +14,7 @@ use Php\Pie\Command\RepositoryAddCommand; use Php\Pie\Command\RepositoryListCommand; use Php\Pie\Command\RepositoryRemoveCommand; use Php\Pie\Command\SelfUpdateCommand; +use Php\Pie\Command\SelfVerifyCommand; use Php\Pie\Command\ShowCommand; use Php\Pie\Command\UninstallCommand; use Php\Pie\Util\PieVersion; @@ -45,6 +46,7 @@ $application->setCommandLoader(new ContainerCommandLoader( 'repository:remove' => RepositoryRemoveCommand::class, 'uninstall' => UninstallCommand::class, 'self-update' => SelfUpdateCommand::class, + 'self-verify' => SelfVerifyCommand::class, 'install-extensions-for-project' => InstallExtensionsForProjectCommand::class, ] )); diff --git a/src/Command/SelfUpdateCommand.php b/src/Command/SelfUpdateCommand.php index 4faf1e50..8f55101b 100644 --- a/src/Command/SelfUpdateCommand.php +++ b/src/Command/SelfUpdateCommand.php @@ -10,6 +10,7 @@ use Php\Pie\ComposerIntegration\PieComposerFactory; use Php\Pie\ComposerIntegration\PieComposerRequest; use Php\Pie\ComposerIntegration\QuieterConsoleIO; +use Php\Pie\File\FullPathToSelf; use Php\Pie\File\SudoFilePut; use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub; use Php\Pie\SelfManage\Update\ReleaseMetadata; @@ -24,14 +25,10 @@ use Symfony\Component\Console\Output\OutputInterface; use function file_get_contents; -use function getcwd; use function preg_match; -use function realpath; use function sprintf; use function unlink; -use const DIRECTORY_SEPARATOR; - #[AsCommand( name: 'self-update', description: 'Self update PIE', @@ -67,7 +64,7 @@ public function execute(InputInterface $input, OutputInterface $output): int if (! PieVersion::isPharBuild()) { $output->writeln('Aborting! You are not running a PHAR, cannot self-update.'); - return 1; + return Command::FAILURE; } $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); @@ -141,8 +138,7 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::FAILURE; } - $phpSelf = $_SERVER['PHP_SELF'] ?? ''; - $fullPathToSelf = $this->isAbsolutePath($phpSelf) ? $phpSelf : (getcwd() . DIRECTORY_SEPARATOR . $phpSelf); + $fullPathToSelf = (new FullPathToSelf())(); $output->writeln( sprintf('Writing new version to %s', $fullPathToSelf), OutputInterface::VERBOSITY_VERBOSE, @@ -153,21 +149,4 @@ public function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - - private function isAbsolutePath(string $path): bool - { - if (realpath($path) === $path) { - return true; - } - - if ($path === '' || $path === '.') { - return false; - } - - if (preg_match('#^[a-zA-Z]:\\\\#', $path)) { - return true; - } - - return $path[0] === '/' || $path[0] === '\\'; - } } diff --git a/src/Command/SelfVerifyCommand.php b/src/Command/SelfVerifyCommand.php new file mode 100644 index 00000000..b012c3e0 --- /dev/null +++ b/src/Command/SelfVerifyCommand.php @@ -0,0 +1,89 @@ +writeln('Aborting! You are not running a PHAR, cannot self-update.'); + + return Command::FAILURE; + } + + $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output); + $composer = PieComposerFactory::createPieComposer( + $this->container, + PieComposerRequest::noOperation( + $output, + $targetPlatform, + ), + ); + $httpDownloader = new HttpDownloader($this->io, $composer->getConfig()); + $authHelper = new AuthHelper($this->io, $composer->getConfig()); + $latestRelease = new ReleaseMetadata(PieVersion::get(), 'blah'); + $pharFilename = BinaryFile::fromFileWithSha256Checksum((new FullPathToSelf())()); + $verifyPiePhar = VerifyPieReleaseUsingAttestation::factory($this->githubApiBaseUrl, $httpDownloader, $authHelper); + + try { + $verifyPiePhar->verify($latestRelease, $pharFilename, $output); + } catch (FailedToVerifyRelease $failedToVerifyRelease) { + $output->writeln(sprintf( + '❌ Failed to verify the pie.phar release %s: %s', + $latestRelease->tag, + $failedToVerifyRelease->getMessage(), + )); + + return Command::FAILURE; + } + + $output->writeln(sprintf( + '✅ You are running an authentic PIE version %s.', + $latestRelease->tag, + )); + + return Command::SUCCESS; + } +} diff --git a/src/Container.php b/src/Container.php index 1e5c82c3..02233a06 100644 --- a/src/Container.php +++ b/src/Container.php @@ -18,6 +18,7 @@ use Php\Pie\Command\RepositoryListCommand; use Php\Pie\Command\RepositoryRemoveCommand; use Php\Pie\Command\SelfUpdateCommand; +use Php\Pie\Command\SelfVerifyCommand; use Php\Pie\Command\ShowCommand; use Php\Pie\Command\UninstallCommand; use Php\Pie\ComposerIntegration\MinimalHelperSet; @@ -84,6 +85,7 @@ static function (ConsoleCommandEvent $event) use (&$displayedBanner): void { $container->singleton(RepositoryRemoveCommand::class); $container->singleton(UninstallCommand::class); $container->singleton(SelfUpdateCommand::class); + $container->singleton(SelfVerifyCommand::class); $container->singleton(InstallExtensionsForProjectCommand::class); $container->singleton(QuieterConsoleIO::class, static function (ContainerInterface $container): QuieterConsoleIO { @@ -109,6 +111,10 @@ static function (ConsoleCommandEvent $event) use (&$displayedBanner): void { ->needs('$githubApiBaseUrl') ->give('https://api.github.com'); + $container->when(SelfVerifyCommand::class) + ->needs('$githubApiBaseUrl') + ->give('https://api.github.com'); + $container->singleton( Build::class, static function (ContainerInterface $container): Build { diff --git a/src/File/FullPathToSelf.php b/src/File/FullPathToSelf.php new file mode 100644 index 00000000..8f559120 --- /dev/null +++ b/src/File/FullPathToSelf.php @@ -0,0 +1,50 @@ +isAbsolutePath($phpSelf) + ? $phpSelf + : (getcwd() . DIRECTORY_SEPARATOR . $phpSelf); + } + + private function isAbsolutePath(string $path): bool + { + if (realpath($path) === $path) { + return true; + } + + if ($path === '' || $path === '.') { + return false; + } + + if (preg_match('#^[a-zA-Z]:\\\\#', $path)) { + return true; + } + + return $path[0] === '/' || $path[0] === '\\'; + } +} diff --git a/src/Installing/InstallForPhpProject/InstallSelectedPackage.php b/src/Installing/InstallForPhpProject/InstallSelectedPackage.php index 8d316b35..3245f754 100644 --- a/src/Installing/InstallForPhpProject/InstallSelectedPackage.php +++ b/src/Installing/InstallForPhpProject/InstallSelectedPackage.php @@ -5,18 +5,16 @@ namespace Php\Pie\Installing\InstallForPhpProject; use Php\Pie\Command\CommandHelper; +use Php\Pie\File\FullPathToSelf; use Php\Pie\Util\Process; -use RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use function array_filter; -use function array_key_exists; use function array_walk; use function getcwd; use function in_array; -use function is_string; use const ARRAY_FILTER_USE_BOTH; @@ -25,14 +23,8 @@ class InstallSelectedPackage { public function withPieCli(string $selectedPackage, InputInterface $input, OutputInterface $output): void { - /** @psalm-suppress TypeDoesNotContainType */ - $self = array_key_exists('PHP_SELF', $_SERVER) && is_string($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : ''; - if ($self === '') { - throw new RuntimeException('Could not find PHP_SELF'); - } - $process = [ - $self, + (new FullPathToSelf())(), 'install', $selectedPackage, ]; diff --git a/src/Util/PieVersion.php b/src/Util/PieVersion.php index 56166b9c..ab1585fd 100644 --- a/src/Util/PieVersion.php +++ b/src/Util/PieVersion.php @@ -45,6 +45,8 @@ public static function isPharBuild(): bool * realistic-looking version; usually either a tag (e.g. `2.0.0`), or a tag * and following commit short hash (e.g. `2.0.0@e558e33`). If not this will * fall back to some other techniques to try to determine a version. + * + * @return non-empty-string */ public static function get(): string { @@ -62,7 +64,7 @@ public static function get(): string * likely be something like `dev-main` (branch name). */ $installedVersion = InstalledVersions::getVersion(InstalledVersions::getRootPackage()['name']); - if ($installedVersion === null) { + if ($installedVersion === null || $installedVersion === '') { return self::SYMFONY_MAGIC_CONST_UNKNOWN; }