2011-01-25 20:57:47 +01:00
|
|
|
<?php
|
|
|
|
|
2012-01-12 01:07:36 +01:00
|
|
|
/**
|
2012-04-17 16:52:01 +02:00
|
|
|
* 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
|
2012-01-12 01:07:36 +01:00
|
|
|
*/
|
2011-01-31 20:55:26 +01:00
|
|
|
final class PhabricatorEnv {
|
2012-04-17 16:52:01 +02:00
|
|
|
|
2012-12-25 15:44:29 +01:00
|
|
|
private static $sourceStack;
|
2013-01-18 01:25:38 +01:00
|
|
|
private static $repairSource;
|
2013-02-26 07:20:23 +01:00
|
|
|
private static $overrideSource;
|
2013-01-22 22:57:02 +01:00
|
|
|
private static $requestBaseURI;
|
2013-04-01 21:05:49 +02:00
|
|
|
private static $cache;
|
2011-01-31 20:55:26 +01:00
|
|
|
|
2012-12-25 15:15:28 +01:00
|
|
|
/**
|
|
|
|
* @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);
|
Enable log discard modes for all scripts
Summary:
Fixes T2273. We currently discard logs, service calls, etc., for daemons, but not for other scripts. However, other scripts may be long-running or issue a large body of service calls (e.g., `bin/search index --all`). We never retrieve this information from scripts (it is used to build darkconsole; in scripts, we echo it immediately under --trace), so discard it immediately to prevent these scripts from requiring a large amount of memory.
(When the daemons load `__init_script__.php` they end up calling this code, so this doesn't change anything for them. They hit another ServiceProfiler discard along the daemon pathways in libphutil, but the call is idempotent.)
Test Plan: Ran `bin/search index --all` and saw increasing memory usage before this patch, but steady memory usage after this patch.
Reviewers: btrahan, vrana, codeblock
Reviewed By: codeblock
CC: aran
Maniphest Tasks: T2273
Differential Revision: https://secure.phabricator.com/D4364
2013-01-09 00:54:08 +01:00
|
|
|
|
|
|
|
// 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();
|
2012-12-25 15:15:28 +01:00
|
|
|
}
|
|
|
|
|
2012-12-25 15:44:29 +01:00
|
|
|
|
2012-12-25 15:15:28 +01:00
|
|
|
private static function initializeCommonEnvironment() {
|
|
|
|
PhutilErrorHandler::initialize();
|
|
|
|
|
2013-01-18 20:29:08 +01:00
|
|
|
self::buildConfigurationSourceStack();
|
|
|
|
|
2013-01-19 17:38:37 +01:00
|
|
|
// Force a valid timezone. If both PHP and Phabricator configuration are
|
|
|
|
// invalid, use UTC.
|
2012-12-25 15:15:28 +01:00
|
|
|
$tz = PhabricatorEnv::getEnvConfig('phabricator.timezone');
|
|
|
|
if ($tz) {
|
2013-01-19 17:38:37 +01:00
|
|
|
@date_default_timezone_set($tz);
|
|
|
|
}
|
|
|
|
$ok = @date_default_timezone_set(date_default_timezone_get());
|
|
|
|
if (!$ok) {
|
|
|
|
date_default_timezone_set('UTC');
|
2012-12-25 15:15:28 +01:00
|
|
|
}
|
|
|
|
|
2013-04-03 21:49:22 +02:00
|
|
|
// 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;
|
2012-12-25 15:15:28 +01:00
|
|
|
}
|
2013-04-03 21:49:22 +02:00
|
|
|
putenv('PATH='.$env_path);
|
2012-12-25 15:15:28 +01:00
|
|
|
|
2013-11-28 06:03:00 +01:00
|
|
|
// Write this back into $_ENV, too, so ExecFuture picks it up when creating
|
|
|
|
// subprocess environments.
|
|
|
|
$_ENV['PATH'] = $env_path;
|
|
|
|
|
2012-12-25 15:15:28 +01:00
|
|
|
PhabricatorEventEngine::initialize();
|
|
|
|
|
|
|
|
$translation = PhabricatorEnv::newObjectFromConfig('translation.provider');
|
|
|
|
PhutilTranslator::getInstance()
|
|
|
|
->setLanguage($translation->getLanguage())
|
|
|
|
->addTranslations($translation->getTranslations());
|
|
|
|
}
|
|
|
|
|
2013-01-01 23:10:33 +01:00
|
|
|
private static function buildConfigurationSourceStack() {
|
2013-04-01 21:05:49 +02:00
|
|
|
self::dropConfigCache();
|
|
|
|
|
2013-01-01 23:10:33 +01:00
|
|
|
$stack = new PhabricatorConfigStackSource();
|
|
|
|
self::$sourceStack = $stack;
|
|
|
|
|
2013-01-23 21:03:19 +01:00
|
|
|
$default_source = id(new PhabricatorConfigDefaultSource())
|
2013-01-19 17:34:54 +01:00
|
|
|
->setName(pht('Global Default'));
|
2013-01-23 21:03:19 +01:00
|
|
|
$stack->pushSource($default_source);
|
2013-01-01 23:10:33 +01:00
|
|
|
|
|
|
|
$env = self::getSelectedEnvironmentName();
|
2013-01-23 21:03:19 +01:00
|
|
|
if ($env) {
|
|
|
|
$stack->pushSource(
|
|
|
|
id(new PhabricatorConfigFileSource($env))
|
|
|
|
->setName(pht("File '%s'", $env)));
|
|
|
|
}
|
2013-01-01 23:10:33 +01:00
|
|
|
|
|
|
|
$stack->pushSource(
|
|
|
|
id(new PhabricatorConfigLocalSource())
|
|
|
|
->setName(pht("Local Config")));
|
Add database configuration source to the source stack
Summary:
Read configuration from the new database source.
This adds an extra MySQL connect + query to every page. They're very cheap so I think we can suffer them for now, but I'd like to put cache in front of this at some point. The difficulties are:
- If we use APC, multi-frontend installs (Facebook) can't dirty it (major problem), and the CLI can't dirty it (fine for now, maybe a major problem later).
- If we use Memcache, we need to add config stuff.
- We could use APC in all non-Facebook installs if we can make it dirtyable from the CLI, but I don't see a reasonable way to do that.
- We don't have any other caches which are faster than the database.
So I'll probably implement Memcache support at some point, although this is a lame excuse for it.
Test Plan: Added some config values via web UI, saw them active on the install.
Reviewers: btrahan, codeblock, vrana
Reviewed By: codeblock
CC: aran
Maniphest Tasks: T2221
Differential Revision: https://secure.phabricator.com/D4296
2013-01-18 00:10:21 +01:00
|
|
|
|
2013-01-18 03:59:58 +01:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2013-01-19 17:34:54 +01:00
|
|
|
// 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.
|
2013-01-23 21:03:19 +01:00
|
|
|
$default_source->loadExternalOptions();
|
2013-01-19 17:34:54 +01:00
|
|
|
|
2013-01-18 16:50:52 +01:00
|
|
|
try {
|
|
|
|
$stack->pushSource(
|
|
|
|
id(new PhabricatorConfigDatabaseSource('default'))
|
|
|
|
->setName(pht("Database")));
|
2013-01-18 20:29:08 +01:00
|
|
|
} catch (AphrontQueryException $exception) {
|
2013-01-18 16:50:52 +01:00
|
|
|
// If the database is not available, just skip this configuration
|
|
|
|
// source. This happens during `bin/storage upgrade`, `bin/conf` before
|
|
|
|
// schema setup, etc.
|
|
|
|
}
|
2013-01-01 23:10:33 +01:00
|
|
|
}
|
|
|
|
|
2013-01-18 01:25:38 +01:00
|
|
|
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));
|
2013-04-01 21:05:49 +02:00
|
|
|
self::dropConfigCache();
|
2013-01-18 01:25:38 +01:00
|
|
|
}
|
|
|
|
|
2013-02-26 07:20:23 +01:00
|
|
|
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));
|
2013-04-01 21:05:49 +02:00
|
|
|
self::dropConfigCache();
|
2013-02-26 07:20:23 +01:00
|
|
|
}
|
|
|
|
|
2013-01-18 01:25:38 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-25 15:15:28 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
Add a local configuration source and a non-environmental ENV config source
Summary:
See discussion in T2221. Before we can move configuration to the database, we have a bootstrapping problem: we need database credentials to live //somewhere// if we can't guess them (and we can only really guess localhost / root / no password).
Some options for this are:
- Have them live in ENV variables.
- These are often somewhat unfamiliar to users.
- Scripts would become a huge pain -- you'd have to dump a bunch of stuff into ENV.
- Some environments have limited ability to set ENV vars.
- SSH is also a pain.
- Have them live in a normal config file.
- This probably isn't really too awful, but:
- Since we deploy/upgrade with git, we can't currently let them edit a file which already exists, or their working copy will become dirty.
- So they have to copy or create a file, then edit it.
- The biggest issue I have with this is that it will be difficult to give specific, easily-followed directions from Setup. The instructions need to be like "Copy template.conf.php to real.conf.php, then edit these keys: x, y, z". This isn't as easy to follow as "run script Y".
- Have them live in an abnormal config file with script access (this diff).
- I think this is a little better than a normal config file, because we can tell users 'run phabricator/bin/config set mysql.user phabricator' and such, which is easier to follow than editing a config file.
I think this is only a marginal improvement over a normal config file and am open to arguments against this approach, but I think it will be a little easier for users to deal with than a normal config file. In most cases they should only need to store three values in this file -- db user/host/pass -- since once we have those we can bootstrap everything else. Normal config files also aren't going away for more advanced users, we're just offering a simple alternative for most users.
This also adds an ENVIRONMENT file as an alternative to PHABRICATOR_ENV. This is just a simple way to specify the environment if you don't have convenient access to env vars.
Test Plan: Ran `config set x y`, verified writes. Wrote to ENVIRONMENT, ran `PHABRICATOR_ENV= ./bin/repository`.
Reviewers: btrahan, vrana, codeblock
Reviewed By: codeblock
CC: aran
Maniphest Tasks: T2221
Differential Revision: https://secure.phabricator.com/D4294
2012-12-30 15:16:15 +01:00
|
|
|
if (!$env) {
|
|
|
|
$root = dirname(phutil_get_library_root('phabricator'));
|
|
|
|
$path = $root.'/conf/local/ENVIRONMENT';
|
|
|
|
if (Filesystem::pathExists($path)) {
|
|
|
|
$env = trim(Filesystem::readFile($path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-25 15:15:28 +01:00
|
|
|
return $env;
|
|
|
|
}
|
|
|
|
|
2011-01-31 20:55:26 +01:00
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
/* -( Reading Configuration )---------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current configuration setting for a given key.
|
|
|
|
*
|
2013-01-19 21:11:11 +01:00
|
|
|
* If the key is not found, then throw an Exception.
|
|
|
|
*
|
2012-04-17 16:52:01 +02:00
|
|
|
* @task read
|
|
|
|
*/
|
2013-01-19 21:11:11 +01:00
|
|
|
public static function getEnvConfig($key) {
|
2013-04-01 21:05:49 +02:00
|
|
|
if (isset(self::$cache[$key])) {
|
|
|
|
return self::$cache[$key];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists($key, self::$cache)) {
|
|
|
|
return self::$cache[$key];
|
|
|
|
}
|
|
|
|
|
2012-12-25 15:44:29 +01:00
|
|
|
$result = self::$sourceStack->getKeys(array($key));
|
2013-01-19 21:11:11 +01:00
|
|
|
if (array_key_exists($key, $result)) {
|
2013-04-01 21:05:49 +02:00
|
|
|
self::$cache[$key] = $result[$key];
|
2013-01-19 21:11:11 +01:00
|
|
|
return $result[$key];
|
|
|
|
} else {
|
|
|
|
throw new Exception("No config value specified for key '{$key}'.");
|
|
|
|
}
|
2012-03-22 23:44:34 +01:00
|
|
|
}
|
|
|
|
|
2011-04-30 07:20:52 +02:00
|
|
|
|
2013-06-20 20:18:11 +02:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
/**
|
|
|
|
* Get the fully-qualified URI for a path.
|
|
|
|
*
|
|
|
|
* @task read
|
|
|
|
*/
|
2011-01-31 20:55:26 +01:00
|
|
|
public static function getURI($path) {
|
2013-01-22 22:57:02 +01:00
|
|
|
return rtrim(self::getAnyBaseURI(), '/').$path;
|
2011-01-31 20:55:26 +01:00
|
|
|
}
|
2011-02-27 05:57:21 +01:00
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the fully-qualified production URI for a path.
|
|
|
|
*
|
|
|
|
* @task read
|
|
|
|
*/
|
2011-04-04 23:22:16 +02:00
|
|
|
public static function getProductionURI($path) {
|
2012-02-16 02:06:36 +01:00
|
|
|
// 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;
|
2011-04-04 23:22:16 +02:00
|
|
|
}
|
2012-02-16 02:06:36 +01:00
|
|
|
|
|
|
|
$production_domain = self::getEnvConfig('phabricator.production-uri');
|
|
|
|
if (!$production_domain) {
|
2013-01-22 22:57:02 +01:00
|
|
|
$production_domain = self::getAnyBaseURI();
|
2012-02-16 02:06:36 +01:00
|
|
|
}
|
|
|
|
return rtrim($production_domain, '/').$path;
|
2011-04-04 23:22:16 +02:00
|
|
|
}
|
|
|
|
|
2013-05-26 19:57:29 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the fully-qualified production URI for a static resource path.
|
|
|
|
*
|
|
|
|
* @task read
|
|
|
|
*/
|
Move ALL files to serve from the alternate file domain, not just files without
"Content-Disposition: attachment"
Summary:
We currently serve some files off the primary domain (with "Content-Disposition:
attachment" + a CSRF check) and some files off the alternate domain (without
either).
This is not sufficient, because some UAs (like the iPad) ignore
"Content-Disposition: attachment". So there's an attack that goes like this:
- Alice uploads xss.html
- Alice says to Bob "hey download this file on your iPad"
- Bob clicks "Download" on Phabricator on his iPad, gets XSS'd.
NOTE: This removes the CSRF check for downloading files. The check is nice to
have but only raises the barrier to entry slightly. Between iPad / sniffing /
flash bytecode attacks, single-domain installs are simply insecure. We could
restore the check at some point in conjunction with a derived authentication
cookie (i.e., a mini-session-token which is only useful for downloading files),
but that's a lot of complexity to drop all at once.
(Because files are now authenticated only by knowing the PHID and secret key,
this also fixes the "no profile pictures in public feed while logged out"
issue.)
Test Plan: Viewed, info'd, and downloaded files
Reviewers: btrahan, arice, alok
Reviewed By: arice
CC: aran, epriestley
Maniphest Tasks: T843
Differential Revision: https://secure.phabricator.com/D1608
2012-02-14 23:52:27 +01:00
|
|
|
public static function getCDNURI($path) {
|
|
|
|
$alt = self::getEnvConfig('security.alternate-file-domain');
|
|
|
|
if (!$alt) {
|
2013-01-22 22:57:02 +01:00
|
|
|
$alt = self::getAnyBaseURI();
|
Move ALL files to serve from the alternate file domain, not just files without
"Content-Disposition: attachment"
Summary:
We currently serve some files off the primary domain (with "Content-Disposition:
attachment" + a CSRF check) and some files off the alternate domain (without
either).
This is not sufficient, because some UAs (like the iPad) ignore
"Content-Disposition: attachment". So there's an attack that goes like this:
- Alice uploads xss.html
- Alice says to Bob "hey download this file on your iPad"
- Bob clicks "Download" on Phabricator on his iPad, gets XSS'd.
NOTE: This removes the CSRF check for downloading files. The check is nice to
have but only raises the barrier to entry slightly. Between iPad / sniffing /
flash bytecode attacks, single-domain installs are simply insecure. We could
restore the check at some point in conjunction with a derived authentication
cookie (i.e., a mini-session-token which is only useful for downloading files),
but that's a lot of complexity to drop all at once.
(Because files are now authenticated only by knowing the PHID and secret key,
this also fixes the "no profile pictures in public feed while logged out"
issue.)
Test Plan: Viewed, info'd, and downloaded files
Reviewers: btrahan, arice, alok
Reviewed By: arice
CC: aran, epriestley
Maniphest Tasks: T843
Differential Revision: https://secure.phabricator.com/D1608
2012-02-14 23:52:27 +01:00
|
|
|
}
|
|
|
|
$uri = new PhutilURI($alt);
|
|
|
|
$uri->setPath($path);
|
|
|
|
return (string)$uri;
|
|
|
|
}
|
|
|
|
|
2011-01-31 20:55:26 +01:00
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
/**
|
|
|
|
* Get the fully-qualified production URI for a documentation resource.
|
|
|
|
*
|
|
|
|
* @task read
|
|
|
|
*/
|
2011-05-19 22:40:12 +02:00
|
|
|
public static function getDoclink($resource) {
|
2012-04-09 23:23:10 +02:00
|
|
|
return 'http://www.phabricator.com/docs/phabricator/'.$resource;
|
2011-05-19 22:40:12 +02:00
|
|
|
}
|
|
|
|
|
2012-01-12 01:07:36 +01:00
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
/**
|
|
|
|
* Build a concrete object from a configuration key.
|
|
|
|
*
|
|
|
|
* @task read
|
|
|
|
*/
|
|
|
|
public static function newObjectFromConfig($key, $args = array()) {
|
|
|
|
$class = self::getEnvConfig($key);
|
2013-01-18 01:25:38 +01:00
|
|
|
return newv($class, $args);
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
2013-01-22 22:57:02 +01:00
|
|
|
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;
|
|
|
|
}
|
2012-04-17 16:52:01 +02:00
|
|
|
|
|
|
|
/* -( Unit Test Support )-------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task test
|
|
|
|
*/
|
|
|
|
public static function beginScopedEnv() {
|
2012-12-25 15:44:29 +01:00
|
|
|
return new PhabricatorScopedEnv(self::pushTestEnvironment());
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task test
|
|
|
|
*/
|
2012-12-25 15:44:29 +01:00
|
|
|
private static function pushTestEnvironment() {
|
2013-04-01 21:05:49 +02:00
|
|
|
self::dropConfigCache();
|
2012-12-25 15:44:29 +01:00
|
|
|
$source = new PhabricatorConfigDictionarySource(array());
|
|
|
|
self::$sourceStack->pushSource($source);
|
|
|
|
return spl_object_hash($source);
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task test
|
|
|
|
*/
|
2012-12-25 15:44:29 +01:00
|
|
|
public static function popTestEnvironment($key) {
|
2013-04-01 21:05:49 +02:00
|
|
|
self::dropConfigCache();
|
2012-12-25 15:44:29 +01:00
|
|
|
$source = self::$sourceStack->popSource();
|
|
|
|
$stack_key = spl_object_hash($source);
|
2012-04-17 16:52:01 +02:00
|
|
|
if ($stack_key !== $key) {
|
2013-01-17 20:50:49 +01:00
|
|
|
self::$sourceStack->pushSource($source);
|
2012-04-17 16:52:01 +02:00
|
|
|
throw new Exception(
|
|
|
|
"Scoped environments were destroyed in a diffent order than they ".
|
|
|
|
"were initialized.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-01-12 01:07:36 +01:00
|
|
|
/* -( 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;
|
|
|
|
}
|
|
|
|
|
2012-04-17 16:52:01 +02:00
|
|
|
|
|
|
|
/* -( Internals )---------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task internal
|
|
|
|
*/
|
|
|
|
public static function envConfigExists($key) {
|
2012-12-25 15:44:29 +01:00
|
|
|
return array_key_exists($key, self::$sourceStack->getKeys(array($key)));
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task internal
|
|
|
|
*/
|
|
|
|
public static function getAllConfigKeys() {
|
2012-12-25 15:44:29 +01:00
|
|
|
return self::$sourceStack->getAllKeys();
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
2013-01-01 23:10:33 +01:00
|
|
|
public static function getConfigSourceStack() {
|
|
|
|
return self::$sourceStack;
|
|
|
|
}
|
2012-04-17 16:52:01 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @task internal
|
|
|
|
*/
|
2012-12-25 15:44:29 +01:00
|
|
|
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);
|
|
|
|
}
|
2013-10-04 04:05:47 +02:00
|
|
|
|
|
|
|
self::dropConfigCache();
|
2012-04-17 16:52:01 +02:00
|
|
|
}
|
|
|
|
|
2013-04-01 21:05:49 +02:00
|
|
|
private static function dropConfigCache() {
|
|
|
|
self::$cache = array();
|
|
|
|
}
|
|
|
|
|
2011-01-25 20:57:47 +01:00
|
|
|
}
|