Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893186
PhabricatorEnv.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
15 KB
Referenced Files
None
Subscribers
None
PhabricatorEnv.php
View Options
<?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
(
)
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sun, Jan 19, 18:02 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127216
Default Alt Text
PhabricatorEnv.php (15 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment