mirror of
https://we.phorge.it/source/phorge.git
synced 2024-09-22 10:18:48 +02:00
0a62f13464
Summary: Ran `arc lint --apply-patches --everything` over rP, mainly to change double quotes to single quotes where appropriate. These changes also validate that the `ArcanistXHPASTLinter::LINT_DOUBLE_QUOTE` rule is working as expected. Test Plan: Eyeballed it. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley, Korvin, hach-que Differential Revision: https://secure.phabricator.com/D9431
547 lines
15 KiB
PHP
547 lines
15 KiB
PHP
<?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;
|
|
}
|
|
|
|
|
|
/* -( 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;
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
}
|