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 ![]() |
|||||
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
![]() |
|||||
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
![]() |
|||||
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