SierraTecnologia /
fabrica
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 Fabrica\Helper; |
||
| 4 | |||
| 5 | use Exception; |
||
| 6 | use Fabrica\Tools\Logging\BuildLogger; |
||
| 7 | use Symfony\Component\Process\Process; |
||
| 8 | |||
| 9 | /** |
||
| 10 | * Handles running system commands with variables. |
||
| 11 | */ |
||
| 12 | class CommandExecutor implements CommandExecutorInterface |
||
| 13 | { |
||
| 14 | /** |
||
| 15 | * @var BuildLogger |
||
| 16 | */ |
||
| 17 | protected $logger; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * @var bool |
||
| 21 | */ |
||
| 22 | protected $verbose; |
||
| 23 | |||
| 24 | /** |
||
| 25 | * @var array |
||
| 26 | */ |
||
| 27 | protected $lastOutput; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * @var string |
||
| 31 | */ |
||
| 32 | protected $lastError; |
||
| 33 | |||
| 34 | /** |
||
| 35 | * @var bool |
||
| 36 | */ |
||
| 37 | public $logExecOutput = true; |
||
| 38 | |||
| 39 | /** |
||
| 40 | * The path which findBinary will look in. |
||
| 41 | * |
||
| 42 | * @var string |
||
| 43 | */ |
||
| 44 | protected $rootDir; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * Current build path |
||
| 48 | * |
||
| 49 | * @var string |
||
| 50 | */ |
||
| 51 | protected $buildPath; |
||
| 52 | |||
| 53 | /** |
||
| 54 | * Commands with no proper exit mechanism |
||
| 55 | * |
||
| 56 | * @var array |
||
| 57 | */ |
||
| 58 | private static $noExitCommands = [ |
||
| 59 | 'codecept', |
||
| 60 | ]; |
||
| 61 | |||
| 62 | /** |
||
| 63 | * Environment variables that should not be inherited |
||
| 64 | * |
||
| 65 | * @var array |
||
| 66 | */ |
||
| 67 | private static $blacklistEnvVars = [ |
||
| 68 | 'PHP_SELF', |
||
| 69 | 'SCRIPT_NAME', |
||
| 70 | 'SCRIPT_FILENAME', |
||
| 71 | 'PATH_TRANSLATED', |
||
| 72 | 'DOCUMENT_ROOT', |
||
| 73 | 'SHELL_VERBOSITY', |
||
| 74 | ]; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @param BuildLogger $logger |
||
| 78 | * @param string $rootDir |
||
| 79 | * @param bool $verbose |
||
| 80 | */ |
||
| 81 | public function __construct(BuildLogger $logger, $rootDir, $verbose = false) |
||
| 82 | { |
||
| 83 | $this->logger = $logger; |
||
| 84 | $this->verbose = $verbose; |
||
| 85 | $this->lastOutput = []; |
||
| 86 | $this->rootDir = $rootDir; |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * Executes shell commands. |
||
| 91 | * |
||
| 92 | * @param array $args |
||
| 93 | * |
||
| 94 | * @return bool Indicates success |
||
| 95 | */ |
||
| 96 | public function executeCommand($args = []) |
||
| 97 | { |
||
| 98 | $this->lastOutput = []; |
||
| 99 | |||
| 100 | $this->logger->logDebug('Args: ' . json_encode($args)); |
||
| 101 | |||
| 102 | $command = call_user_func_array('sprintf', $args); |
||
| 103 | |||
| 104 | $this->logger->logDebug('Command: ' . $command); |
||
| 105 | |||
| 106 | $withNoExit = ''; |
||
| 107 | foreach (self::$noExitCommands as $nec) { |
||
| 108 | if (preg_match("/\b{$nec}\b/", $command)) { |
||
| 109 | $withNoExit = $nec; |
||
| 110 | break; |
||
| 111 | } |
||
| 112 | } |
||
| 113 | |||
| 114 | $process = new Process($command, $this->buildPath); |
||
| 115 | $process->setTimeout(86400); |
||
| 116 | |||
| 117 | $env = $this->getDefaultEnv(); |
||
| 118 | |||
| 119 | if (!empty($withNoExit)) { |
||
| 120 | $process->start(null, $env); |
||
| 121 | |||
| 122 | $this->logger->logDebug("Assuming command '{$withNoExit}' does not exit properly"); |
||
| 123 | do { |
||
| 124 | sleep(15); |
||
| 125 | $response = []; |
||
| 126 | exec("ps auxww | grep '{$withNoExit}' | grep -v grep", $response); |
||
| 127 | $response = array_filter( |
||
| 128 | $response, |
||
| 129 | function ($a) { |
||
| 130 | return strpos($a, $this->buildPath) !== false; |
||
| 131 | } |
||
| 132 | ); |
||
| 133 | } while (!empty($response)); |
||
| 134 | $process->stop(); |
||
| 135 | $status = 0; |
||
| 136 | } else { |
||
| 137 | $process->setIdleTimeout(600); |
||
| 138 | $process->start(null, $env); |
||
| 139 | $status = $process->wait(); |
||
| 140 | } |
||
| 141 | |||
| 142 | $lastOutput = $this->replaceIllegalCharacters($process->getOutput()); |
||
| 143 | $lastError = $this->replaceIllegalCharacters($process->getErrorOutput()); |
||
| 144 | |||
| 145 | $this->lastOutput = array_filter(explode(PHP_EOL, $lastOutput)); |
||
| 146 | $this->lastError = $lastError; |
||
| 147 | |||
| 148 | $shouldOutput = ($this->logExecOutput && ($this->verbose || 0 !== $status)); |
||
| 149 | |||
| 150 | if ($shouldOutput && !empty($this->lastOutput)) { |
||
| 151 | $this->logger->log($this->lastOutput); |
||
| 152 | } |
||
| 153 | |||
| 154 | if (!empty($this->lastError)) { |
||
| 155 | $this->logger->logFailure($this->lastError); |
||
| 156 | } |
||
| 157 | |||
| 158 | $isSuccess = false; |
||
| 159 | if (0 === $status) { |
||
| 160 | $isSuccess = true; |
||
| 161 | } |
||
| 162 | |||
| 163 | $this->logger->logDebug('Execution status: ' . $status); |
||
| 164 | |||
| 165 | return $isSuccess; |
||
| 166 | } |
||
| 167 | |||
| 168 | /** |
||
| 169 | * @param string $utf8String |
||
| 170 | * |
||
| 171 | * @return string |
||
| 172 | */ |
||
| 173 | public function replaceIllegalCharacters($utf8String) |
||
| 174 | { |
||
| 175 | mb_substitute_character(0xFFFD); // is '�' |
||
| 176 | $legalUtf8String = mb_convert_encoding($utf8String, 'utf8', 'utf8'); |
||
| 177 | $regexp = '/[\x00-\x08\x10\x0B\x0C\x0E-\x19\x7F]' . |
||
| 178 | '|[^\x{0}-\x{ffff}]/u'; // more than 3 byte UTF-8 sequences (unsupported in mysql) |
||
| 179 | |||
| 180 | return preg_replace($regexp, '�', $legalUtf8String); |
||
| 181 | } |
||
| 182 | |||
| 183 | /** |
||
| 184 | * Returns the output from the last command run. |
||
| 185 | * |
||
| 186 | * @return string |
||
| 187 | */ |
||
| 188 | public function getLastOutput() |
||
| 189 | { |
||
| 190 | return implode(PHP_EOL, $this->lastOutput); |
||
| 191 | } |
||
| 192 | |||
| 193 | /** |
||
| 194 | * Returns the stderr output from the last command run. |
||
| 195 | * |
||
| 196 | * @return string |
||
| 197 | */ |
||
| 198 | public function getLastError() |
||
| 199 | { |
||
| 200 | return $this->lastError; |
||
| 201 | } |
||
| 202 | |||
| 203 | /** |
||
| 204 | * @param string $binaryPath |
||
| 205 | * @param string $binary |
||
| 206 | * |
||
| 207 | * @return string|false |
||
| 208 | */ |
||
| 209 | View Code Duplication | protected function findBinaryByPath($binaryPath, $binary) |
|
|
0 ignored issues
–
show
|
|||
| 210 | { |
||
| 211 | if (is_dir($binaryPath) && is_file($binaryPath . '/' . $binary)) { |
||
| 212 | $this->logger->logDebug(sprintf('Found in %s (binary_path): %s', $binaryPath, $binary)); |
||
| 213 | |||
| 214 | return $binaryPath . '/' . $binary; |
||
| 215 | } |
||
| 216 | |||
| 217 | return false; |
||
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * @param string $composerBin |
||
| 222 | * @param string $binary |
||
| 223 | * |
||
| 224 | * @return string|false |
||
| 225 | */ |
||
| 226 | View Code Duplication | protected function findBinaryLocal($composerBin, $binary) |
|
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 227 | { |
||
| 228 | if (is_dir($composerBin) && is_file($composerBin . '/' . $binary)) { |
||
| 229 | $this->logger->logDebug(sprintf('Found in %s (local): %s', $composerBin, $binary)); |
||
| 230 | |||
| 231 | return $composerBin . '/' . $binary; |
||
| 232 | } |
||
| 233 | |||
| 234 | return false; |
||
| 235 | } |
||
| 236 | |||
| 237 | /** |
||
| 238 | * @param string $binary |
||
| 239 | * |
||
| 240 | * @return string|false |
||
| 241 | */ |
||
| 242 | protected function findBinaryGlobal($binary) |
||
| 243 | { |
||
| 244 | if (is_file($this->rootDir . 'vendor/bin/' . $binary)) { |
||
| 245 | $this->logger->logDebug(sprintf('Found in %s (global): %s', 'vendor/bin', $binary)); |
||
| 246 | |||
| 247 | return $this->rootDir . 'vendor/bin/' . $binary; |
||
| 248 | } |
||
| 249 | |||
| 250 | return false; |
||
| 251 | } |
||
| 252 | |||
| 253 | /** |
||
| 254 | * Uses 'which' to find a system binary by name |
||
| 255 | * |
||
| 256 | * @param string $binary |
||
| 257 | * |
||
| 258 | * @return string|false |
||
| 259 | */ |
||
| 260 | protected function findBinarySystem($binary) |
||
| 261 | { |
||
| 262 | $tempBinary = trim(shell_exec('which ' . $binary)); |
||
| 263 | if (is_file($tempBinary)) { |
||
| 264 | $this->logger->logDebug(sprintf('Found in %s (system): %s', '', $binary)); |
||
| 265 | |||
| 266 | return $tempBinary; |
||
| 267 | } |
||
| 268 | |||
| 269 | return false; |
||
| 270 | } |
||
| 271 | |||
| 272 | /** |
||
| 273 | * {@inheritdoc} |
||
| 274 | */ |
||
| 275 | public function findBinary($binary, $priorityPath = 'local', $binaryPath = '', $binaryName = []) |
||
| 276 | { |
||
| 277 | $composerBin = $this->getComposerBinDir($this->buildPath); |
||
| 278 | |||
| 279 | if (is_string($binary)) { |
||
| 280 | $binary = [$binary]; |
||
| 281 | } |
||
| 282 | |||
| 283 | if ($binaryName) { |
||
|
0 ignored issues
–
show
The expression
$binaryName of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 284 | array_unshift($binary, ...$binaryName); |
||
| 285 | } |
||
| 286 | |||
| 287 | foreach ($binary as $bin) { |
||
| 288 | $this->logger->logDebug(sprintf('Looking for binary: %s, priority = %s', $bin, $priorityPath)); |
||
| 289 | |||
| 290 | if ('binary_path' === $priorityPath) { |
||
| 291 | if ($binaryPath = $this->findBinaryByPath($binaryPath, $bin)) { |
||
|
0 ignored issues
–
show
It seems like
$binaryPath defined by $this->findBinaryByPath($binaryPath, $bin) on line 291 can also be of type false; however, Fabrica\Helper\CommandExecutor::findBinaryByPath() does only seem to accept string, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 292 | return $binaryPath; |
||
| 293 | } |
||
| 294 | |||
| 295 | if ($binaryLocal = $this->findBinaryLocal($composerBin, $bin)) { |
||
| 296 | return $binaryLocal; |
||
| 297 | } |
||
| 298 | |||
| 299 | if ($binaryGlobal = $this->findBinaryGlobal($bin)) { |
||
| 300 | return $binaryGlobal; |
||
| 301 | } |
||
| 302 | |||
| 303 | if ($binarySystem = $this->findBinarySystem($bin)) { |
||
| 304 | return $binarySystem; |
||
| 305 | } |
||
| 306 | View Code Duplication | } elseif ('system' === $priorityPath) { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 307 | if ($binarySystem = $this->findBinarySystem($bin)) { |
||
| 308 | return $binarySystem; |
||
| 309 | } |
||
| 310 | |||
| 311 | if ($binaryLocal = $this->findBinaryLocal($composerBin, $bin)) { |
||
| 312 | return $binaryLocal; |
||
| 313 | } |
||
| 314 | |||
| 315 | if ($binaryGlobal = $this->findBinaryGlobal($bin)) { |
||
| 316 | return $binaryGlobal; |
||
| 317 | } |
||
| 318 | |||
| 319 | if ($binaryPath = $this->findBinaryByPath($binaryPath, $bin)) { |
||
|
0 ignored issues
–
show
It seems like
$binaryPath defined by $this->findBinaryByPath($binaryPath, $bin) on line 319 can also be of type false; however, Fabrica\Helper\CommandExecutor::findBinaryByPath() does only seem to accept string, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 320 | return $binaryPath; |
||
| 321 | } |
||
| 322 | } elseif ('global' === $priorityPath) { |
||
| 323 | if ($binaryGlobal = $this->findBinaryGlobal($bin)) { |
||
| 324 | return $binaryGlobal; |
||
| 325 | } |
||
| 326 | |||
| 327 | if ($binaryLocal = $this->findBinaryLocal($composerBin, $bin)) { |
||
| 328 | return $binaryLocal; |
||
| 329 | } |
||
| 330 | |||
| 331 | if ($binarySystem = $this->findBinarySystem($bin)) { |
||
| 332 | return $binarySystem; |
||
| 333 | } |
||
| 334 | |||
| 335 | if ($binaryPath = $this->findBinaryByPath($binaryPath, $bin)) { |
||
|
0 ignored issues
–
show
It seems like
$binaryPath defined by $this->findBinaryByPath($binaryPath, $bin) on line 335 can also be of type false; however, Fabrica\Helper\CommandExecutor::findBinaryByPath() does only seem to accept string, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 336 | return $binaryPath; |
||
| 337 | } |
||
| 338 | View Code Duplication | } else { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 339 | if ($binaryLocal = $this->findBinaryLocal($composerBin, $bin)) { |
||
| 340 | return $binaryLocal; |
||
| 341 | } |
||
| 342 | |||
| 343 | if ($binaryGlobal = $this->findBinaryGlobal($bin)) { |
||
| 344 | return $binaryGlobal; |
||
| 345 | } |
||
| 346 | |||
| 347 | if ($binarySystem = $this->findBinarySystem($bin)) { |
||
| 348 | return $binarySystem; |
||
| 349 | } |
||
| 350 | |||
| 351 | if ($binaryPath = $this->findBinaryByPath($binaryPath, $bin)) { |
||
|
0 ignored issues
–
show
It seems like
$binaryPath defined by $this->findBinaryByPath($binaryPath, $bin) on line 351 can also be of type false; however, Fabrica\Helper\CommandExecutor::findBinaryByPath() does only seem to accept string, did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new Loading history...
|
|||
| 352 | return $binaryPath; |
||
| 353 | } |
||
| 354 | } |
||
| 355 | } |
||
| 356 | |||
| 357 | throw new Exception(sprintf('Could not find %s', implode('/', $binary))); |
||
| 358 | } |
||
| 359 | |||
| 360 | /** |
||
| 361 | * Try to load the composer.json file in the building project |
||
| 362 | * If the bin-dir is configured, return the full path to it |
||
| 363 | * |
||
| 364 | * @param string $path Current build path |
||
| 365 | * |
||
| 366 | * @return string|null |
||
| 367 | */ |
||
| 368 | public function getComposerBinDir($path) |
||
| 369 | { |
||
| 370 | if (is_dir($path)) { |
||
| 371 | $composer = $path . '/composer.json'; |
||
| 372 | if (is_file($composer)) { |
||
| 373 | $json = json_decode(file_get_contents($composer)); |
||
| 374 | |||
| 375 | if (isset($json->config->{"bin-dir"})) { |
||
| 376 | return $path . '/' . $json->config->{"bin-dir"}; |
||
| 377 | } elseif (is_dir($path . '/vendor/bin')) { |
||
| 378 | return $path . '/vendor/bin'; |
||
| 379 | } |
||
| 380 | } |
||
| 381 | } |
||
| 382 | return null; |
||
| 383 | } |
||
| 384 | |||
| 385 | /** |
||
| 386 | * Set the buildPath property. |
||
| 387 | * |
||
| 388 | * @param string $path |
||
| 389 | */ |
||
| 390 | public function setBuildPath($path) |
||
| 391 | { |
||
| 392 | $this->buildPath = $path; |
||
| 393 | } |
||
| 394 | |||
| 395 | |||
| 396 | |||
| 397 | |||
| 398 | private function getDefaultEnv() |
||
| 399 | { |
||
| 400 | $env = array(); |
||
| 401 | |||
| 402 | foreach ($_SERVER as $k => $v) { |
||
| 403 | if (in_array($k, self::$blacklistEnvVars)) { |
||
| 404 | continue; |
||
| 405 | } |
||
| 406 | if (\is_string($v) && false !== $v = getenv($k)) { |
||
| 407 | $env[$k] = $v; |
||
| 408 | } |
||
| 409 | } |
||
| 410 | |||
| 411 | View Code Duplication | foreach ($_ENV as $k => $v) { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 412 | if (in_array($k, self::$blacklistEnvVars)) { |
||
| 413 | continue; |
||
| 414 | } |
||
| 415 | if (\is_string($v)) { |
||
| 416 | $env[$k] = $v; |
||
| 417 | } |
||
| 418 | } |
||
| 419 | |||
| 420 | if (PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 1) { |
||
| 421 | View Code Duplication | foreach (getenv() as $k => $v) { |
|
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. Loading history...
|
|||
| 422 | if (in_array($k, self::$blacklistEnvVars)) { |
||
| 423 | continue; |
||
| 424 | } |
||
| 425 | if (\is_string($v)) { |
||
| 426 | $env[$k] = $v; |
||
| 427 | } |
||
| 428 | } |
||
| 429 | } else { |
||
| 430 | $output = []; |
||
| 431 | exec('env', $output); |
||
| 432 | foreach ($output as $o) { |
||
|
0 ignored issues
–
show
The expression
$output of type null|array is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
Loading history...
|
|||
| 433 | $keyval = explode('=', $o, 2); |
||
| 434 | if (count($keyval) < 2 || empty($keyval[1])) { |
||
| 435 | continue; |
||
| 436 | } |
||
| 437 | if (in_array($keyval[0], self::$blacklistEnvVars)) { |
||
| 438 | continue; |
||
| 439 | } |
||
| 440 | $env[$keyval[0]] = $keyval[1]; |
||
| 441 | } |
||
| 442 | } |
||
| 443 | |||
| 444 | return $env; |
||
| 445 | } |
||
| 446 | } |
||
| 447 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.