This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Stecman\Component\Symfony\Console\BashCompletion; |
||
4 | |||
5 | use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface; |
||
6 | use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionInterface; |
||
7 | use Symfony\Component\Console\Application; |
||
8 | use Symfony\Component\Console\Command\Command; |
||
9 | use Symfony\Component\Console\Input\ArrayInput; |
||
10 | use Symfony\Component\Console\Input\InputArgument; |
||
11 | use Symfony\Component\Console\Input\InputOption; |
||
12 | |||
13 | class CompletionHandler |
||
14 | { |
||
15 | /** |
||
16 | * Application to complete for |
||
17 | * @var \Symfony\Component\Console\Application |
||
18 | */ |
||
19 | protected $application; |
||
20 | |||
21 | /** |
||
22 | * @var Command |
||
23 | */ |
||
24 | protected $command; |
||
25 | |||
26 | /** |
||
27 | * @var CompletionContext |
||
28 | */ |
||
29 | protected $context; |
||
30 | |||
31 | /** |
||
32 | * Array of completion helpers. |
||
33 | * @var CompletionInterface[] |
||
34 | */ |
||
35 | protected $helpers = array(); |
||
36 | |||
37 | /** |
||
38 | * Index the command name was detected at |
||
39 | * @var int |
||
40 | */ |
||
41 | private $commandWordIndex; |
||
42 | |||
43 | public function __construct(Application $application, CompletionContext $context = null) |
||
44 | { |
||
45 | $this->application = $application; |
||
46 | $this->context = $context; |
||
47 | |||
48 | // Set up completions for commands that are built-into Application |
||
49 | $this->addHandler( |
||
50 | new Completion( |
||
51 | 'help', |
||
52 | 'command_name', |
||
53 | Completion::TYPE_ARGUMENT, |
||
54 | $this->getCommandNames() |
||
55 | ) |
||
56 | ); |
||
57 | |||
58 | $this->addHandler( |
||
59 | new Completion( |
||
60 | 'list', |
||
61 | 'namespace', |
||
62 | Completion::TYPE_ARGUMENT, |
||
63 | $application->getNamespaces() |
||
64 | ) |
||
65 | ); |
||
66 | } |
||
67 | |||
68 | public function setContext(CompletionContext $context) |
||
69 | { |
||
70 | $this->context = $context; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * @return CompletionContext |
||
75 | */ |
||
76 | public function getContext() |
||
77 | { |
||
78 | return $this->context; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * @param CompletionInterface[] $array |
||
83 | */ |
||
84 | public function addHandlers(array $array) |
||
85 | { |
||
86 | $this->helpers = array_merge($this->helpers, $array); |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * @param CompletionInterface $helper |
||
91 | */ |
||
92 | public function addHandler(CompletionInterface $helper) |
||
93 | { |
||
94 | $this->helpers[] = $helper; |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Do the actual completion, returning an array of strings to provide to the parent shell's completion system |
||
99 | * |
||
100 | * @throws \RuntimeException |
||
101 | * @return string[] |
||
102 | */ |
||
103 | public function runCompletion() |
||
104 | { |
||
105 | if (!$this->context) { |
||
106 | throw new \RuntimeException('A CompletionContext must be set before requesting completion.'); |
||
107 | } |
||
108 | |||
109 | // Set the command to query options and arugments from |
||
110 | $this->command = $this->detectCommand(); |
||
111 | |||
112 | $process = array( |
||
113 | 'completeForOptionValues', |
||
114 | 'completeForOptionShortcuts', |
||
115 | 'completeForOptionShortcutValues', |
||
116 | 'completeForOptions', |
||
117 | 'completeForCommandName', |
||
118 | 'completeForCommandArguments' |
||
119 | ); |
||
120 | |||
121 | foreach ($process as $methodName) { |
||
122 | $result = $this->{$methodName}(); |
||
123 | |||
124 | if (false !== $result) { |
||
125 | // Return the result of the first completion mode that matches |
||
126 | return $this->filterResults((array) $result); |
||
127 | } |
||
128 | } |
||
129 | |||
130 | return array(); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Get an InputInterface representation of the completion context |
||
135 | * |
||
136 | * @deprecated Incorrectly uses the ArrayInput API and is no longer needed. |
||
137 | * This will be removed in the next major version. |
||
138 | * |
||
139 | * @return ArrayInput |
||
140 | */ |
||
141 | public function getInput() |
||
142 | { |
||
143 | // Filter the command line content to suit ArrayInput |
||
144 | $words = $this->context->getWords(); |
||
145 | array_shift($words); |
||
146 | $words = array_filter($words); |
||
147 | |||
148 | return new ArrayInput($words); |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Attempt to complete the current word as a long-form option (--my-option) |
||
153 | * |
||
154 | * @return array|false |
||
155 | */ |
||
156 | protected function completeForOptions() |
||
157 | { |
||
158 | $word = $this->context->getCurrentWord(); |
||
159 | |||
160 | if (substr($word, 0, 2) === '--') { |
||
161 | $options = array(); |
||
162 | |||
163 | foreach ($this->getAllOptions() as $opt) { |
||
164 | $options[] = '--'.$opt->getName(); |
||
165 | } |
||
166 | |||
167 | return $options; |
||
168 | } |
||
169 | |||
170 | return false; |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Attempt to complete the current word as an option shortcut. |
||
175 | * |
||
176 | * If the shortcut exists it will be completed, but a list of possible shortcuts is never returned for completion. |
||
177 | * |
||
178 | * @return array|false |
||
179 | */ |
||
180 | protected function completeForOptionShortcuts() |
||
181 | { |
||
182 | $word = $this->context->getCurrentWord(); |
||
183 | |||
184 | if (strpos($word, '-') === 0 && strlen($word) == 2) { |
||
185 | $definition = $this->command ? $this->command->getNativeDefinition() : $this->application->getDefinition(); |
||
186 | |||
187 | if ($definition->hasShortcut(substr($word, 1))) { |
||
188 | return array($word); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | return false; |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * Attempt to complete the current word as the value of an option shortcut |
||
197 | * |
||
198 | * @return array|false |
||
199 | */ |
||
200 | View Code Duplication | protected function completeForOptionShortcutValues() |
|
201 | { |
||
202 | $wordIndex = $this->context->getWordIndex(); |
||
203 | |||
204 | if ($this->command && $wordIndex > 1) { |
||
205 | $left = $this->context->getWordAtIndex($wordIndex - 1); |
||
206 | |||
207 | // Complete short options |
||
208 | if ($left[0] == '-' && strlen($left) == 2) { |
||
209 | $shortcut = substr($left, 1); |
||
210 | $def = $this->command->getNativeDefinition(); |
||
211 | |||
212 | if (!$def->hasShortcut($shortcut)) { |
||
213 | return false; |
||
214 | } |
||
215 | |||
216 | $opt = $def->getOptionForShortcut($shortcut); |
||
217 | if ($opt->isValueRequired() || $opt->isValueOptional()) { |
||
218 | return $this->completeOption($opt); |
||
219 | } |
||
220 | } |
||
221 | } |
||
222 | |||
223 | return false; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Attemp to complete the current word as the value of a long-form option |
||
228 | * |
||
229 | * @return array|false |
||
230 | */ |
||
231 | View Code Duplication | protected function completeForOptionValues() |
|
232 | { |
||
233 | $wordIndex = $this->context->getWordIndex(); |
||
234 | |||
235 | if ($this->command && $wordIndex > 1) { |
||
236 | $left = $this->context->getWordAtIndex($wordIndex - 1); |
||
237 | |||
238 | if (strpos($left, '--') === 0) { |
||
239 | $name = substr($left, 2); |
||
240 | $def = $this->command->getNativeDefinition(); |
||
241 | |||
242 | if (!$def->hasOption($name)) { |
||
243 | return false; |
||
244 | } |
||
245 | |||
246 | $opt = $def->getOption($name); |
||
247 | if ($opt->isValueRequired() || $opt->isValueOptional()) { |
||
248 | return $this->completeOption($opt); |
||
249 | } |
||
250 | } |
||
251 | } |
||
252 | |||
253 | return false; |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * Attempt to complete the current word as a command name |
||
258 | * |
||
259 | * @return array|false |
||
260 | */ |
||
261 | protected function completeForCommandName() |
||
262 | { |
||
263 | if (!$this->command || $this->context->getWordIndex() == $this->commandWordIndex) { |
||
264 | return $this->getCommandNames(); |
||
265 | } |
||
266 | |||
267 | return false; |
||
268 | } |
||
269 | |||
270 | /** |
||
271 | * Attempt to complete the current word as a command argument value |
||
272 | * |
||
273 | * @see Symfony\Component\Console\Input\InputArgument |
||
274 | * @return array|false |
||
275 | */ |
||
276 | protected function completeForCommandArguments() |
||
277 | { |
||
278 | if (!$this->command || strpos($this->context->getCurrentWord(), '-') === 0) { |
||
279 | return false; |
||
280 | } |
||
281 | |||
282 | $definition = $this->command->getNativeDefinition(); |
||
283 | $argWords = $this->mapArgumentsToWords($definition->getArguments()); |
||
284 | $wordIndex = $this->context->getWordIndex(); |
||
285 | |||
286 | if (isset($argWords[$wordIndex])) { |
||
287 | $name = $argWords[$wordIndex]; |
||
288 | } elseif (!empty($argWords) && $definition->getArgument(end($argWords))->isArray()) { |
||
289 | $name = end($argWords); |
||
290 | } else { |
||
291 | return false; |
||
292 | } |
||
293 | |||
294 | if ($helper = $this->getCompletionHelper($name, Completion::TYPE_ARGUMENT)) { |
||
295 | return $helper->run(); |
||
296 | } |
||
297 | |||
298 | if ($this->command instanceof CompletionAwareInterface) { |
||
299 | return $this->command->completeArgumentValues($name, $this->context); |
||
300 | } |
||
301 | |||
302 | return false; |
||
303 | } |
||
304 | |||
305 | /** |
||
306 | * Find a CompletionInterface that matches the current command, target name, and target type |
||
307 | * |
||
308 | * @param string $name |
||
309 | * @param string $type |
||
310 | * @return CompletionInterface|null |
||
311 | */ |
||
312 | protected function getCompletionHelper($name, $type) |
||
313 | { |
||
314 | foreach ($this->helpers as $helper) { |
||
315 | if ($helper->getType() != $type && $helper->getType() != CompletionInterface::ALL_TYPES) { |
||
316 | continue; |
||
317 | } |
||
318 | |||
319 | if ($helper->getCommandName() == CompletionInterface::ALL_COMMANDS || $helper->getCommandName() == $this->command->getName()) { |
||
320 | if ($helper->getTargetName() == $name) { |
||
321 | return $helper; |
||
322 | } |
||
323 | } |
||
324 | } |
||
325 | |||
326 | return null; |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Complete the value for the given option if a value completion is availble |
||
331 | * |
||
332 | * @param InputOption $option |
||
333 | * @return array|false |
||
334 | */ |
||
335 | protected function completeOption(InputOption $option) |
||
336 | { |
||
337 | if ($helper = $this->getCompletionHelper($option->getName(), Completion::TYPE_OPTION)) { |
||
338 | return $helper->run(); |
||
339 | } |
||
340 | |||
341 | if ($this->command instanceof CompletionAwareInterface) { |
||
342 | return $this->command->completeOptionValues($option->getName(), $this->context); |
||
343 | } |
||
344 | |||
345 | return false; |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Step through the command line to determine which word positions represent which argument values |
||
350 | * |
||
351 | * The word indexes of argument values are found by eliminating words that are known to not be arguments (options, |
||
352 | * option values, and command names). Any word that doesn't match for elimination is assumed to be an argument value, |
||
353 | * |
||
354 | * @param InputArgument[] $argumentDefinitions |
||
355 | * @return array as [argument name => word index on command line] |
||
356 | */ |
||
357 | protected function mapArgumentsToWords($argumentDefinitions) |
||
358 | { |
||
359 | $argumentPositions = array(); |
||
360 | $argumentNumber = 0; |
||
361 | $previousWord = null; |
||
362 | $argumentNames = array_keys($argumentDefinitions); |
||
363 | |||
364 | // Build a list of option values to filter out |
||
365 | $optionsWithArgs = $this->getOptionWordsWithValues(); |
||
366 | |||
367 | foreach ($this->context->getWords() as $wordIndex => $word) { |
||
368 | // Skip program name, command name, options, and option values |
||
369 | if ($wordIndex == 0 |
||
370 | || $wordIndex === $this->commandWordIndex |
||
371 | || ($word && '-' === $word[0]) |
||
372 | || in_array($previousWord, $optionsWithArgs)) { |
||
373 | $previousWord = $word; |
||
374 | continue; |
||
375 | } else { |
||
376 | $previousWord = $word; |
||
377 | } |
||
378 | |||
379 | // If argument n exists, pair that argument's name with the current word |
||
380 | if (isset($argumentNames[$argumentNumber])) { |
||
381 | $argumentPositions[$wordIndex] = $argumentNames[$argumentNumber]; |
||
382 | } |
||
383 | |||
384 | $argumentNumber++; |
||
385 | } |
||
386 | |||
387 | return $argumentPositions; |
||
388 | } |
||
389 | |||
390 | /** |
||
391 | * Build a list of option words/flags that will have a value after them |
||
392 | * Options are returned in the format they appear as on the command line. |
||
393 | * |
||
394 | * @return string[] - eg. ['--myoption', '-m', ... ] |
||
395 | */ |
||
396 | protected function getOptionWordsWithValues() |
||
397 | { |
||
398 | $strings = array(); |
||
399 | |||
400 | foreach ($this->getAllOptions() as $option) { |
||
401 | if ($option->isValueRequired()) { |
||
402 | $strings[] = '--' . $option->getName(); |
||
403 | |||
404 | if ($option->getShortcut()) { |
||
405 | $strings[] = '-' . $option->getShortcut(); |
||
406 | } |
||
407 | } |
||
408 | } |
||
409 | |||
410 | return $strings; |
||
411 | } |
||
412 | |||
413 | /** |
||
414 | * Filter out results that don't match the current word on the command line |
||
415 | * |
||
416 | * @param string[] $array |
||
417 | * @return string[] |
||
418 | */ |
||
419 | protected function filterResults(array $array) |
||
420 | { |
||
421 | $curWord = $this->context->getCurrentWord(); |
||
422 | |||
423 | return array_filter($array, function($val) use ($curWord) { |
||
424 | return fnmatch($curWord.'*', $val); |
||
425 | }); |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Get the combined options of the application and entered command |
||
430 | * |
||
431 | * @return InputOption[] |
||
432 | */ |
||
433 | protected function getAllOptions() |
||
434 | { |
||
435 | if (!$this->command) { |
||
436 | return $this->application->getDefinition()->getOptions(); |
||
437 | } |
||
438 | |||
439 | return array_merge( |
||
440 | $this->command->getNativeDefinition()->getOptions(), |
||
441 | $this->application->getDefinition()->getOptions() |
||
442 | ); |
||
443 | } |
||
444 | |||
445 | /** |
||
446 | * Get command names available for completion |
||
447 | * |
||
448 | * Filters out hidden commands where supported. |
||
449 | * |
||
450 | * @return string[] |
||
451 | */ |
||
452 | protected function getCommandNames() |
||
453 | { |
||
454 | // Command::Hidden isn't supported before Symfony Console 3.2.0 |
||
455 | // We don't complete hidden command names as these are intended to be private |
||
456 | if (method_exists('\Symfony\Component\Console\Command\Command', 'isHidden')) { |
||
457 | $commands = array(); |
||
458 | |||
459 | foreach ($this->application->all() as $name => $command) { |
||
460 | if (!$command->isHidden()) { |
||
461 | $commands[] = $name; |
||
462 | } |
||
463 | } |
||
464 | |||
465 | return $commands; |
||
466 | |||
467 | } else { |
||
468 | |||
469 | // Fallback for compatibility with Symfony Console < 3.2.0 |
||
470 | // This was the behaviour prior to pull #75 |
||
471 | $commands = $this->application->all(); |
||
472 | unset($commands['_completion']); |
||
473 | |||
474 | return array_keys($commands); |
||
475 | } |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Find the current command name in the command-line |
||
480 | * |
||
481 | * Note this only cares about flag-type options. Options with values cannot |
||
482 | * appear before a command name in Symfony Console application. |
||
483 | * |
||
484 | * @return Command|null |
||
485 | */ |
||
486 | private function detectCommand() |
||
487 | { |
||
488 | // Always skip the first word (program name) |
||
489 | $skipNext = true; |
||
490 | |||
491 | foreach ($this->context->getWords() as $index => $word) { |
||
492 | |||
493 | // Skip word if flagged |
||
494 | if ($skipNext) { |
||
495 | $skipNext = false; |
||
496 | continue; |
||
497 | } |
||
498 | |||
499 | // Skip empty words and words that look like options |
||
500 | if (strlen($word) == 0 || $word[0] === '-') { |
||
501 | continue; |
||
502 | } |
||
503 | |||
504 | // Return the first unambiguous match to argument-like words |
||
505 | try { |
||
506 | $cmd = $this->application->find($word); |
||
507 | $this->commandWordIndex = $index; |
||
0 ignored issues
–
show
|
|||
508 | return $cmd; |
||
509 | } catch (\InvalidArgumentException $e) { |
||
510 | // Exception thrown, when multiple or no commands are found. |
||
511 | } |
||
512 | } |
||
513 | |||
514 | // No command found |
||
515 | return null; |
||
516 | } |
||
517 | } |
||
518 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.