Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/config/check/PhabricatorSetupCheckDaemons.php b/src/applications/config/check/PhabricatorSetupCheckDaemons.php
index 8ea68debbe..9e02ac4ed9 100644
--- a/src/applications/config/check/PhabricatorSetupCheckDaemons.php
+++ b/src/applications/config/check/PhabricatorSetupCheckDaemons.php
@@ -1,86 +1,93 @@
<?php
final class PhabricatorSetupCheckDaemons extends PhabricatorSetupCheck {
protected function executeChecks() {
$task_daemon = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->withDaemonClasses(array('PhabricatorTaskmasterDaemon'))
->setLimit(1)
->execute();
if (!$task_daemon) {
$doc_href = PhabricatorEnv::getDocLink(
'Managing Daemons with phd');
$summary = pht(
'You must start the Phabricator daemons to send email, rebuild '.
'search indexes, and do other background processing.');
$message = pht(
'The Phabricator daemons are not running, so Phabricator will not '.
'be able to perform background processing (including sending email, '.
'rebuilding search indexes, importing commits, cleaning up old data, '.
'running builds, etc.).'.
"\n\n".
'Use %s to start daemons. See %s for more information.',
phutil_tag('tt', array(), 'bin/phd start'),
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank'
),
pht('Managing Daemons with phd')));
$this->newIssue('daemons.not-running')
->setShortName(pht('Daemons Not Running'))
->setName(pht('Phabricator Daemons Are Not Running'))
->setSummary($summary)
->setMessage($message)
->addCommand('phabricator/ $ ./bin/phd start');
}
$environment_hash = PhabricatorEnv::calculateEnvironmentHash();
$all_daemons = id(new PhabricatorDaemonLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE)
->execute();
foreach ($all_daemons as $daemon) {
if ($daemon->getEnvHash() != $environment_hash) {
$doc_href = PhabricatorEnv::getDocLink(
'Managing Daemons with phd');
$summary = pht(
'You should restart the daemons. Their configuration is out of '.
'date.');
$message = pht(
'The Phabricator daemons are running with an out of date '.
'configuration. If you are making multiple configuration changes, '.
'you only need to restart the daemons once after the last change.'.
"\n\n".
- 'Use %s to restart daemons. See %s for more information.',
+ 'Use %s to restart daemons. See the %s or %s for more information.',
phutil_tag('tt', array(), 'bin/phd restart'),
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => '/daemon/',
+ 'target' => '_blank'
+ ),
+ pht('Daemon Console')),
phutil_tag(
'a',
array(
'href' => $doc_href,
'target' => '_blank'
),
pht('Managing Daemons with phd')));
$this->newIssue('daemons.need-restarting')
->setShortName(pht('Daemons Need Restarting'))
->setName(pht('Phabricator Daemons Need Restarting'))
->setSummary($summary)
->setMessage($message)
->addCommand('phabricator/ $ ./bin/phd restart');
break;
}
}
}
}
diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
index ef6315bfc2..10ee28471d 100644
--- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
@@ -1,191 +1,199 @@
<?php
final class PhabricatorDaemonLogViewController
extends PhabricatorDaemonController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$log = id(new PhabricatorDaemonLogQuery())
->setViewer($user)
->withIDs(array($this->id))
->setAllowStatusWrites(true)
->executeOne();
if (!$log) {
return new Aphront404Response();
}
$events = id(new PhabricatorDaemonLogEvent())->loadAllWhere(
'logID = %d ORDER BY id DESC LIMIT 1000',
$log->getID());
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Daemon %s', $log->getID()));
$header = id(new PHUIHeaderView())
->setHeader($log->getDaemon());
$tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE);
$status = $log->getStatus();
switch ($status) {
case PhabricatorDaemonLog::STATUS_UNKNOWN:
$tag->setBackgroundColor(PHUITagView::COLOR_ORANGE);
$tag->setName(pht('Unknown'));
break;
case PhabricatorDaemonLog::STATUS_RUNNING:
$tag->setBackgroundColor(PHUITagView::COLOR_GREEN);
$tag->setName(pht('Running'));
break;
case PhabricatorDaemonLog::STATUS_DEAD:
$tag->setBackgroundColor(PHUITagView::COLOR_RED);
$tag->setName(pht('Dead'));
break;
case PhabricatorDaemonLog::STATUS_WAIT:
$tag->setBackgroundColor(PHUITagView::COLOR_BLUE);
$tag->setName(pht('Waiting'));
break;
case PhabricatorDaemonLog::STATUS_EXITING:
$tag->setBackgroundColor(PHUITagView::COLOR_YELLOW);
$tag->setName(pht('Exiting'));
break;
case PhabricatorDaemonLog::STATUS_EXITED:
$tag->setBackgroundColor(PHUITagView::COLOR_GREY);
$tag->setName(pht('Exited'));
break;
}
$header->addTag($tag);
+ $env_hash = PhabricatorEnv::calculateEnvironmentHash();
+ if ($log->getEnvHash() != $env_hash) {
+ $tag = id(new PHUITagView())
+ ->setType(PHUITagView::TYPE_STATE)
+ ->setBackgroundColor(PHUITagView::COLOR_YELLOW)
+ ->setName(pht('Stale Config'));
+ $header->addTag($tag);
+ }
$properties = $this->buildPropertyListView($log);
$event_view = id(new PhabricatorDaemonLogEventsView())
->setUser($user)
->setEvents($events);
$event_panel = new AphrontPanelView();
$event_panel->setNoBackground();
$event_panel->appendChild($event_view);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
$event_panel,
),
array(
'title' => pht('Daemon Log'),
'device' => false,
));
}
private function buildPropertyListView(PhabricatorDaemonLog $daemon) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$id = $daemon->getID();
$c_epoch = $daemon->getDateCreated();
$u_epoch = $daemon->getDateModified();
$unknown_time = PhabricatorDaemonLogQuery::getTimeUntilUnknown();
$dead_time = PhabricatorDaemonLogQuery::getTimeUntilDead();
$wait_time = PhutilDaemonOverseer::RESTART_WAIT;
$details = null;
$status = $daemon->getStatus();
switch ($status) {
case PhabricatorDaemonLog::STATUS_RUNNING:
$details = pht(
'This daemon is running normally and reported a status update '.
'recently (within %s).',
phutil_format_relative_time($unknown_time));
break;
case PhabricatorDaemonLog::STATUS_UNKNOWN:
$details = pht(
'This daemon has not reported a status update recently (within %s). '.
'It may have exited abruptly. After %s, it will be presumed dead.',
phutil_format_relative_time($unknown_time),
phutil_format_relative_time($dead_time));
break;
case PhabricatorDaemonLog::STATUS_DEAD:
$details = pht(
'This daemon did not report a status update for %s. It is '.
'presumed dead. Usually, this indicates that the daemon was '.
'killed or otherwise exited abruptly with an error. You may '.
'need to restart it.',
phutil_format_relative_time($dead_time));
break;
case PhabricatorDaemonLog::STATUS_WAIT:
$details = pht(
'This daemon is running normally and reported a status update '.
'recently (within %s). However, it encountered an error while '.
'doing work and is waiting a little while (%s) to resume '.
'processing. After encountering an error, daemons wait before '.
'resuming work to avoid overloading services.',
phutil_format_relative_time($unknown_time),
phutil_format_relative_time($wait_time));
break;
case PhabricatorDaemonLog::STATUS_EXITING:
$details = pht(
'This daemon is shutting down gracefully.');
break;
case PhabricatorDaemonLog::STATUS_EXITED:
$details = pht(
'This daemon exited normally and is no longer running.');
break;
}
$view->addProperty(pht('Status Details'), $details);
$view->addProperty(pht('Daemon Class'), $daemon->getDaemon());
$view->addProperty(pht('Host'), $daemon->getHost());
$view->addProperty(pht('PID'), $daemon->getPID());
$view->addProperty(pht('Started'), phabricator_datetime($c_epoch, $viewer));
$view->addProperty(
pht('Seen'),
pht(
'%s ago (%s)',
phutil_format_relative_time(time() - $u_epoch),
phabricator_datetime($u_epoch, $viewer)));
$argv = $daemon->getArgv();
if (is_array($argv)) {
$argv = implode("\n", $argv);
}
$view->addProperty(
pht('Argv'),
phutil_tag(
'textarea',
array(
'style' => 'width: 100%; height: 12em;',
),
$argv));
$view->addProperty(
pht('View Full Logs'),
phutil_tag(
'tt',
array(),
"phabricator/ $ ./bin/phd log {$id}"));
return $view;
}
}
diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
index e9f7876d07..a1f668d480 100644
--- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php
+++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
@@ -1,79 +1,87 @@
<?php
final class PhabricatorDaemonLogListView extends AphrontView {
private $daemonLogs;
public function setDaemonLogs(array $daemon_logs) {
assert_instances_of($daemon_logs, 'PhabricatorDaemonLog');
$this->daemonLogs = $daemon_logs;
return $this;
}
public function render() {
$rows = array();
if (!$this->user) {
throw new Exception('Call setUser() before rendering!');
}
+ $env_hash = PhabricatorEnv::calculateEnvironmentHash();
$list = new PHUIObjectItemListView();
foreach ($this->daemonLogs as $log) {
$id = $log->getID();
$epoch = $log->getDateCreated();
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Daemon %s', $id))
->setHeader($log->getDaemon())
->setHref("/daemon/log/{$id}/")
->addIcon('none', phabricator_datetime($epoch, $this->user));
$status = $log->getStatus();
switch ($status) {
case PhabricatorDaemonLog::STATUS_RUNNING:
- $item->setBarColor('green');
- $item->addAttribute(pht('This daemon is running.'));
+ if ($env_hash != $log->getEnvHash()) {
+ $item->setBarColor('yellow');
+ $item->addAttribute(pht(
+ 'This daemon is running with an out of date configuration and '.
+ 'should be restarted.'));
+ } else {
+ $item->setBarColor('green');
+ $item->addAttribute(pht('This daemon is running.'));
+ }
break;
case PhabricatorDaemonLog::STATUS_DEAD:
$item->setBarColor('red');
$item->addAttribute(
pht(
'This daemon is lost or exited uncleanly, and is presumed '.
'dead.'));
$item->addIcon('fa-times grey', pht('Dead'));
break;
case PhabricatorDaemonLog::STATUS_EXITING:
$item->addAttribute(pht('This daemon is exiting.'));
$item->addIcon('fa-check', pht('Exiting'));
break;
case PhabricatorDaemonLog::STATUS_EXITED:
$item->setDisabled(true);
$item->addAttribute(pht('This daemon exited cleanly.'));
$item->addIcon('fa-check grey', pht('Exited'));
break;
case PhabricatorDaemonLog::STATUS_WAIT:
$item->setBarColor('blue');
$item->addAttribute(
pht(
'This daemon encountered an error recently and is waiting a '.
'moment to restart.'));
$item->addIcon('fa-clock-o grey', pht('Waiting'));
break;
case PhabricatorDaemonLog::STATUS_UNKNOWN:
default:
$item->setBarColor('orange');
$item->addAttribute(
pht(
'This daemon has not reported its status recently. It may '.
'have exited uncleanly.'));
$item->addIcon('fa-exclamation-circle', pht('Unknown'));
break;
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php
index d5e3237b88..a8f4dcec6b 100644
--- a/src/infrastructure/env/PhabricatorEnv.php
+++ b/src/infrastructure/env/PhabricatorEnv.php
@@ -1,571 +1,573 @@
<?php
/**
* Manages the execution environment configuration, exposing APIs to read
* configuration settings and other similar values that are derived directly
* from configuration settings.
*
*
* = Reading Configuration =
*
* The primary role of this class is to provide an API for reading
* Phabricator configuration, @{method:getEnvConfig}:
*
* $value = PhabricatorEnv::getEnvConfig('some.key', $default);
*
* The class also handles some URI construction based on configuration, via
* the methods @{method:getURI}, @{method:getProductionURI},
* @{method:getCDNURI}, and @{method:getDoclink}.
*
* For configuration which allows you to choose a class to be responsible for
* some functionality (e.g., which mail adapter to use to deliver email),
* @{method:newObjectFromConfig} provides a simple interface that validates
* the configured value.
*
*
* = Unit Test Support =
*
* In unit tests, you can use @{method:beginScopedEnv} to create a temporary,
* mutable environment. The method returns a scope guard object which restores
* the environment when it is destroyed. For example:
*
* public function testExample() {
* $env = PhabricatorEnv::beginScopedEnv();
* $env->overrideEnv('some.key', 'new-value-for-this-test');
*
* // Some test which depends on the value of 'some.key'.
*
* }
*
* Your changes will persist until the `$env` object leaves scope or is
* destroyed.
*
* You should //not// use this in normal code.
*
*
* @task read Reading Configuration
* @task uri URI Validation
* @task test Unit Test Support
* @task internal Internals
*/
final class PhabricatorEnv {
private static $sourceStack;
private static $repairSource;
private static $overrideSource;
private static $requestBaseURI;
private static $cache;
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public static function initializeWebEnvironment() {
self::initializeCommonEnvironment();
}
public static function initializeScriptEnvironment() {
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);
// There are several places where we log information (about errors, events,
// service calls, etc.) for analysis via DarkConsole or similar. These are
// useful for web requests, but grow unboundedly in long-running scripts and
// daemons. Discard data as it arrives in these cases.
PhutilServiceProfiler::getInstance()->enableDiscardMode();
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
DarkConsoleEventPluginAPI::enableDiscardMode();
}
private static function initializeCommonEnvironment() {
PhutilErrorHandler::initialize();
self::buildConfigurationSourceStack();
// Force a valid timezone. If both PHP and Phabricator configuration are
// invalid, use UTC.
$tz = PhabricatorEnv::getEnvConfig('phabricator.timezone');
if ($tz) {
@date_default_timezone_set($tz);
}
$ok = @date_default_timezone_set(date_default_timezone_get());
if (!$ok) {
date_default_timezone_set('UTC');
}
// Prepend '/support/bin' and append any paths to $PATH if we need to.
$env_path = getenv('PATH');
$phabricator_path = dirname(phutil_get_library_root('phabricator'));
$support_path = $phabricator_path.'/support/bin';
$env_path = $support_path.PATH_SEPARATOR.$env_path;
$append_dirs = PhabricatorEnv::getEnvConfig('environment.append-paths');
if (!empty($append_dirs)) {
$append_path = implode(PATH_SEPARATOR, $append_dirs);
$env_path = $env_path.PATH_SEPARATOR.$append_path;
}
putenv('PATH='.$env_path);
// Write this back into $_ENV, too, so ExecFuture picks it up when creating
// subprocess environments.
$_ENV['PATH'] = $env_path;
PhabricatorEventEngine::initialize();
$translation = PhabricatorEnv::newObjectFromConfig('translation.provider');
PhutilTranslator::getInstance()
->setLanguage($translation->getLanguage())
->addTranslations($translation->getTranslations());
}
private static function buildConfigurationSourceStack() {
self::dropConfigCache();
$stack = new PhabricatorConfigStackSource();
self::$sourceStack = $stack;
$default_source = id(new PhabricatorConfigDefaultSource())
->setName(pht('Global Default'));
$stack->pushSource($default_source);
$env = self::getSelectedEnvironmentName();
if ($env) {
$stack->pushSource(
id(new PhabricatorConfigFileSource($env))
->setName(pht("File '%s'", $env)));
}
$stack->pushSource(
id(new PhabricatorConfigLocalSource())
->setName(pht('Local Config')));
// If the install overrides the database adapter, we might need to load
// the database adapter class before we can push on the database config.
// This config is locked and can't be edited from the web UI anyway.
foreach (PhabricatorEnv::getEnvConfig('load-libraries') as $library) {
phutil_load_library($library);
}
// If custom libraries specify config options, they won't get default
// values as the Default source has already been loaded, so we get it to
// pull in all options from non-phabricator libraries now they are loaded.
$default_source->loadExternalOptions();
try {
$stack->pushSource(
id(new PhabricatorConfigDatabaseSource('default'))
->setName(pht('Database')));
} catch (AphrontQueryException $exception) {
// If the database is not available, just skip this configuration
// source. This happens during `bin/storage upgrade`, `bin/conf` before
// schema setup, etc.
}
}
public static function repairConfig($key, $value) {
if (!self::$repairSource) {
self::$repairSource = id(new PhabricatorConfigDictionarySource(array()))
->setName(pht('Repaired Config'));
self::$sourceStack->pushSource(self::$repairSource);
}
self::$repairSource->setKeys(array($key => $value));
self::dropConfigCache();
}
public static function overrideConfig($key, $value) {
if (!self::$overrideSource) {
self::$overrideSource = id(new PhabricatorConfigDictionarySource(array()))
->setName(pht('Overridden Config'));
self::$sourceStack->pushSource(self::$overrideSource);
}
self::$overrideSource->setKeys(array($key => $value));
self::dropConfigCache();
}
public static function getUnrepairedEnvConfig($key, $default = null) {
foreach (self::$sourceStack->getStack() as $source) {
if ($source === self::$repairSource) {
continue;
}
$result = $source->getKeys(array($key));
if ($result) {
return $result[$key];
}
}
return $default;
}
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);
}
if (!$env) {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/conf/local/ENVIRONMENT';
if (Filesystem::pathExists($path)) {
$env = trim(Filesystem::readFile($path));
}
}
return $env;
}
public static function calculateEnvironmentHash() {
$keys = array_keys(self::getAllConfigKeys());
+ asort($keys);
+
$values = array();
foreach ($keys as $key) {
$values[$key] = self::getEnvConfigIfExists($key);
}
return PhabricatorHash::digest(json_encode($values));
}
/* -( Reading Configuration )---------------------------------------------- */
/**
* Get the current configuration setting for a given key.
*
* If the key is not found, then throw an Exception.
*
* @task read
*/
public static function getEnvConfig($key) {
if (isset(self::$cache[$key])) {
return self::$cache[$key];
}
if (array_key_exists($key, self::$cache)) {
return self::$cache[$key];
}
$result = self::$sourceStack->getKeys(array($key));
if (array_key_exists($key, $result)) {
self::$cache[$key] = $result[$key];
return $result[$key];
} else {
throw new Exception("No config value specified for key '{$key}'.");
}
}
/**
* Get the current configuration setting for a given key. If the key
* does not exist, return a default value instead of throwing. This is
* primarily useful for migrations involving keys which are slated for
* removal.
*
* @task read
*/
public static function getEnvConfigIfExists($key, $default = null) {
try {
return self::getEnvConfig($key);
} catch (Exception $ex) {
return $default;
}
}
/**
* Get the fully-qualified URI for a path.
*
* @task read
*/
public static function getURI($path) {
return rtrim(self::getAnyBaseURI(), '/').$path;
}
/**
* Get the fully-qualified production URI for a path.
*
* @task read
*/
public static function getProductionURI($path) {
// If we're passed a URI which already has a domain, simply return it
// unmodified. In particular, files may have URIs which point to a CDN
// domain.
$uri = new PhutilURI($path);
if ($uri->getDomain()) {
return $path;
}
$production_domain = self::getEnvConfig('phabricator.production-uri');
if (!$production_domain) {
$production_domain = self::getAnyBaseURI();
}
return rtrim($production_domain, '/').$path;
}
public static function getAllowedURIs($path) {
$uri = new PhutilURI($path);
if ($uri->getDomain()) {
return $path;
}
$allowed_uris = self::getEnvConfig('phabricator.allowed-uris');
$return = array();
foreach ($allowed_uris as $allowed_uri) {
$return[] = rtrim($allowed_uri, '/').$path;
}
return $return;
}
/**
* Get the fully-qualified production URI for a static resource path.
*
* @task read
*/
public static function getCDNURI($path) {
$alt = self::getEnvConfig('security.alternate-file-domain');
if (!$alt) {
$alt = self::getAnyBaseURI();
}
$uri = new PhutilURI($alt);
$uri->setPath($path);
return (string)$uri;
}
/**
* Get the fully-qualified production URI for a documentation resource.
*
* @task read
*/
public static function getDoclink($resource, $type = 'article') {
$uri = new PhutilURI('https://secure.phabricator.com/diviner/find/');
$uri->setQueryParam('name', $resource);
$uri->setQueryParam('type', $type);
$uri->setQueryParam('jump', true);
return (string)$uri;
}
/**
* Build a concrete object from a configuration key.
*
* @task read
*/
public static function newObjectFromConfig($key, $args = array()) {
$class = self::getEnvConfig($key);
return newv($class, $args);
}
public static function getAnyBaseURI() {
$base_uri = self::getEnvConfig('phabricator.base-uri');
if (!$base_uri) {
$base_uri = self::getRequestBaseURI();
}
if (!$base_uri) {
throw new Exception(
"Define 'phabricator.base-uri' in your configuration to continue.");
}
return $base_uri;
}
public static function getRequestBaseURI() {
return self::$requestBaseURI;
}
public static function setRequestBaseURI($uri) {
self::$requestBaseURI = $uri;
}
/* -( Unit Test Support )-------------------------------------------------- */
/**
* @task test
*/
public static function beginScopedEnv() {
return new PhabricatorScopedEnv(self::pushTestEnvironment());
}
/**
* @task test
*/
private static function pushTestEnvironment() {
self::dropConfigCache();
$source = new PhabricatorConfigDictionarySource(array());
self::$sourceStack->pushSource($source);
return spl_object_hash($source);
}
/**
* @task test
*/
public static function popTestEnvironment($key) {
self::dropConfigCache();
$source = self::$sourceStack->popSource();
$stack_key = spl_object_hash($source);
if ($stack_key !== $key) {
self::$sourceStack->pushSource($source);
throw new Exception(
'Scoped environments were destroyed in a diffent order than they '.
'were initialized.');
}
}
/* -( URI Validation )----------------------------------------------------- */
/**
* Detect if a URI satisfies either @{method:isValidLocalWebResource} or
* @{method:isValidRemoteWebResource}, i.e. is a page on this server or the
* URI of some other resource which has a valid protocol. This rejects
* garbage URIs and URIs with protocols which do not appear in the
* ##uri.allowed-protocols## configuration, notably 'javascript:' URIs.
*
* NOTE: This method is generally intended to reject URIs which it may be
* unsafe to put in an "href" link attribute.
*
* @param string URI to test.
* @return bool True if the URI identifies a web resource.
* @task uri
*/
public static function isValidWebResource($uri) {
return self::isValidLocalWebResource($uri) ||
self::isValidRemoteWebResource($uri);
}
/**
* Detect if a URI identifies some page on this server.
*
* NOTE: This method is generally intended to reject URIs which it may be
* unsafe to issue a "Location:" redirect to.
*
* @param string URI to test.
* @return bool True if the URI identifies a local page.
* @task uri
*/
public static function isValidLocalWebResource($uri) {
$uri = (string)$uri;
if (!strlen($uri)) {
return false;
}
if (preg_match('/\s/', $uri)) {
// PHP hasn't been vulnerable to header injection attacks for a bunch of
// years, but we can safely reject these anyway since they're never valid.
return false;
}
// Chrome (at a minimum) interprets backslashes in Location headers and the
// URL bar as forward slashes. This is probably intended to reduce user
// error caused by confusion over which key is "forward slash" vs "back
// slash".
//
// However, it means a URI like "/\evil.com" is interpreted like
// "//evil.com", which is a protocol relative remote URI.
//
// Since we currently never generate URIs with backslashes in them, reject
// these unconditionally rather than trying to figure out how browsers will
// interpret them.
if (preg_match('/\\\\/', $uri)) {
return false;
}
// Valid URIs must begin with '/', followed by the end of the string or some
// other non-'/' character. This rejects protocol-relative URIs like
// "//evil.com/evil_stuff/".
return (bool)preg_match('@^/([^/]|$)@', $uri);
}
/**
* Detect if a URI identifies some valid remote resource.
*
* @param string URI to test.
* @return bool True if a URI idenfies a remote resource with an allowed
* protocol.
* @task uri
*/
public static function isValidRemoteWebResource($uri) {
$uri = (string)$uri;
$proto = id(new PhutilURI($uri))->getProtocol();
if (!$proto) {
return false;
}
$allowed = self::getEnvConfig('uri.allowed-protocols');
if (empty($allowed[$proto])) {
return false;
}
return true;
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
public static function envConfigExists($key) {
return array_key_exists($key, self::$sourceStack->getKeys(array($key)));
}
/**
* @task internal
*/
public static function getAllConfigKeys() {
return self::$sourceStack->getAllKeys();
}
public static function getConfigSourceStack() {
return self::$sourceStack;
}
/**
* @task internal
*/
public static function overrideTestEnvConfig($stack_key, $key, $value) {
$tmp = array();
// If we don't have the right key, we'll throw when popping the last
// source off the stack.
do {
$source = self::$sourceStack->popSource();
array_unshift($tmp, $source);
if (spl_object_hash($source) == $stack_key) {
$source->setKeys(array($key => $value));
break;
}
} while (true);
foreach ($tmp as $source) {
self::$sourceStack->pushSource($source);
}
self::dropConfigCache();
}
private static function dropConfigCache() {
self::$cache = array();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Aug 14, 11:34 AM (6 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
202270
Default Alt Text
(30 KB)

Event Timeline