1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

Move a lot of pre-request checks to PhabricatorStartup

Summary:
We have a lot of mess to get through before we can load libphutil and enter Phabricator code properly. Move it to a dedicated class.

I'm probably going to merge PhabricatorRequestOverseer into this, although the check that lives there now is kind of weird. It also does not really need to be a pre-load check and could be handled better.

I stopped shoving stuff in here once I got to ENV stuff, I'm going to tackle that next.

Test Plan: Ran phabricator normally; introduced fatals and misconfigurations. Grepped for changed symbols.

Reviewers: btrahan, vrana

Reviewed By: btrahan

CC: aran, asherkin

Maniphest Tasks: T2223

Differential Revision: https://secure.phabricator.com/D4282
This commit is contained in:
epriestley 2012-12-25 06:11:39 -08:00
parent a88b69a4b6
commit ed58f6c5f4
4 changed files with 241 additions and 114 deletions

View file

@ -15,6 +15,9 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
return 'Information about services.'; return 'Information about services.';
} }
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function generateData() { public function generateData() {
$log = PhutilServiceProfiler::getInstance()->getServiceCallLog(); $log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
@ -130,7 +133,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
} }
return array( return array(
'start' => $GLOBALS['__start__'], 'start' => PhabricatorStartup::getStartTime(),
'end' => microtime(true), 'end' => microtime(true),
'log' => $log, 'log' => $log,
); );

View file

@ -19,6 +19,8 @@ final class PhabricatorRequestOverseer {
* to the documentation the stream isn't available for "multipart/form-data" * 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 * (on nginx + php-fpm it appears that it is available, though, at least) so
* any attempt to generate $_POST would be fragile. * any attempt to generate $_POST would be fragile.
*
* @phutil-external-symbol class PhabricatorStartup
*/ */
private function detectPostMaxSizeTriggered() { private function detectPostMaxSizeTriggered() {
// If this wasn't a POST, we're fine. // If this wasn't a POST, we're fine.
@ -83,7 +85,7 @@ final class PhabricatorRequestOverseer {
// populated into $_POST, but it wasn't. // populated into $_POST, but it wasn't.
$config = ini_get('post_max_size'); $config = ini_get('post_max_size');
$this->fatal( PhabricatorStartup::didFatal(
"As received by the server, this request had a nonzero content length ". "As received by the server, this request had a nonzero content length ".
"but no POST data.\n\n". "but no POST data.\n\n".
"Normally, this indicates that it exceeds the 'post_max_size' setting ". "Normally, this indicates that it exceeds the 'post_max_size' setting ".
@ -93,14 +95,4 @@ final class PhabricatorRequestOverseer {
"'post_max_size' is set to '{$config}'."); "'post_max_size' is set to '{$config}'.");
} }
/**
* Defined in webroot/index.php.
* TODO: Move here.
*
* @phutil-external-symbol function phabricator_fatal
*/
public function fatal($message) {
phabricator_fatal('FATAL ERROR: '.$message);
}
} }

View file

@ -0,0 +1,222 @@
<?php
/**
* Handle request startup, before loading the environment or libraries. This
* class bootstraps the request state up to the point where we can enter
* Phabricator code.
*
* NOTE: This class MUST NOT have any dependencies. It runs before libraries
* load.
*
* @task info Accessing Request Information
* @task hook Startup Hooks
* @task apocalypse In Case Of Apocalypse
* @task validation Validation
*/
final class PhabricatorStartup {
private static $startTime;
private static $globals = array();
/* -( Accessing Request Information )-------------------------------------- */
/**
* @task info
*/
public static function getStartTime() {
return self::$startTime;
}
/**
* @task info
*/
public static function setGlobal($key, $value) {
self::validateGlobal($key);
self::$globals[$key] = $value;
}
/**
* @task info
*/
public static function getGlobal($key, $default = null) {
self::validateGlobal($key);
if (!array_key_exists($key, self::$globals)) {
return $default;
}
return self::$globals[$key];
}
/* -( Startup Hooks )------------------------------------------------------ */
/**
* @task hook
*/
public static function didStartup() {
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
// same request, but also against repeated requests to the same
// interpreter state, which we may implement in the future.
register_shutdown_function(array(__CLASS__, 'didShutdown'));
$registered = true;
}
}
/**
* @task hook
*/
public static function didShutdown() {
$event = error_get_last();
if (!$event) {
return;
}
switch ($event['type']) {
case E_ERROR:
case E_PARSE:
case E_COMPILE_ERROR:
break;
default:
return;
}
$msg = ">>> UNRECOVERABLE FATAL ERROR <<<\n\n";
if ($event) {
// Even though we should be emitting this as text-plain, escape things
// just to be sure since we can't really be sure what the program state
// is when we get here.
$msg .= htmlspecialchars(
$event['message']."\n\n".$event['file'].':'.$event['line'],
ENT_QUOTES,
'UTF-8');
}
// flip dem tables
$msg .= "\n\n\n";
$msg .= "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb\x20\xef\xb8\xb5\x20\xc2\xaf".
"\x5c\x5f\x28\xe3\x83\x84\x29\x5f\x2f\xc2\xaf\x20\xef\xb8\xb5\x20".
"\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb";
self::didFatal($msg);
}
/* -( In Case of Apocalypse )---------------------------------------------- */
/**
* @task apocalypse
*/
public static function didFatal($message) {
$access_log = self::getGlobal('log.access');
if ($access_log) {
try {
$access_log->setData(
array(
'c' => 500,
));
$access_log->write();
} catch (Exception $ex) {
$message .= "\n(Moreover, unable to write to access log.)";
}
}
header(
'Content-Type: text/plain; charset=utf-8',
$replace = true,
$http_error = 500);
error_log($message);
echo $message;
exit(1);
}
/* -( Validation )--------------------------------------------------------- */
/**
* @task valiation
*/
private static function setupPHP() {
error_reporting(E_ALL | E_STRICT);
ini_set('memory_limit', -1);
}
/**
* @task valiation
*/
private static function verifyPHP() {
$required_version = '5.2.3';
if (version_compare(PHP_VERSION, $required_version) < 0) {
self::didFatal(
"You are running PHP version '".PHP_VERSION."', which is older than ".
"the minimum version, '{$required_version}'. Update to at least ".
"'{$required_version}'.");
}
if (get_magic_quotes_gpc()) {
self::didFatal(
"Your server is configured with PHP 'magic_quotes_gpc' enabled. This ".
"feature is 'highly discouraged' by PHP's developers and you must ".
"disable it to run Phabricator. Consult the PHP manual for ".
"instructions.");
}
}
/**
* @task valiation
*/
private static function verifyRewriteRules() {
if (isset($_REQUEST['__path__'])) {
return;
}
if (php_sapi_name() == 'cli-server') {
// Compatibility with PHP 5.4+ built-in web server.
$url = parse_url($_SERVER['REQUEST_URI']);
$_REQUEST['__path__'] = $url['path'];
} else {
self::didFatal(
"Request parameter '__path__' is not set. Your rewrite rules ".
"are not configured correctly.");
}
}
/**
* @task valiation
*/
private static function validateGlobal($key) {
static $globals = array(
'log.access' => true,
);
if (empty($globals[$key])) {
throw new Exception("Access to unknown startup global '{$key}'!");
}
}
}

