taskmaster".
'phd.start-taskmasters' => 4,
// Launch daemons in "verbose" mode by default. This creates a lot of output,
// but can help debug issues. Daemons launched in debug mode with "phd debug"
// are always launched in verbose mode. See also 'phd.trace'.
'phd.verbose' => false,
// Launch daemons in "trace" mode by default. This creates an ENORMOUS amount
// of output, but can help debug issues. Daemons launched in debug mode with
// "phd debug" are always launched in trace mdoe. See also 'phd.verbose'.
'phd.trace' => false,
// Path to custom celerity resource map relative to 'phabricator/src'.
// See also `scripts/celerity_mapper.php`.
'celerity.resource-path' => '__celerity_resource_map__.php',
// This value is an input to the hash function when building resource hashes.
// It has no security value, but if you accidentally poison user caches (by
// pushing a bad patch or having something go wrong with a CDN, e.g.) you can
// change this to something else and rebuild the Celerity map to break user
// caches. Unless you are doing Celerity development, it is exceptionally
// unlikely that you need to modify this.
'celerity.resource-hash' => 'd9455ea150622ee044f7931dabfa52aa',
// In a development environment, it is desirable to force static resources
// (CSS and JS) to be read from disk on every request, so that edits to them
// appear when you reload the page even if you haven't updated the resource
// maps. This setting ensures requests will be verified against the state on
// disk. Generally, you should leave this off in production (caching behavior
// and performance improve with it off) but turn it on in development. (These
// settings are the defaults.)
'celerity.force-disk-reads' => false,
// Minify static resources by removing whitespace and comments. You should
// enable this in production, but disable it in development.
'celerity.minify' => false,
// You can respond to various application events by installing listeners,
// which will receive callbacks when interesting things occur. Specify a list
// of classes which extend PhabricatorEventListener here.
'events.listeners' => array(),
// -- Syntax Highlighting --------------------------------------------------- //
// Phabricator can highlight PHP by default and use Pygments for other
// languages if enabled. You can provide a custom highlighter engine by
// extending class PhutilSyntaxHighlighterEngine.
'syntax-highlighter.engine' => 'PhutilDefaultSyntaxHighlighterEngine',
// If you want syntax highlighting for other languages than PHP then you can
// install the python package 'Pygments', make sure the 'pygmentize' script is
// available in the $PATH of the webserver, and then enable this.
'pygments.enabled' => false,
// In places that we display a dropdown to syntax-highlight code,
// this is where that list is defined.
// Syntax is 'lexer-name' => 'Display Name',
'pygments.dropdown-choices' => array(
'apacheconf' => 'Apache Configuration',
'bash' => 'Bash Scripting',
'brainfuck' => 'Brainf*ck',
'c' => 'C',
'cpp' => 'C++',
'css' => 'CSS',
'diff' => 'Diff',
'django' => 'Django Templating',
'erb' => 'Embedded Ruby/ERB',
'erlang' => 'Erlang',
'html' => 'HTML',
'infer' => 'Infer from title (extension)',
'java' => 'Java',
'js' => 'Javascript',
'mysql' => 'MySQL',
'perl' => 'Perl',
'php' => 'PHP',
'text' => 'Plain Text',
'python' => 'Python',
'rainbow' => 'Rainbow',
'remarkup' => 'Remarkup',
'ruby' => 'Ruby',
'xml' => 'XML',
),
'pygments.dropdown-default' => 'infer',
// This is an override list of regular expressions which allows you to choose
// what language files are highlighted as. If your projects have certain rules
// about filenames or use unusual or ambiguous language extensions, you can
// create a mapping here. This is an ordered dictionary of regular expressions
// which will be tested against the filename. They should map to either an
// explicit language as a string value, or a numeric index into the captured
// groups as an integer.
'syntax.filemap' => array(
// Example: Treat all '*.xyz' files as PHP.
// '@\\.xyz$@' => 'php',
// Example: Treat 'httpd.conf' as 'apacheconf'.
// '@/httpd\\.conf$@' => 'apacheconf',
// Example: Treat all '*.x.bak' file as '.x'. NOTE: we map to capturing
// group 1 by specifying the mapping as "1".
// '@\\.([^.]+)\\.bak$@' => 1,
'@\.arcconfig$@' => 'js',
'@\.divinerconfig$@' => 'js',
),
// Set the default monospaced font style for users who haven't set a custom
// style.
'style.monospace' => '10px "Menlo", "Consolas", "Monaco", monospace',
+// -- Debugging ------------------------------------------------------------- //
+
+ // Enable this to change HTTP redirects into normal pages with a link to the
+ // redirection target. For example, after you submit a form you'll get a page
+ // saying "normally, you'd be redirected...". This is useful to examine
+ // service or profiler information on write pathways, or debug redirects. It
+ // also makes the UX horrible for normal use, so you should enable it only
+ // when debugging.
+ //
+ // NOTE: This does not currently work for forms with Javascript "workflow",
+ // since the redirect happens in Javascript.
+ 'debug.stop-on-redirect' => false,
+
+ // Enable this to always profile every page. This is very slow! You should
+ // only enable it when debugging.
+ 'debug.profile-every-request' => false,
+
);
diff --git a/src/aphront/console/plugin/DarkConsoleXHProfPlugin.php b/src/aphront/console/plugin/DarkConsoleXHProfPlugin.php
index e2a03d7804..0c38b60f99 100644
--- a/src/aphront/console/plugin/DarkConsoleXHProfPlugin.php
+++ b/src/aphront/console/plugin/DarkConsoleXHProfPlugin.php
@@ -1,112 +1,112 @@
getData();
if ($run) {
return '• XHProf';
}
return 'XHProf';
}
public function getDescription() {
return 'Provides detailed PHP profiling information through XHProf.';
}
public function generateData() {
return $this->xhprofID;
}
public function getXHProfRunID() {
return $this->xhprofID;
}
public function render() {
if (!DarkConsoleXHProfPluginAPI::isProfilerAvailable()) {
$href = PhabricatorEnv::getDoclink('article/Installation_Guide.html');
$install_guide = phutil_render_tag(
'a',
array(
'href' => $href,
'class' => 'bright-link',
),
'Installation Guide');
return
''.
'The "xhprof" PHP extension is not available. Install xhprof '.
'to enable the XHProf console plugin. You can find instructions in '.
'the '.$install_guide.'.'.
'
';
}
$result = array();
$run = $this->getXHProfRunID();
$header =
'';
$result[] = $header;
if ($run) {
$result[] =
'Profile Permalink'.
'';
} else {
$result[] =
''.
'Profiling was not enabled for this page. Use the button above '.
'to enable it.'.
'
';
}
return implode("\n", $result);
}
public function willShutdown() {
- if (isset($_REQUEST['__profile__']) &&
- $_REQUEST['__profile__'] != 'all') {
+ if (DarkConsoleXHProfPluginAPI::isProfilerRequested() &&
+ (DarkConsoleXHProfPluginAPI::isProfilerRequested() !== 'all')) {
$this->xhprofID = DarkConsoleXHProfPluginAPI::stopProfiler();
}
}
}
diff --git a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php
index 38bc063aed..8ab312c4df 100644
--- a/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php
+++ b/src/aphront/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php
@@ -1,90 +1,127 @@
'application/xhprof',
- 'name' => 'profile.xhprof',
- ));
- return $file->getPHID();
+ // Since these happen on GET we can't do guarded writes. These also
+ // sometimes happen after we've disposed of the write guard; in this
+ // case we need to disable the whole mechanism.
+
+ $use_scope = AphrontWriteGuard::isGuardActive();
+ if ($use_scope) {
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ } else {
+ AphrontWriteGuard::allowDangerousUnguardedWrites(true);
+ }
+
+ $caught = null;
+ try {
+ $file = call_user_func(
+ array($file_class, 'newFromFileData'),
+ $data,
+ array(
+ 'mime-type' => 'application/xhprof',
+ 'name' => 'profile.xhprof',
+ ));
+ } catch (Exception $ex) {
+ $caught = $ex;
+ }
+
+ if ($use_scope) {
+ unset($unguarded);
+ } else {
+ AphrontWriteGuard::allowDangerousUnguardedWrites(false);
+ }
+
+ if ($caught) {
+ throw $caught;
+ } else {
+ return $file->getPHID();
+ }
} else {
return null;
}
}
}
diff --git a/src/aphront/response/AphrontRedirectResponse.php b/src/aphront/response/AphrontRedirectResponse.php
index f49681436b..7d060bb16b 100644
--- a/src/aphront/response/AphrontRedirectResponse.php
+++ b/src/aphront/response/AphrontRedirectResponse.php
@@ -1,49 +1,81 @@
uri = $uri;
return $this;
}
public function getURI() {
return (string)$this->uri;
}
+ public function shouldStopForDebugging() {
+ return PhabricatorEnv::getEnvConfig('debug.stop-on-redirect');
+ }
+
public function getHeaders() {
- $headers = array(
- array('Location', $this->uri),
- );
+ $headers = array();
+ if (!$this->shouldStopForDebugging()) {
+ $headers[] = array('Location', $this->uri);
+ }
$headers = array_merge(parent::getHeaders(), $headers);
return $headers;
}
public function buildResponseString() {
+ if ($this->shouldStopForDebugging()) {
+ $view = new PhabricatorStandardPageView();
+ $view->setRequest($this->getRequest());
+ $view->setApplicationName('Debug');
+ $view->setTitle('Stopped on Redirect');
+
+ $error = new AphrontErrorView();
+ $error->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $error->setTitle('Stopped on Redirect');
+
+ $link = phutil_render_tag(
+ 'a',
+ array(
+ 'href' => $this->getURI(),
+ ),
+ 'Continue to: '.phutil_escape_html($this->getURI()));
+
+ $error->appendChild(
+ 'You were stopped here because debug.stop-on-redirect '.
+ 'is set in your configuration.
'.
+ ''.$link.'
');
+
+ $view->appendChild($error);
+
+ return $view->render();
+ }
+
return '';
}
}
diff --git a/src/aphront/writeguard/AphrontWriteGuard.php b/src/aphront/writeguard/AphrontWriteGuard.php
index 38d6ce7ad2..e0a61284cf 100644
--- a/src/aphront/writeguard/AphrontWriteGuard.php
+++ b/src/aphront/writeguard/AphrontWriteGuard.php
@@ -1,256 +1,267 @@
dispose();
*
* Normally, you do not need to manage guards yourself -- the Aphront stack
* handles it for you.
*
* @param AphrontRequest Request to read CSRF token information from.
* @return this
* @task manage
*/
public function __construct(AphrontRequest $request) {
if (self::$instance) {
throw new Exception(
"An AphrontWriteGuard already exists. Dispose of the previous guard ".
"before creating a new one.");
}
if (self::$allowUnguardedWrites) {
throw new Exception(
"An AphrontWriteGuard is being created in a context which permits ".
"unguarded writes unconditionally. This is not allowed and indicates ".
"a serious error.");
}
$this->request = $request;
self::$instance = $this;
}
/**
* Dispose of the active write guard. You must call this method when you are
* done with a write guard. You do not normally need to call this yourself.
*
* @return void
* @task manage
*/
public function dispose() {
if ($this->allowDepth > 0) {
throw new Exception(
"Imbalanced AphrontWriteGuard: more beginUnguardedWrites() calls than ".
"endUnguardedWrites() calls.");
}
self::$instance = null;
}
+ /**
+ * Determine if there is an active write guard.
+ *
+ * @return bool
+ * @task manage
+ */
+ public static function isGuardActive() {
+ return (bool)self::$instance;
+ }
+
+
/* -( Protecting Writes )-------------------------------------------------- */
/**
* Declare intention to perform a write, validating that writes are allowed.
* You should call this method before executing a write whenever you implement
* a new storage engine where information can be permanently kept.
*
* Writes are permitted if:
*
* - The request has valid CSRF tokens.
* - Unguarded writes have been temporarily enabled by a call to
* @{method:beginUnguardedWrites}.
* - All write guarding has been disabled with
* @{method:allowDangerousUnguardedWrites}.
*
* If none of these conditions are true, this method will throw and prevent
* the write.
*
* @return void
* @task protect
*/
public static function willWrite() {
if (!self::$instance) {
if (!self::$allowUnguardedWrites) {
throw new Exception(
"Unguarded write! There must be an active AphrontWriteGuard to ".
"perform writes.");
} else {
// Unguarded writes are being allowed unconditionally.
return;
}
}
$instance = self::$instance;
if ($instance->allowDepth == 0) {
$instance->request->validateCSRF();
}
}
/* -( Disabling Write Protection )----------------------------------------- */
/**
* Enter a scope which permits unguarded writes. This works like
* @{method:beginUnguardedWrites} but returns an object which will end
* the unguarded write scope when its __destruct() method is called. This
* is useful to more easily handle exceptions correctly in unguarded write
* blocks:
*
* // Restores the guard even if do_logging() throws.
* function unguarded_scope() {
* $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
* do_logging();
* }
*
* @return AphrontScopedUnguardedWriteCapability Object which ends unguarded
* writes when it leaves scope.
* @task disable
*/
public static function beginScopedUnguardedWrites() {
self::beginUnguardedWrites();
return new AphrontScopedUnguardedWriteCapability();
}
/**
* Begin a block which permits unguarded writes. You should use this very
* sparingly, and only for things like logging where CSRF is not a concern.
*
* You must pair every call to @{method:beginUnguardedWrites} with a call to
* @{method:endUnguardedWrites}:
*
* AphrontWriteGuard::beginUnguardedWrites();
* do_logging();
* AphrontWriteGuard::endUnguardedWrites();
*
* @return void
* @task disable
*/
public static function beginUnguardedWrites() {
if (!self::$instance) {
return;
}
self::$instance->allowDepth++;
}
/**
* Declare that you have finished performing unguarded writes. You must
* call this exactly once for each call to @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function endUnguardedWrites() {
if (!self::$instance) {
return;
}
if (self::$instance->allowDepth <= 0) {
throw new Exception(
"Imbalanced AphrontWriteGuard: more endUnguardedWrites() calls than ".
"beginUnguardedWrites() calls.");
}
self::$instance->allowDepth--;
}
/**
* Allow execution of unguarded writes. This is ONLY appropriate for use in
* script contexts or other contexts where you are guaranteed to never be
* vulnerable to CSRF concerns. Calling this method is EXTREMELY DANGEROUS
* if you do not understand the consequences.
*
* If you need to perform unguarded writes on an otherwise guarded workflow
* which is vulnerable to CSRF, use @{method:beginUnguardedWrites}.
*
* @return void
* @task disable
*/
public static function allowDangerousUnguardedWrites($allow) {
if (self::$instance) {
throw new Exception(
"You can not unconditionally disable AphrontWriteGuard by calling ".
"allowDangerousUnguardedWrites() while a write guard is active. Use ".
"beginUnguardedWrites() to temporarily allow unguarded writes.");
}
self::$allowUnguardedWrites = true;
}
/* -( Internals )---------------------------------------------------------- */
/**
* When the object is destroyed, make sure @{method:dispose} was called.
*/
public function __destruct() {
if (isset(self::$instance)) {
throw new Exception(
"AphrontWriteGuard was not properly disposed of! Call dispose() on ".
"every AphrontWriteGuard object you instantiate.");
}
}
}
diff --git a/webroot/index.php b/webroot/index.php
index 1b5076134a..cfca18e57a 100644
--- a/webroot/index.php
+++ b/webroot/index.php
@@ -1,392 +1,392 @@
', where '' ".
"is one of 'development', 'production', or a custom environment.");
}
if (!isset($_REQUEST['__path__'])) {
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';
try {
setup_aphront_basics();
$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);
}
// This is the earliest we can get away with this, we need env config first.
PhabricatorAccessLog::init();
$access_log = PhabricatorAccessLog::getLog();
if ($access_log) {
$access_log->setData(
array(
'R' => idx($_SERVER, 'HTTP_REFERER', '-'),
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
));
}
DarkConsoleXHProfPluginAPI::hookProfiler();
PhutilErrorHandler::initialize();
} catch (Exception $ex) {
phabricator_fatal("[Initialization Exception] ".$ex->getMessage());
}
PhutilErrorHandler::setErrorListener(
array('DarkConsoleErrorLogPluginAPI', 'handleErrors'));
foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) {
phutil_load_library($library);
}
if (PhabricatorEnv::getEnvConfig('phabricator.setup')) {
try {
PhabricatorSetup::runSetup();
} catch (Exception $ex) {
echo "EXCEPTION!\n";
echo $ex;
}
return;
}
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__'];
switch ($host) {
default:
$config_key = 'aphront.default-application-configuration-class';
$application = PhabricatorEnv::newObjectFromConfig($config_key);
break;
}
$application->setHost($host);
$application->setPath($path);
$application->willBuildRequest();
$request = $application->buildRequest();
$write_guard = new AphrontWriteGuard($request);
PhabricatorEventEngine::initialize();
$application->setRequest($request);
list($controller, $uri_data) = $application->buildController();
if ($access_log) {
$access_log->setData(
array(
'U' => (string)$request->getRequestURI()->getPath(),
'C' => get_class($controller),
));
}
// If execution throws an exception and then trying to render that exception
// throws another exception, we want to show the original exception, as it is
// likely the root cause of the rendering exception.
$original_exception = null;
try {
$response = $controller->willBeginExecution();
if ($access_log) {
if ($request->getUser() && $request->getUser()->getPHID()) {
$access_log->setData(
array(
'u' => $request->getUser()->getUserName(),
));
}
}
if (!$response) {
$controller->willProcessRequest($uri_data);
$response = $controller->processRequest();
}
} catch (AphrontRedirectException $ex) {
$response = id(new AphrontRedirectResponse())
->setURI($ex->getURI());
} catch (Exception $ex) {
$original_exception = $ex;
$response = $application->handleException($ex);
}
try {
$response = $application->willSendResponse($response);
$response->setRequest($request);
$response_string = $response->buildResponseString();
} catch (Exception $ex) {
$write_guard->dispose();
if ($access_log) {
$access_log->write();
}
if ($original_exception) {
$ex = new PhutilAggregateException(
"Multiple exceptions during processing and rendering.",
array(
$original_exception,
$ex,
));
}
phabricator_fatal('[Rendering Exception] '.$ex->getMessage());
}
$write_guard->dispose();
// TODO: Share the $sink->writeResponse() pathway here?
$sink = new AphrontPHPHTTPSink();
$sink->writeHTTPStatus($response->getHTTPResponseCode());
$headers = $response->getCacheHeaders();
$headers = array_merge($headers, $response->getHeaders());
$sink->writeHeaders($headers);
// TODO: This shouldn't be possible in a production-configured environment.
-if (isset($_REQUEST['__profile__']) &&
- ($_REQUEST['__profile__'] == 'all')) {
+if (DarkConsoleXHProfPluginAPI::isProfilerRequested() &&
+ DarkConsoleXHProfPluginAPI::isProfilerRequested() === 'all') {
$profile = DarkConsoleXHProfPluginAPI::stopProfiler();
$profile =
'';
if (strpos($response_string, '') !== false) {
$response_string = str_replace(
'',
''.$profile,
$response_string);
} else {
$sink->writeData($profile);
}
}
$sink->writeData($response_string);
if ($access_log) {
$access_log->setData(
array(
'c' => $response->getHTTPResponseCode(),
'T' => (int)(1000000 * (microtime(true) - $__start__)),
));
$access_log->write();
}
/**
* @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_fatal_config_error($msg) {
phabricator_fatal("CONFIG ERROR: ".$msg."\n");
}
function phabricator_detect_bad_base_uri() {
$conf = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$uri = new PhutilURI($conf);
switch ($uri->getProtocol()) {
case 'http':
case 'https':
break;
default:
return phabricator_fatal_config_error(
"'phabricator.base-uri' is set to '{$conf}', which is invalid. ".
"The URI must start with 'http://' or 'https://'.");
}
if (strpos($uri->getDomain(), '.') === false) {
phabricator_fatal_config_error(
"'phabricator.base-uri' is set to '{$conf}', which is invalid. The URI ".
"must contain a dot ('.'), like 'http://example.com/', not just ".
"'http://example/'. Some web browsers will not set cookies on domains ".
"with no TLD, and Phabricator requires cookies for login. ".
"If you are using localhost, create an entry in the hosts file like ".
"'127.0.0.1 example.com', and access the localhost with ".
"'http://example.com/'.");
}
}
function phabricator_detect_insane_memory_limit() {
$memory_limit = ini_get('memory_limit');
$char_limit = 12;
if (strlen($memory_limit) <= $char_limit) {
return;
}
// colmdoyle ran into an issue on an Ubuntu box with Suhosin where his
// 'memory_limit' was set to:
//
// 3232323232323232323232323232323232323232323232323232323232323232M
//
// Not a typo. A wizard did it.
//
// Anyway, with this 'memory_limit', the machine would immediately fatal
// when executing the ini_set() later. I wasn't able to reproduce this on my
// EC2 Ubuntu + Suhosin box, but verified that it caused the problem on his
// machine and that setting it to a more sensible value fixed it. Since I
// have no idea how to actually trigger the issue, we look for a coarse
// approximation of it (a memory_limit setting more than 12 characters in
// length).
phabricator_fatal_config_error(
"Your PHP 'memory_limit' is set to something ridiculous ".
"(\"{$memory_limit}\"). Set it to a more reasonable value (it must be no ".
"more than {$char_limit} characters long).");
}
function phabricator_shutdown() {
$event = error_get_last();
if (!$event) {
return;
}
if ($event['type'] != E_ERROR && $event['type'] != E_PARSE) {
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) {
$access_log->setData(
array(
'c' => 500,
));
$access_log->write();
}
header(
'Content-Type: text/plain; charset=utf-8',
$replace = true,
$http_error = 500);
error_log($msg);
echo $msg;
exit(1);
}