Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/pie
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
]
));
Expand Down
27 changes: 3 additions & 24 deletions src/Command/SelfUpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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',
Expand Down Expand Up @@ -67,7 +64,7 @@ public function execute(InputInterface $input, OutputInterface $output): int
if (! PieVersion::isPharBuild()) {
$output->writeln('<comment>Aborting! You are not running a PHAR, cannot self-update.</comment>');

return 1;
return Command::FAILURE;
}

$targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
Expand Down Expand Up @@ -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,
Expand All @@ -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] === '\\';
}
}
89 changes: 89 additions & 0 deletions src/Command/SelfVerifyCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\SelfManage\Verify\FailedToVerifyRelease;
use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation;
use Php\Pie\Util\PieVersion;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
name: 'self-verify',
description: 'Self verify PIE',
)]
final class SelfVerifyCommand extends Command
{
/** @param non-empty-string $githubApiBaseUrl */
public function __construct(
private readonly string $githubApiBaseUrl,
private readonly QuieterConsoleIO $io,
private readonly ContainerInterface $container,
) {
parent::__construct();
}

public function configure(): void
{
parent::configure();

CommandHelper::configurePhpConfigOptions($this);
}

public function execute(InputInterface $input, OutputInterface $output): int
{
if (! PieVersion::isPharBuild()) {
$output->writeln('<comment>Aborting! You are not running a PHAR, cannot self-update.</comment>');

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(
'<error>❌ Failed to verify the pie.phar release %s: %s</error>',
$latestRelease->tag,
$failedToVerifyRelease->getMessage(),
));

return Command::FAILURE;
}

$output->writeln(sprintf(
'<info>✅ You are running an authentic PIE version %s.</info>',
$latestRelease->tag,
));

return Command::SUCCESS;
}
}
6 changes: 6 additions & 0 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions src/File/FullPathToSelf.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function array_key_exists;
use function getcwd;
use function is_string;
use function preg_match;
use function realpath;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class FullPathToSelf
{
/** @return non-empty-string */
public function __invoke(): string
{
/** @psalm-suppress TypeDoesNotContainType */
$phpSelf = array_key_exists('PHP_SELF', $_SERVER) && is_string($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
if ($phpSelf === '') {
throw new RuntimeException('Could not find PHP_SELF');
}

return $this->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] === '\\';
}
}
12 changes: 2 additions & 10 deletions src/Installing/InstallForPhpProject/InstallSelectedPackage.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
];
Expand Down
4 changes: 3 additions & 1 deletion src/Util/PieVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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;
}

Expand Down