humbug /
box
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | /* |
||||
| 6 | * This file is part of the box project. |
||||
| 7 | * |
||||
| 8 | * (c) Kevin Herrera <[email protected]> |
||||
| 9 | * Théo Fidry <[email protected]> |
||||
| 10 | * |
||||
| 11 | * This source file is subject to the MIT license that is bundled |
||||
| 12 | * with this source code in the file LICENSE. |
||||
| 13 | */ |
||||
| 14 | |||||
| 15 | namespace KevinGH\Box\Console\Command; |
||||
| 16 | |||||
| 17 | use Fidry\Console\Command\Command; |
||||
| 18 | use Fidry\Console\Command\Configuration; |
||||
| 19 | use Fidry\Console\ExitCode; |
||||
| 20 | use Fidry\Console\IO; |
||||
|
0 ignored issues
–
show
|
|||||
| 21 | use Fidry\FileSystem\FS; |
||||
| 22 | use KevinGH\Box\Console\Php\PhpSettingsChecker; |
||||
| 23 | use KevinGH\Box\Phar\PharFactory; |
||||
| 24 | use KevinGH\Box\Phar\PharMeta; |
||||
|
0 ignored issues
–
show
The type
KevinGH\Box\Phar\PharMeta was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||||
| 25 | use KevinGH\Box\Phar\Throwable\InvalidPhar; |
||||
| 26 | use Symfony\Component\Console\Input\InputArgument; |
||||
| 27 | use Symfony\Component\Console\Input\InputOption; |
||||
| 28 | use Symfony\Component\Console\Question\ConfirmationQuestion; |
||||
| 29 | use Throwable; |
||||
| 30 | use function bin2hex; |
||||
| 31 | use function file_exists; |
||||
| 32 | use function realpath; |
||||
| 33 | use function sprintf; |
||||
| 34 | use const DIRECTORY_SEPARATOR; |
||||
| 35 | |||||
| 36 | /** |
||||
| 37 | * @private |
||||
| 38 | */ |
||||
| 39 | final class Extract implements Command |
||||
| 40 | { |
||||
| 41 | public const STUB_PATH = '.phar/stub.php'; |
||||
| 42 | public const PHAR_META_PATH = '.phar/meta.json'; |
||||
| 43 | |||||
| 44 | private const PHAR_ARG = 'phar'; |
||||
| 45 | private const OUTPUT_ARG = 'output'; |
||||
| 46 | private const INTERNAL_OPT = 'internal'; |
||||
| 47 | |||||
| 48 | public function getConfiguration(): Configuration |
||||
| 49 | { |
||||
| 50 | return new Configuration( |
||||
| 51 | 'extract', |
||||
| 52 | '🚚 Extracts a given PHAR into a directory', |
||||
| 53 | '', |
||||
| 54 | [ |
||||
| 55 | new InputArgument( |
||||
| 56 | self::PHAR_ARG, |
||||
| 57 | InputArgument::REQUIRED, |
||||
| 58 | 'The path to the PHAR file', |
||||
| 59 | ), |
||||
| 60 | new InputArgument( |
||||
| 61 | self::OUTPUT_ARG, |
||||
| 62 | InputArgument::REQUIRED, |
||||
| 63 | 'The output directory', |
||||
| 64 | ), |
||||
| 65 | ], |
||||
| 66 | [ |
||||
| 67 | new InputOption( |
||||
| 68 | self::INTERNAL_OPT, |
||||
| 69 | null, |
||||
| 70 | InputOption::VALUE_NONE, |
||||
| 71 | 'Internal option; Should not be used.', |
||||
| 72 | ), |
||||
| 73 | ], |
||||
| 74 | ); |
||||
| 75 | } |
||||
| 76 | |||||
| 77 | public function execute(IO $io): int |
||||
| 78 | { |
||||
| 79 | PhpSettingsChecker::check($io); |
||||
| 80 | |||||
| 81 | $pharPath = self::getPharFilePath($io); |
||||
| 82 | $outputDir = $io->getTypedArgument(self::OUTPUT_ARG)->asNonEmptyString(); |
||||
| 83 | $internal = $io->getTypedOption(self::INTERNAL_OPT)->asBoolean(); |
||||
| 84 | |||||
| 85 | if (null === $pharPath) { |
||||
| 86 | return ExitCode::FAILURE; |
||||
| 87 | } |
||||
| 88 | |||||
| 89 | if (file_exists($outputDir)) { |
||||
| 90 | $canDelete = $io->askQuestion( |
||||
| 91 | new ConfirmationQuestion( |
||||
| 92 | 'The output directory already exists. Do you want to delete its current content?', |
||||
| 93 | // If is interactive, we want the prompt to default to false since it can be an error made by the user. |
||||
| 94 | // Otherwise, this is likely launched by a script or Pharaoh in which case we do not care. |
||||
| 95 | $internal, |
||||
| 96 | ), |
||||
| 97 | ); |
||||
| 98 | |||||
| 99 | if ($canDelete) { |
||||
| 100 | FS::remove($outputDir); |
||||
| 101 | // Continue |
||||
| 102 | } else { |
||||
| 103 | // Do nothing |
||||
| 104 | return ExitCode::FAILURE; |
||||
| 105 | } |
||||
| 106 | } |
||||
| 107 | |||||
| 108 | FS::mkdir($outputDir); |
||||
| 109 | |||||
| 110 | try { |
||||
| 111 | self::dumpPhar($pharPath, $outputDir); |
||||
| 112 | } catch (InvalidPhar $invalidPhar) { |
||||
| 113 | if (!$internal) { |
||||
| 114 | throw $invalidPhar; |
||||
| 115 | } |
||||
| 116 | |||||
| 117 | $io->getErrorIO()->write($invalidPhar->getMessage()); |
||||
| 118 | |||||
| 119 | return ExitCode::FAILURE; |
||||
| 120 | } |
||||
| 121 | |||||
| 122 | return ExitCode::SUCCESS; |
||||
| 123 | } |
||||
| 124 | |||||
| 125 | private static function getPharFilePath(IO $io): ?string |
||||
| 126 | { |
||||
| 127 | $filePath = realpath($io->getTypedArgument(self::PHAR_ARG)->asString()); |
||||
| 128 | |||||
| 129 | if (false !== $filePath) { |
||||
| 130 | return $filePath; |
||||
| 131 | } |
||||
| 132 | |||||
| 133 | $io->error( |
||||
| 134 | sprintf( |
||||
| 135 | 'The file "%s" could not be found.', |
||||
| 136 | $io->getTypedArgument(self::PHAR_ARG)->asRaw(), |
||||
| 137 | ), |
||||
| 138 | ); |
||||
| 139 | |||||
| 140 | return null; |
||||
| 141 | } |
||||
| 142 | |||||
| 143 | private static function dumpPhar(string $file, string $tmpDir): string |
||||
| 144 | { |
||||
| 145 | // We have to give every one a different alias, or it pukes. |
||||
| 146 | $alias = self::generateAlias($file); |
||||
| 147 | |||||
| 148 | // Create a temporary PHAR: this is because the extension might be |
||||
| 149 | // missing in which case we would not be able to create a Phar instance |
||||
| 150 | // as it requires the .phar extension. |
||||
| 151 | $tmpFile = $tmpDir.DIRECTORY_SEPARATOR.$alias; |
||||
| 152 | $pubKey = $file.'.pubkey'; |
||||
| 153 | $pubKeyContent = null; |
||||
| 154 | $tmpPubKey = $tmpFile.'.pubkey'; |
||||
| 155 | $stub = $tmpDir.DIRECTORY_SEPARATOR.self::STUB_PATH; |
||||
| 156 | |||||
| 157 | try { |
||||
| 158 | FS::copy($file, $tmpFile, true); |
||||
| 159 | |||||
| 160 | if (file_exists($pubKey)) { |
||||
| 161 | FS::copy($pubKey, $tmpPubKey, true); |
||||
| 162 | $pubKeyContent = FS::getFileContents($pubKey); |
||||
| 163 | } |
||||
| 164 | |||||
| 165 | $phar = PharFactory::create($tmpFile, $file); |
||||
| 166 | $pharMeta = PharMeta::fromPhar($phar, $pubKeyContent); |
||||
| 167 | |||||
| 168 | $phar->extractTo($tmpDir); |
||||
| 169 | FS::dumpFile($stub, $phar->getStub()); |
||||
| 170 | } catch (Throwable $throwable) { |
||||
| 171 | FS::remove([$tmpFile, $tmpPubKey]); |
||||
| 172 | |||||
| 173 | throw $throwable; |
||||
| 174 | } |
||||
| 175 | |||||
| 176 | FS::dumpFile( |
||||
| 177 | $tmpDir.DIRECTORY_SEPARATOR.self::PHAR_META_PATH, |
||||
| 178 | $pharMeta->toJson(), |
||||
| 179 | ); |
||||
| 180 | |||||
| 181 | // Cleanup the temporary PHAR. |
||||
| 182 | FS::remove([$tmpFile, $tmpPubKey]); |
||||
| 183 | |||||
| 184 | return $tmpDir; |
||||
| 185 | } |
||||
| 186 | |||||
| 187 | private static function generateAlias(string $file): string |
||||
| 188 | { |
||||
| 189 | $extension = self::getExtension($file); |
||||
| 190 | |||||
| 191 | return bin2hex(random_bytes(16)).$extension; |
||||
| 192 | } |
||||
| 193 | |||||
| 194 | private static function getExtension(string $file): string |
||||
| 195 | { |
||||
| 196 | $lastExtension = pathinfo($file, PATHINFO_EXTENSION); |
||||
| 197 | $extension = ''; |
||||
| 198 | |||||
| 199 | while ('' !== $lastExtension) { |
||||
| 200 | $extension = '.'.$lastExtension.$extension; |
||||
|
0 ignored issues
–
show
Are you sure
$lastExtension of type array|string can be used in concatenation?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 201 | $file = mb_substr($file, 0, -(mb_strlen($lastExtension) + 1)); |
||||
|
0 ignored issues
–
show
It seems like
$lastExtension can also be of type array; however, parameter $string of mb_strlen() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 202 | $lastExtension = pathinfo($file, PATHINFO_EXTENSION); |
||||
| 203 | } |
||||
| 204 | |||||
| 205 | return '' === $extension ? '.phar' : $extension; |
||||
| 206 | } |
||||
| 207 | } |
||||
| 208 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths