From fa61eb5efdd1a347e0aae96f5450fc8a02997b6c Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Fri, 28 Jul 2017 16:38:22 -0700 Subject: [PATCH] Refactor preflight verification tests into a PreflightVerify class. --- src/Config/Environment.php | 12 ++++ src/Preflight/Preflight.php | 34 ++++----- src/Preflight/PreflightVerify.php | 114 ++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 src/Preflight/PreflightVerify.php diff --git a/src/Config/Environment.php b/src/Config/Environment.php index 7ec64c88a3..3d015c6cbc 100644 --- a/src/Config/Environment.php +++ b/src/Config/Environment.php @@ -190,4 +190,16 @@ public static function isWindows($os = null) { return strtoupper(substr($os ?: PHP_OS, 0, 3)) === 'WIN'; } + + /** + * Verify that we are running PHP through the command line interface. + * + * @return + * A boolean value that is true when PHP is being run through the command line, + * and false if being run through cgi or mod_php. + */ + function verifyCLI() { + return (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)); + } + } diff --git a/src/Preflight/Preflight.php b/src/Preflight/Preflight.php index adcfc326c7..da132816bc 100644 --- a/src/Preflight/Preflight.php +++ b/src/Preflight/Preflight.php @@ -31,9 +31,21 @@ class Preflight */ protected $environment; - public function __construct(Environment $environment) + /** + * @var PreflightVerify + */ + protected $verify; + + /** + * @var ConfigLocator + */ + protected $configLocator; + + public function __construct(Environment $environment, $verify = null, $configLocator = null) { $this->environment = $environment; + $this->verify = $verify ?: new PreflightVerify(); + $this->configLocator = $configLocator ?: new ConfigLocator(); } public function init($preflightArgs) @@ -102,14 +114,14 @@ public function run($argv) protected function doRun($argv) { - // Fail fast if the PHP version is not at least 5.6.0. - $this->confirmPhpVersion('5.6.0'); + // Fail fast if there is anything in our environment that does not check out + $this->verify->verify($this->environment); // Get the preflight args and begin collecting configuration files. $preflightArgs = $this->preflightArgs($argv); $configLocator = $this->prepareConfig($preflightArgs, $this->environment); - // Do legacy initialization + // Do legacy initialization (load static includes, define old constants, etc.) $this->init($preflightArgs); // Determine the local site targeted, if any. @@ -130,8 +142,8 @@ protected function doRun($argv) $container = DependencyInjection::initContainer($application, $config, $input, $output); // We need to check the php minimum version again, in case anyone - // has set it to something higher. - $this->confirmPhpVersion($config->get('drush.php.minimum-version')); + // has set it to something higher in one of the config files we loaded. + $this->verify->confirmPhpVersion($config->get('drush.php.minimum-version')); // Find all of the available commandfiles, save for those that are // provided by modules in the selected site; those will be added @@ -228,16 +240,6 @@ protected function findCommandFileSearchPath($preflightArgs) return $searchpath; } - /** - * Fail fast if the php version does not meet the minimum requirements. - */ - protected function confirmPhpVersion($minimumPhpVersion) - { - if (version_compare(phpversion(), $minimumPhpVersion) < 0 && !getenv('DRUSH_NO_MIN_PHP')) { - throw new \Exception(dt('Your command line PHP installation is too old. Drush requires at least PHP !version. To suppress this check, set the environment variable DRUSH_NO_MIN_PHP=1', ['!version' => $minimumPhpVersion])); - } - } - /** * Make sure we are notified on exit, and when bad things happen. */ diff --git a/src/Preflight/PreflightVerify.php b/src/Preflight/PreflightVerify.php new file mode 100644 index 0000000000..5b2e08354e --- /dev/null +++ b/src/Preflight/PreflightVerify.php @@ -0,0 +1,114 @@ +confirmPhpVersion('5.6.0'); + + // Fail if this is not a CLI php + $this->confirmUsingCLI($environment); + + // Fail if any manditory functions have been disabled, or any + // illegal options have been set in php.ini. + $this->checkPhpIni(); + } + + /** + * Fail fast if the php version does not meet the minimum requirements. + * + * @param string $minimumPhpVersion + * The minimum allowable php version + */ + public function confirmPhpVersion($minimumPhpVersion) + { + if (version_compare(phpversion(), $minimumPhpVersion) < 0 && !getenv('DRUSH_NO_MIN_PHP')) { + throw new \Exception(StringUtils::interpolate('Your command line PHP installation is too old. Drush requires at least PHP {version}. To suppress this check, set the environment variable DRUSH_NO_MIN_PHP=1', ['version' => $minimumPhpVersion])); + } + } + + /** + * Fail if not being run from the command line. + * + * @param Environment $environment + */ + protected function confirmUsingCLI(Environment $environment) + { + if (!$environment->verifyCLI()) { + throw new \Exception(StringUtils::interpolate('Drush is designed to run via the command line.')); + } + } + + /** + * Evaluate the environment before command bootstrapping + * begins. If the php environment is too restrictive, then + * notify the user that a setting change is needed and abort. + */ + protected function checkPhpIni() + { + $ini_checks = ['safe_mode' => '', 'open_basedir' => '', 'disable_functions' => ['exec', 'system'], 'disable_classes' => '']; + + // Test to insure that certain php ini restrictions have not been enabled + $prohibited_list = []; + foreach ($ini_checks as $prohibited_mode => $disallowed_value) { + $ini_value = ini_get($prohibited_mode); + if ($this->invalidIniValue($ini_value, $disallowed_value)) { + $prohibited_list[] = $prohibited_mode; + } + } + if (!empty($prohibited_list)) { + throw new \Exception(StringUtils::interpolate('The following restricted PHP modes have non-empty values: {prohibited_list}. This configuration is incompatible with drush. {php_ini_msg}', ['prohibited_list' => implode(' and ', $prohibited_list), 'php_ini_msg' => $this->loadedPhpIniMessage()])); + } + } + + /** + * Determine whether an ini value is valid based on the criteria. + * + * @param string $ini_value + * The value of the ini setting being tested. + * @param string|string[] $disallowed_value + * The value that the ini seting cannot be, or a list of disallowed + * values that cannot appear in the setting. + * @return bool + */ + protected function invalidIniValue($ini_value, $disallowed_value) + { + if (empty($disallowed_value)) { + return !empty($ini_value) && (strcasecmp($ini_value, 'off') != 0); + } else { + foreach ($disallowed_value as $test_value) { + if (preg_match('/(^|,)' . $test_value . '(,|$)/', $ini_value)) { + return true; + } + } + } + return false; + } + + /** + * Returns a localizable message about php.ini that + * varies depending on whether the php_ini_loaded_file() + * is available or not. + */ + protected function loadedPhpIniMessage() + { + if (function_exists('php_ini_loaded_file')) { + return StringUtils::interpolate('Please check your configuration settings in !phpini or in your drush.ini file; see examples/example.drush.ini for details.', ['!phpini' => php_ini_loaded_file()]); + } else { + return StringUtils::interpolate('Please check your configuration settings in your php.ini file or in your drush.ini file; see examples/example.drush.ini for details.'); + } + } +}