From 9e6d59829ced8756d89def89fe35f2bb8eee38f7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 25 Dec 2012 06:15:28 -0800 Subject: [PATCH] Consolidate environmental initialization Summary: We have a bunch of code duplication now between __init_script__.php and webroot/index.php. Consoldiate these methods and move them into PhabricatorEnv. Merge PhabricatorRequestOverseer into PhabricatorStartup. Test Plan: Loaded page, ran script. Wiped PHABRICATOR_ENV; loaded page, ran script; got errors. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T2223 Differential Revision: https://secure.phabricator.com/D4283 --- scripts/__init_script__.php | 86 +++-------- src/__phutil_library_map__.php | 1 - src/infrastructure/PhabricatorEnv.php | 96 +++++++++++++ .../PhabricatorRequestOverseer.php | 98 ------------- support/PhabricatorStartup.php | 133 +++++++++++++++++- webroot/index.php | 87 +----------- 6 files changed, 245 insertions(+), 256 deletions(-) delete mode 100644 src/infrastructure/PhabricatorRequestOverseer.php diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php index 4d70ec0dbd..c0a198a145 100644 --- a/scripts/__init_script__.php +++ b/scripts/__init_script__.php @@ -1,72 +1,24 @@ setLanguage($translation->getLanguage()) - ->addTranslations($translation->getTranslations()); - -// Append any paths to $PATH if we need to. -$paths = PhabricatorEnv::getEnvConfig('environment.append-paths'); -if (!empty($paths)) { - $current_env_path = getenv('PATH'); - $new_env_paths = implode(PATH_SEPARATOR, $paths); - putenv('PATH='.$current_env_path.PATH_SEPARATOR.$new_env_paths); -} +init_phabricator_script(); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1b1949924e..8277b13d05 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1080,7 +1080,6 @@ phutil_register_library_map(array( 'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php', 'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php', 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', - 'PhabricatorRequestOverseer' => 'infrastructure/PhabricatorRequestOverseer.php', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php', 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', diff --git a/src/infrastructure/PhabricatorEnv.php b/src/infrastructure/PhabricatorEnv.php index 49002a6070..0bddb995e3 100644 --- a/src/infrastructure/PhabricatorEnv.php +++ b/src/infrastructure/PhabricatorEnv.php @@ -53,6 +53,102 @@ final class PhabricatorEnv { private static $env; private static $stack = array(); + /** + * @phutil-external-symbol class PhabricatorStartup + */ + public static function initializeWebEnvironment() { + $env = self::getSelectedEnvironmentName(); + if (!$env) { + PhabricatorStartup::didFatal( + "The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ". + "your httpd.conf to include 'SetEnv PHABRICATOR_ENV ', where ". + "'' is one of 'development', 'production', or a custom ". + "environment."); + } + + self::initializeCommonEnvironment(); + } + + public static function initializeScriptEnvironment() { + $env = self::getSelectedEnvironmentName(); + if (!$env) { + echo phutil_console_wrap( + phutil_console_format( + "**ERROR**: PHABRICATOR_ENV Not Set\n\n". + "Define the __PHABRICATOR_ENV__ environment variable before ". + "running this script. You can do it on the command line like ". + "this:\n\n". + " $ PHABRICATOR_ENV=__custom/myconfig__ %s ...\n\n". + "Replace __custom/myconfig__ with the path to your configuration ". + "file. For more information, see the 'Configuration Guide' in the ". + "Phabricator documentation.\n\n", + $GLOBALS['argv'][0])); + exit(1); + } + + self::initializeCommonEnvironment(); + + // NOTE: This is dangerous in general, but we know we're in a script context + // and are not vulnerable to CSRF. + AphrontWriteGuard::allowDangerousUnguardedWrites(true); + } + + /** + * @phutil-external-symbol function phabricator_read_config_file + */ + private static function initializeCommonEnvironment() { + $env = self::getSelectedEnvironmentName(); + + $root = dirname(phutil_get_library_root('phabricator')); + require_once $root.'/conf/__init_conf__.php'; + $conf = phabricator_read_config_file($env); + $conf['phabricator.env'] = $env; + + PhabricatorEnv::setEnvConfig($conf); + + PhutilErrorHandler::initialize(); + + $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); + if ($tz) { + date_default_timezone_set($tz); + } + + // Append any paths to $PATH if we need to. + $paths = PhabricatorEnv::getEnvConfig('environment.append-paths'); + if (!empty($paths)) { + $current_env_path = getenv('PATH'); + $new_env_paths = implode(PATH_SEPARATOR, $paths); + putenv('PATH='.$current_env_path.PATH_SEPARATOR.$new_env_paths); + } + + foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { + phutil_load_library($library); + } + + PhabricatorEventEngine::initialize(); + + $translation = PhabricatorEnv::newObjectFromConfig('translation.provider'); + PhutilTranslator::getInstance() + ->setLanguage($translation->getLanguage()) + ->addTranslations($translation->getTranslations()); + } + + public static function getSelectedEnvironmentName() { + $env_var = 'PHABRICATOR_ENV'; + + $env = idx($_SERVER, $env_var); + + if (!$env) { + $env = getenv($env_var); + } + + if (!$env) { + $env = idx($_ENV, $env_var); + } + + return $env; + } + /* -( Reading Configuration )---------------------------------------------- */ diff --git a/src/infrastructure/PhabricatorRequestOverseer.php b/src/infrastructure/PhabricatorRequestOverseer.php deleted file mode 100644 index ca5408c565..0000000000 --- a/src/infrastructure/PhabricatorRequestOverseer.php +++ /dev/null @@ -1,98 +0,0 @@ -detectPostMaxSizeTriggered(); - } - - /** - * Detect if this request has had its POST data stripped by exceeding the - * 'post_max_size' PHP configuration limit. - * - * PHP has a setting called 'post_max_size'. If a POST request arrives with - * a body larger than the limit, PHP doesn't generate $_POST but processes - * the request anyway, and provides no formal way to detect that this - * happened. - * - * We can still read the entire body out of `php://input`. However according - * to the documentation the stream isn't available for "multipart/form-data" - * (on nginx + php-fpm it appears that it is available, though, at least) so - * any attempt to generate $_POST would be fragile. - * - * @phutil-external-symbol class PhabricatorStartup - */ - private function detectPostMaxSizeTriggered() { - // If this wasn't a POST, we're fine. - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - return; - } - - // If there's POST data, clearly we're in good shape. - if ($_POST) { - return; - } - - // For HTML5 drag-and-drop file uploads, Safari submits the data as - // "application/x-www-form-urlencoded". For most files this generates - // something in POST because most files decode to some nonempty (albeit - // meaningless) value. However, some files (particularly small images) - // don't decode to anything. If we know this is a drag-and-drop upload, - // we can skip this check. - if (isset($_REQUEST['__upload__'])) { - return; - } - - // PHP generates $_POST only for two content types. This routing happens - // in `main/php_content_types.c` in PHP. Normally, all forms use one of - // these content types, but some requests may not -- for example, Firefox - // submits files sent over HTML5 XMLHTTPRequest APIs with the Content-Type - // of the file itself. If we don't have a recognized content type, we - // don't need $_POST. - // - // NOTE: We use strncmp() because the actual content type may be something - // like "multipart/form-data; boundary=...". - // - // NOTE: Chrome sometimes omits this header, see some discussion in T1762 - // and http://code.google.com/p/chromium/issues/detail?id=6800 - $content_type = idx($_SERVER, 'CONTENT_TYPE', ''); - - $parsed_types = array( - 'application/x-www-form-urlencoded', - 'multipart/form-data', - ); - - $is_parsed_type = false; - foreach ($parsed_types as $parsed_type) { - if (strncmp($content_type, $parsed_type, strlen($parsed_type)) === 0) { - $is_parsed_type = true; - break; - } - } - - if (!$is_parsed_type) { - return; - } - - // Check for 'Content-Length'. If there's no data, we don't expect $_POST - // to exist. - $length = (int)$_SERVER['CONTENT_LENGTH']; - if (!$length) { - return; - } - - // Time to fatal: we know this was a POST with data that should have been - // populated into $_POST, but it wasn't. - - $config = ini_get('post_max_size'); - PhabricatorStartup::didFatal( - "As received by the server, this request had a nonzero content length ". - "but no POST data.\n\n". - "Normally, this indicates that it exceeds the 'post_max_size' setting ". - "in the PHP configuration on the server. Increase the 'post_max_size' ". - "setting or reduce the size of the request.\n\n". - "Request size according to 'Content-Length' was '{$length}', ". - "'post_max_size' is set to '{$config}'."); - } - -} diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index 90b20e997d..b428b53f4a 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -63,11 +63,6 @@ final class PhabricatorStartup { self::$startTime = microtime(true); self::$globals = array(); - self::setupPHP(); - self::verifyPHP(); - - self::verifyRewriteRules(); - static $registered; if (!$registered) { // NOTE: This protects us against multiple calls to didStartup() in the @@ -76,6 +71,13 @@ final class PhabricatorStartup { register_shutdown_function(array(__CLASS__, 'didShutdown')); $registered = true; } + + self::setupPHP(); + self::verifyPHP(); + + self::verifyRewriteRules(); + + self::detectPostMaxSizeTriggered(); } @@ -118,6 +120,35 @@ final class PhabricatorStartup { self::didFatal($msg); } + public static function loadCoreLibraries() { + $phabricator_root = dirname(dirname(__FILE__)); + $libraries_root = dirname($phabricator_root); + + $root = null; + if (!empty($_SERVER['PHUTIL_LIBRARY_ROOT'])) { + $root = $_SERVER['PHUTIL_LIBRARY_ROOT']; + } + + ini_set( + 'include_path', + $libraries_root.PATH_SEPARATOR.ini_get('include_path')); + + @include_once $root.'libphutil/src/__phutil_library_init__.php'; + if (!@constant('__LIBPHUTIL__')) { + self::didFatal( + "Unable to load libphutil. Put libphutil/ next to phabricator/, or ". + "update your PHP 'include_path' to include the parent directory of ". + "libphutil/."); + } + + phutil_load_library('arcanist/src'); + + // Load Phabricator itself using the absolute path, so we never end up doing + // anything surprising (loading index.php and libraries from different + // directories). + phutil_load_library($phabricator_root.'/src'); + } + /* -( In Case of Apocalypse )---------------------------------------------- */ @@ -219,4 +250,96 @@ final class PhabricatorStartup { } } + + /** + * Detect if this request has had its POST data stripped by exceeding the + * 'post_max_size' PHP configuration limit. + * + * PHP has a setting called 'post_max_size'. If a POST request arrives with + * a body larger than the limit, PHP doesn't generate $_POST but processes + * the request anyway, and provides no formal way to detect that this + * happened. + * + * We can still read the entire body out of `php://input`. However according + * to the documentation the stream isn't available for "multipart/form-data" + * (on nginx + php-fpm it appears that it is available, though, at least) so + * any attempt to generate $_POST would be fragile. + * + * @task validation + */ + private static function detectPostMaxSizeTriggered() { + // If this wasn't a POST, we're fine. + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + return; + } + + // If there's POST data, clearly we're in good shape. + if ($_POST) { + return; + } + + // For HTML5 drag-and-drop file uploads, Safari submits the data as + // "application/x-www-form-urlencoded". For most files this generates + // something in POST because most files decode to some nonempty (albeit + // meaningless) value. However, some files (particularly small images) + // don't decode to anything. If we know this is a drag-and-drop upload, + // we can skip this check. + if (isset($_REQUEST['__upload__'])) { + return; + } + + // PHP generates $_POST only for two content types. This routing happens + // in `main/php_content_types.c` in PHP. Normally, all forms use one of + // these content types, but some requests may not -- for example, Firefox + // submits files sent over HTML5 XMLHTTPRequest APIs with the Content-Type + // of the file itself. If we don't have a recognized content type, we + // don't need $_POST. + // + // NOTE: We use strncmp() because the actual content type may be something + // like "multipart/form-data; boundary=...". + // + // NOTE: Chrome sometimes omits this header, see some discussion in T1762 + // and http://code.google.com/p/chromium/issues/detail?id=6800 + $content_type = isset($_SERVER['CONTENT_TYPE']) + ? $_SERVER['CONTENT_TYPE'] + : ''; + + $parsed_types = array( + 'application/x-www-form-urlencoded', + 'multipart/form-data', + ); + + $is_parsed_type = false; + foreach ($parsed_types as $parsed_type) { + if (strncmp($content_type, $parsed_type, strlen($parsed_type)) === 0) { + $is_parsed_type = true; + break; + } + } + + if (!$is_parsed_type) { + return; + } + + // Check for 'Content-Length'. If there's no data, we don't expect $_POST + // to exist. + $length = (int)$_SERVER['CONTENT_LENGTH']; + if (!$length) { + return; + } + + // Time to fatal: we know this was a POST with data that should have been + // populated into $_POST, but it wasn't. + + $config = ini_get('post_max_size'); + PhabricatorStartup::didFatal( + "As received by the server, this request had a nonzero content length ". + "but no POST data.\n\n". + "Normally, this indicates that it exceeds the 'post_max_size' setting ". + "in the PHP configuration on the server. Increase the 'post_max_size' ". + "setting or reduce the size of the request.\n\n". + "Request size according to 'Content-Length' was '{$length}', ". + "'post_max_size' is set to '{$config}'."); + } + } diff --git a/webroot/index.php b/webroot/index.php index bddda96949..1f56a1d0bd 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -3,50 +3,10 @@ require_once dirname(dirname(__FILE__)).'/support/PhabricatorStartup.php'; PhabricatorStartup::didStartup(); -$access_log = null; - -$env = getenv('PHABRICATOR_ENV'); // Apache -if (!$env) { - if (isset($_ENV['PHABRICATOR_ENV'])) { - $env = $_ENV['PHABRICATOR_ENV']; - } -} - -if (!$env) { - PhabricatorStartup::didFatal( - "The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ". - "your httpd.conf to include 'SetEnv PHABRICATOR_ENV ', where '' ". - "is one of 'development', 'production', or a custom environment."); -} - - -require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php'; - try { - setup_aphront_basics(); + PhabricatorStartup::loadCoreLibraries(); - $overseer = new PhabricatorRequestOverseer(); - $overseer->didStartup(); - - $conf = phabricator_read_config_file($env); - $conf['phabricator.env'] = $env; - - PhabricatorEnv::setEnvConfig($conf); - - // This needs to be done before we create the log, because - // PhabricatorAccessLog::getLog() calls date() - $tz = PhabricatorEnv::getEnvConfig('phabricator.timezone'); - if ($tz) { - date_default_timezone_set($tz); - } - - // Append any paths to $PATH if we need to. - $paths = PhabricatorEnv::getEnvConfig('environment.append-paths'); - if (!empty($paths)) { - $current_env_path = getenv('PATH'); - $new_env_paths = implode(':', $paths); - putenv('PATH='.$current_env_path.':'.$new_env_paths); - } + PhabricatorEnv::initializeWebEnvironment(); // This is the earliest we can get away with this, we need env config first. PhabricatorAccessLog::init(); @@ -63,15 +23,9 @@ try { DarkConsoleXHProfPluginAPI::hookProfiler(); - PhutilErrorHandler::initialize(); - PhutilErrorHandler::setErrorListener( array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); - foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) { - phutil_load_library($library); - } - if (PhabricatorEnv::getEnvConfig('phabricator.setup')) { try { PhabricatorSetup::runSetup(); @@ -84,11 +38,6 @@ try { phabricator_detect_bad_base_uri(); - $translation = PhabricatorEnv::newObjectFromConfig('translation.provider'); - PhutilTranslator::getInstance() - ->setLanguage($translation->getLanguage()) - ->addTranslations($translation->getTranslations()); - $host = $_SERVER['HTTP_HOST']; $path = $_REQUEST['__path__']; @@ -216,38 +165,6 @@ try { PhabricatorStartup::didFatal("[Exception] ".$ex->getMessage()); } - -/** - * @group aphront - */ -function setup_aphront_basics() { - $aphront_root = dirname(dirname(__FILE__)); - $libraries_root = dirname($aphront_root); - - $root = null; - if (!empty($_SERVER['PHUTIL_LIBRARY_ROOT'])) { - $root = $_SERVER['PHUTIL_LIBRARY_ROOT']; - } - - ini_set( - 'include_path', - $libraries_root.PATH_SEPARATOR.ini_get('include_path')); - @include_once $root.'libphutil/src/__phutil_library_init__.php'; - if (!@constant('__LIBPHUTIL__')) { - echo "ERROR: Unable to load libphutil. Put libphutil/ next to ". - "phabricator/, or update your PHP 'include_path' to include ". - "the parent directory of libphutil/.\n"; - exit(1); - } - - // Load Phabricator itself using the absolute path, so we never end up doing - // anything surprising (loading index.php and libraries from different - // directories). - phutil_load_library($aphront_root.'/src'); - phutil_load_library('arcanist/src'); - -} - function phabricator_detect_bad_base_uri() { $conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $uri = new PhutilURI($conf);