View file

@ -1,20 +1,10 @@
<?php <?php
$__start__ = microtime(true); require_once dirname(dirname(__FILE__)).'/support/PhabricatorStartup.php';
PhabricatorStartup::didStartup();
$access_log = null; $access_log = null;
error_reporting(E_ALL | E_STRICT);
$required_version = '5.2.3';
if (version_compare(PHP_VERSION, $required_version) < 0) {
phabricator_fatal_config_error(
"You are running PHP version '".PHP_VERSION."', which is older than ".
"the minimum version, '{$required_version}'. Update to at least ".
"'{$required_version}'.");
}
ini_set('memory_limit', -1);
$env = getenv('PHABRICATOR_ENV'); // Apache $env = getenv('PHABRICATOR_ENV'); // Apache
if (!$env) { if (!$env) {
if (isset($_ENV['PHABRICATOR_ENV'])) { if (isset($_ENV['PHABRICATOR_ENV'])) {
@ -23,31 +13,12 @@ if (!$env) {
} }
if (!$env) { if (!$env) {
phabricator_fatal_config_error( PhabricatorStartup::didFatal(
"The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ". "The 'PHABRICATOR_ENV' environmental variable is not defined. Modify ".
"your httpd.conf to include 'SetEnv PHABRICATOR_ENV <env>', where '<env>' ". "your httpd.conf to include 'SetEnv PHABRICATOR_ENV <env>', where '<env>' ".
"is one of 'development', 'production', or a custom environment."); "is one of 'development', 'production', or a custom environment.");
} }
if (!isset($_REQUEST['__path__'])) {
if (php_sapi_name() == 'cli-server') {
// Compatibility with PHP 5.4+ built-in web server.
$url = parse_url($_SERVER['REQUEST_URI']);
$_REQUEST['__path__'] = $url['path'];
} else {
phabricator_fatal_config_error(
"__path__ is not set. Your rewrite rules are not configured correctly.");
}
}
if (get_magic_quotes_gpc()) {
phabricator_fatal_config_error(
"Your server is configured with PHP 'magic_quotes_gpc' enabled. This ".
"feature is 'highly discouraged' by PHP's developers and you must ".
"disable it to run Phabricator. Consult the PHP manual for instructions.");
}
register_shutdown_function('phabricator_shutdown');
require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php'; require_once dirname(dirname(__FILE__)).'/conf/__init_conf__.php';
@ -81,6 +52,7 @@ try {
PhabricatorAccessLog::init(); PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog(); $access_log = PhabricatorAccessLog::getLog();
if ($access_log) { if ($access_log) {
PhabricatorStartup::setGlobal('log.access', $access_log);
$access_log->setData( $access_log->setData(
array( array(
'R' => idx($_SERVER, 'HTTP_REFERER', '-'), 'R' => idx($_SERVER, 'HTTP_REFERER', '-'),
@ -193,7 +165,7 @@ try {
$ex, $ex,
)); ));
} }
phabricator_fatal('[Rendering Exception] '.$ex->getMessage()); PhabricatorStartup::didFatal('[Rendering Exception] '.$ex->getMessage());
} }
$write_guard->dispose(); $write_guard->dispose();
@ -211,10 +183,11 @@ try {
$sink->writeData($response_string); $sink->writeData($response_string);
if ($access_log) { if ($access_log) {
$request_start = PhabricatorStartup::getStartTime();
$access_log->setData( $access_log->setData(
array( array(
'c' => $response->getHTTPResponseCode(), 'c' => $response->getHTTPResponseCode(),
'T' => (int)(1000000 * (microtime(true) - $__start__)), 'T' => (int)(1000000 * (microtime(true) - $request_start)),
)); ));
$access_log->write(); $access_log->write();
} }
@ -240,7 +213,7 @@ try {
} }
} catch (Exception $ex) { } catch (Exception $ex) {
phabricator_fatal("[Exception] ".$ex->getMessage()); PhabricatorStartup::didFatal("[Exception] ".$ex->getMessage());
} }
@ -275,10 +248,6 @@ function setup_aphront_basics() {
} }
function phabricator_fatal_config_error($msg) {
phabricator_fatal("CONFIG ERROR: ".$msg."\n");
}
function phabricator_detect_bad_base_uri() { function phabricator_detect_bad_base_uri() {
$conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); $conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$uri = new PhutilURI($conf); $uri = new PhutilURI($conf);
@ -287,13 +256,14 @@ function phabricator_detect_bad_base_uri() {
case 'https': case 'https':
break; break;
default: default:
return phabricator_fatal_config_error( PhabricatorStartup::didFatal(
"'phabricator.base-uri' is set to '{$conf}', which is invalid. ". "'phabricator.base-uri' is set to '{$conf}', which is invalid. ".
"The URI must start with 'http://' or 'https://'."); "The URI must start with 'http://' or 'https://'.");
return;
} }
if (strpos($uri->getDomain(), '.') === false) { if (strpos($uri->getDomain(), '.') === false) {
phabricator_fatal_config_error( PhabricatorStartup::didFatal(
"'phabricator.base-uri' is set to '{$conf}', which is invalid. The URI ". "'phabricator.base-uri' is set to '{$conf}', which is invalid. The URI ".
"must contain a dot ('.'), like 'http://example.com/', not just ". "must contain a dot ('.'), like 'http://example.com/', not just ".
"'http://example/'. Some web browsers will not set cookies on domains ". "'http://example/'. Some web browsers will not set cookies on domains ".
@ -304,63 +274,3 @@ function phabricator_detect_bad_base_uri() {
} }
} }
function phabricator_shutdown() {
$event = error_get_last();
if (!$event) {
return;
}
switch ($event['type']) {
case E_ERROR:
case E_PARSE:
case E_COMPILE_ERROR:
break;
default:
return;
}
$msg = ">>> UNRECOVERABLE FATAL ERROR <<<\n\n";
if ($event) {
// Even though we should be emitting this as text-plain, escape things just
// to be sure since we can't really be sure what the program state is when
// we get here.
$msg .= phutil_escape_html($event['message'])."\n\n";
$msg .= phutil_escape_html($event['file'].':'.$event['line']);
}
// flip dem tables
$msg .= "\n\n\n";
$msg .= "\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb\x20\xef\xb8\xb5\x20\xc2\xaf".
"\x5c\x5f\x28\xe3\x83\x84\x29\x5f\x2f\xc2\xaf\x20\xef\xb8\xb5\x20".
"\xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb";
phabricator_fatal($msg);
}
function phabricator_fatal($msg) {
global $access_log;
if ($access_log) {
try {
$access_log->setData(
array(
'c' => 500,
));
$access_log->write();
} catch (Exception $ex) {
$msg .= "\nMoreover unable to write to access log.";
}
}
header(
'Content-Type: text/plain; charset=utf-8',
$replace = true,
$http_error = 500);
error_log($msg);
echo $msg;
exit(1);
}