🤩 File Manager - Mr.X
PHP:
8.3.33
Server:
Apache
OS:
Linux 5.14.0-611.49.1.el9_7.x86_64
User:
websparkit
Navigate
Upload:
Upload
New File
New Folder
Editing:JobRunner.php
<?php declare(strict_types=1); /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann <sebastian@phpunit.de> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace PHPUnit\Util\PHP; use const PHP_BINARY; use const PHP_SAPI; use function array_keys; use function array_merge; use function array_values; use function assert; use function count; use function explode; use function fclose; use function file_get_contents; use function file_put_contents; use function function_exists; use function fwrite; use function ini_get_all; use function is_array; use function is_file; use function is_resource; use function proc_close; use function proc_open; use function sprintf; use function str_contains; use function str_replace; use function str_starts_with; use function stream_get_contents; use function sys_get_temp_dir; use function tempnam; use function trim; use function unlink; use function xdebug_is_debugger_active; use PHPUnit\Event\Facade; use PHPUnit\Event\Facade as EventFacade; use PHPUnit\Framework\ChildProcessResultProcessor; use PHPUnit\Framework\Test; use PHPUnit\Runner\CodeCoverage; use SebastianBergmann\Environment\Runtime; /** * @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit * * @internal This class is not covered by the backward compatibility promise for PHPUnit */ final readonly class JobRunner { private ChildProcessResultProcessor $processor; public function __construct(ChildProcessResultProcessor $processor) { $this->processor = $processor; } /** * @param non-empty-string $processResultFile * @param ?non-empty-string $processResultNonce */ public function runTestJob(Job $job, string $processResultFile, Test $test, ?string $processResultNonce = null): void { $result = $this->run($job); $processResult = ''; if (is_file($processResultFile)) { $processResult = file_get_contents($processResultFile); assert($processResult !== false); @unlink($processResultFile); } $this->processor->process( $test, $processResult, $result->stderr(), $processResultNonce, ); EventFacade::emitter()->childProcessFinished($result->stdout(), $result->stderr()); } /** * @throws PhpProcessException */ public function run(Job $job): Result { $temporaryFile = null; if ($job->hasInput()) { $temporaryFile = tempnam(sys_get_temp_dir(), 'phpunit_'); if ($temporaryFile === false || file_put_contents($temporaryFile, $job->code()) === false) { // @codeCoverageIgnoreStart throw new PhpProcessException( 'Unable to write temporary file', ); // @codeCoverageIgnoreEnd } $job = new Job( $job->input(), $job->phpSettings(), $job->environmentVariables(), $job->arguments(), null, $job->redirectErrors(), $job->requiresXdebug(), ); } assert($temporaryFile !== ''); return $this->runProcess($job, $temporaryFile); } /** * @param ?non-empty-string $temporaryFile * * @throws PhpProcessException */ private function runProcess(Job $job, ?string $temporaryFile): Result { $environmentVariables = null; if ($job->hasEnvironmentVariables()) { /** @phpstan-ignore nullCoalesce.variable */ $environmentVariables = $_SERVER ?? []; unset($environmentVariables['argv'], $environmentVariables['argc']); $environmentVariables = array_merge($environmentVariables, $job->environmentVariables()); foreach ($environmentVariables as $key => $value) { if (is_array($value)) { unset($environmentVariables[$key]); } } unset($key, $value); } $pipeSpec = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; if ($job->redirectErrors()) { $pipeSpec[2] = ['redirect', 1]; } $process = proc_open( $this->buildCommand($job, $temporaryFile), $pipeSpec, $pipes, null, $environmentVariables, ); if (!is_resource($process)) { // @codeCoverageIgnoreStart throw new PhpProcessException( 'Unable to spawn worker process', ); // @codeCoverageIgnoreEnd } Facade::emitter()->childProcessStarted(); fwrite($pipes[0], $job->code()); fclose($pipes[0]); $stdout = ''; $stderr = ''; if (isset($pipes[1])) { $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]); } if (isset($pipes[2])) { $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]); } proc_close($process); if ($temporaryFile !== null) { unlink($temporaryFile); } assert($stdout !== false); assert($stderr !== false); return new Result($stdout, $stderr); } /** * @return non-empty-list<string> */ private function buildCommand(Job $job, ?string $file): array { $runtime = new Runtime; $command = [PHP_BINARY]; $phpSettings = $job->phpSettings(); $xdebugModeConfiguredExplicitly = false; foreach ($phpSettings as $phpSetting) { if (str_starts_with($phpSetting, 'xdebug.mode')) { $xdebugModeConfiguredExplicitly = true; break; } } if ($runtime->hasPCOV()) { $pcovSettings = ini_get_all('pcov'); assert($pcovSettings !== false); // The settings forwarded from the current process must be applied // before the settings configured for the job so that the latter, // for instance a "pcov.directory" set in the --INI-- section of a // PHPT test, take precedence: the value of the last -d flag wins. $phpSettings = array_merge( $runtime->getCurrentSettings( array_keys($pcovSettings), ), $phpSettings, ); } elseif ($runtime->hasXdebug()) { assert(function_exists('xdebug_is_debugger_active')); $xdebugSettings = ini_get_all('xdebug'); assert($xdebugSettings !== false); // The settings forwarded from the current process must be applied // before the settings configured for the job so that the latter // take precedence: the value of the last -d flag wins. $phpSettings = array_merge( $runtime->getCurrentSettings( array_keys($xdebugSettings), ), $phpSettings, ); if ( !$xdebugModeConfiguredExplicitly && !CodeCoverage::instance()->isActive() && xdebug_is_debugger_active() === false && !$job->requiresXdebug() ) { // disable xdebug to speedup test execution $phpSettings['xdebug.mode'] = 'xdebug.mode=off'; } } $phpSettings = array_merge($phpSettings, $this->cliIniOverrides($phpSettings)); $command = array_merge($command, $this->settingsToParameters(array_values($phpSettings))); if (PHP_SAPI === 'phpdbg') { $command[] = '-qrr'; if ($file === null) { $command[] = 's='; } } if ($file !== null) { $command[] = '-f'; $command[] = $file; } if ($job->hasArguments()) { if ($file === null) { $command[] = '--'; } foreach ($job->arguments() as $argument) { $command[] = trim($argument); } } return $command; } /** * Detects INI settings that cannot be set via ini_set() (PHP_INI_SYSTEM * and PHP_INI_PERDIR) and whose current value differs from the value * configured in INI files. * * These settings must be forwarded as -d flags to child processes * because the @ini_set() calls in GlobalState::getIniSettingsAsString() * cannot change them at runtime. * * @param array<array-key, string> $alreadySet * * @return array<string, string> */ private function cliIniOverrides(array $alreadySet): array { $overrides = (new Runtime)->getSettingsNotChangeableAtRuntime(); foreach ($overrides as $key => $value) { foreach ($alreadySet as $existing) { if (str_starts_with($existing, $key . '=')) { unset($overrides[$key]); break; } } } return $overrides; } /** * @param list<string> $settings * * @throws PhpProcessException * * @return list<string> */ private function settingsToParameters(array $settings): array { $buffer = []; foreach ($settings as $setting) { $buffer[] = '-d'; $buffer[] = $this->processSettingValue($setting); } return $buffer; } /** * Rejects "name=value" INI settings whose value contains a line-break * character. A newline cannot legitimately appear in a PHP INI value and * would, if forwarded unchanged, be parsed by the child process as a * directive separator — turning a single setting into an attacker- * controlled sequence of directives. * * Otherwise quotes the value portion only when it contains characters * PHP's INI parser would interpret as metacharacters (`;` starts a * comment, `"` is a string delimiter, `=` is the assignment operator * that would otherwise be parsed as starting a new directive and * trigger `PHP: syntax error, unexpected '='`). * * Quoting is avoided for plain values so that boolean keywords such as * `On` / `Off` keep their special INI semantics; wrapping them in quotes * turns them into the literal strings `"On"` / `"Off"` and breaks * settings like `output_buffering`. * * @throws PhpProcessException */ private function processSettingValue(string $setting): string { $parts = explode('=', $setting, 2); if (count($parts) !== 2) { return $setting; } [$name, $value] = $parts; if (str_contains($value, "\n") || str_contains($value, "\r")) { throw new PhpProcessException( sprintf( 'PHP setting "%s" contains a line-break character, which is not permitted', $name, ), ); } if (!str_contains($value, ';') && !str_contains($value, '"') && !str_contains($value, '=')) { return $setting; } return $name . '="' . str_replace('"', '\\"', $value) . '"'; } }
Save Changes
Cancel
Create New File
Create New Folder