* URL or via a POSTed form stripping illegal input characters and
* normalizing Unicode sequences.
*
- * Usually this is used via a global singleton, $wgRequest. You should
- * not create a second WebRequest object; make a FauxRequest object if
- * you want to pass arbitrary data to some function in place of the web
- * input.
- *
* @ingroup HTTP
*/
class WebRequest {
protected $data, $headers = array();
+ /**
+ * Flag to make WebRequest::getHeader return an array of values.
+ * @since 1.26
+ */
+ const GETHEADER_LIST = 1;
+
/**
* Lazy-init response object
* @var WebResponse
*/
private $ip;
+ /**
+ * The timestamp of the start of the request, with microsecond precision.
+ * @var float
+ */
+ protected $requestTime;
+
/**
* Cached URL protocol
* @var string
protected $protocol;
public function __construct() {
- if ( function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc() ) {
- throw new MWException( "MediaWiki does not function when magic quotes are enabled." );
- }
+ $this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
+ ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
// POST overrides GET data
// We don't use $_REQUEST here to avoid interference from cookies...
if ( !preg_match( '!^https?://!', $url ) ) {
$url = 'http://unused' . $url;
}
- wfSuppressWarnings();
+ MediaWiki\suppressWarnings();
$a = parse_url( $url );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( $a ) {
$path = isset( $a['path'] ) ? $a['path'] : '';
);
}
- wfRunHooks( 'WebRequestPathInfoRouter', array( $router ) );
+ Hooks::run( 'WebRequestPathInfoRouter', array( $router ) );
$matches = $router->parse( $path );
}
* @return string
*/
public static function detectServer() {
+ global $wgAssumeProxiesUseDefaultProtocolPorts;
+
$proto = self::detectProtocol();
$stdPort = $proto === 'https' ? 443 : 80;
if ( !isset( $_SERVER[$varName] ) ) {
continue;
}
+
$parts = IP::splitHostAndPort( $_SERVER[$varName] );
if ( !$parts ) {
// Invalid, do not use
continue;
}
+
$host = $parts[0];
- if ( $parts[1] === false ) {
+ if ( $wgAssumeProxiesUseDefaultProtocolPorts && isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
+ // Bug 70021: Assume that upstream proxy is running on the default
+ // port based on the protocol. We have no reliable way to determine
+ // the actual port in use upstream.
+ $port = $stdPort;
+ } elseif ( $parts[1] === false ) {
if ( isset( $_SERVER['SERVER_PORT'] ) ) {
$port = $_SERVER['SERVER_PORT'];
} // else leave it as $stdPort
* @return array
*/
public static function detectProtocol() {
- if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ||
+ if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
- $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) {
+ $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
return 'https';
} else {
return 'http';
}
}
+ /**
+ * Get the number of seconds to have elapsed since request start,
+ * in fractional seconds, with microsecond resolution.
+ *
+ * @return float
+ * @since 1.25
+ */
+ public function getElapsedTime() {
+ return microtime( true ) - $this->requestTime;
+ }
+
/**
* Get the current URL protocol (http or https)
* @return string
}
} else {
global $wgContLang;
- $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal::cleanUp( $data );
+ $data = isset( $wgContLang ) ?
+ $wgContLang->normalize( $data ) :
+ UtfNormal\Validator::cleanUp( $data );
}
return $data;
}
// This shouldn't happen!
throw new MWException( "Web server doesn't provide either " .
"REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
- "of your web server configuration to http://bugzilla.wikimedia.org/" );
+ "of your web server configuration to https://phabricator.wikimedia.org/" );
}
// User-agents should not send a fragment with the URI, but
// if they do, and the web server passes it on to us, we
/**
* Take an arbitrary query and rewrite the present URL to include it
+ * @deprecated Use appendQueryValue/appendQueryArray instead
* @param string $query Query string fragment; do not include initial '?'
- *
* @return string
*/
public function appendQuery( $query ) {
+ wfDeprecated( __METHOD__, '1.25' );
return $this->appendQueryArray( wfCgiToArray( $query ) );
}
/**
* @param string $key
* @param string $value
- * @param bool $onlyquery
+ * @param bool $onlyquery [deprecated]
* @return string
*/
- public function appendQueryValue( $key, $value, $onlyquery = false ) {
+ public function appendQueryValue( $key, $value, $onlyquery = true ) {
return $this->appendQueryArray( array( $key => $value ), $onlyquery );
}
* Appends or replaces value of query variables.
*
* @param array $array Array of values to replace/add to query
- * @param bool $onlyquery Whether to only return the query string and not the complete URL
+ * @param bool $onlyquery Whether to only return the query string
+ * and not the complete URL [deprecated]
* @return string
*/
- public function appendQueryArray( $array, $onlyquery = false ) {
+ public function appendQueryArray( $array, $onlyquery = true ) {
global $wgTitle;
$newquery = $this->getQueryValues();
unset( $newquery['title'] );
$newquery = array_merge( $newquery, $array );
$query = wfArrayToCgi( $newquery );
- return $onlyquery ? $query : $wgTitle->getLocalURL( $query );
+ if ( !$onlyquery ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return $wgTitle->getLocalURL( $query );
+ }
+
+ return $query;
}
/**
*
* @param int $deflimit Limit to use if no input and the user hasn't set the option.
* @param string $optionname To specify an option other than rclimit to pull from.
- * @return array First element is limit, second is offset
+ * @return int[] First element is limit, second is offset
*/
public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
global $wgUser;
/**
* Initialise the header list
*/
- private function initHeaders() {
+ protected function initHeaders() {
if ( count( $this->headers ) ) {
return;
}
}
/**
- * Get a request header, or false if it isn't set
- * @param string $name Case-insensitive header name
+ * Get a request header, or false if it isn't set.
*
- * @return string|bool False on failure
- */
- public function getHeader( $name ) {
+ * @param string $name Case-insensitive header name
+ * @param int $flags Bitwise combination of:
+ * WebRequest::GETHEADER_LIST Treat the header as a comma-separated list
+ * of values, as described in RFC 2616 ยง 4.2.
+ * (since 1.26).
+ * @return string|array|bool False if header is unset; otherwise the
+ * header value(s) as either a string (the default) or an array, if
+ * WebRequest::GETHEADER_LIST flag was set.
+ */
+ public function getHeader( $name, $flags = 0 ) {
$this->initHeaders();
$name = strtoupper( $name );
- if ( isset( $this->headers[$name] ) ) {
- return $this->headers[$name];
- } else {
+ if ( !isset( $this->headers[$name] ) ) {
return false;
}
+ $value = $this->headers[$name];
+ if ( $flags & self::GETHEADER_LIST ) {
+ $value = array_map( 'trim', explode( ',', $value ) );
+ }
+ return $value;
}
/**
* @return bool
*/
public function checkUrlExtension( $extWhitelist = array() ) {
- global $wgScriptExtension;
- $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
+ $extWhitelist[] = 'php';
if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
if ( !$this->wasPosted() ) {
$newUrl = IEUrlExtension::fixUrlForIE6(
}
# Allow extensions to improve our guess
- wfRunHooks( 'GetIP', array( &$ip ) );
+ Hooks::run( 'GetIP', array( &$ip ) );
if ( !$ip ) {
throw new MWException( "Unable to determine IP." );
$this->ip = $ip;
}
}
-
-/**
- * Object to access the $_FILES array
- */
-class WebRequestUpload {
- protected $request;
- protected $doesExist;
- protected $fileInfo;
-
- /**
- * Constructor. Should only be called by WebRequest
- *
- * @param WebRequest $request The associated request
- * @param string $key Key in $_FILES array (name of form field)
- */
- public function __construct( $request, $key ) {
- $this->request = $request;
- $this->doesExist = isset( $_FILES[$key] );
- if ( $this->doesExist ) {
- $this->fileInfo = $_FILES[$key];
- }
- }
-
- /**
- * Return whether a file with this name was uploaded.
- *
- * @return bool
- */
- public function exists() {
- return $this->doesExist;
- }
-
- /**
- * Return the original filename of the uploaded file
- *
- * @return string|null Filename or null if non-existent
- */
- public function getName() {
- if ( !$this->exists() ) {
- return null;
- }
-
- global $wgContLang;
- $name = $this->fileInfo['name'];
-
- # Safari sends filenames in HTML-encoded Unicode form D...
- # Horrid and evil! Let's try to make some kind of sense of it.
- $name = Sanitizer::decodeCharReferences( $name );
- $name = $wgContLang->normalize( $name );
- wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" );
- return $name;
- }
-
- /**
- * Return the file size of the uploaded file
- *
- * @return int File size or zero if non-existent
- */
- public function getSize() {
- if ( !$this->exists() ) {
- return 0;
- }
-
- return $this->fileInfo['size'];
- }
-
- /**
- * Return the path to the temporary file
- *
- * @return string|null Path or null if non-existent
- */
- public function getTempName() {
- if ( !$this->exists() ) {
- return null;
- }
-
- return $this->fileInfo['tmp_name'];
- }
-
- /**
- * Return the upload error. See link for explanation
- * http://www.php.net/manual/en/features.file-upload.errors.php
- *
- * @return int One of the UPLOAD_ constants, 0 if non-existent
- */
- public function getError() {
- if ( !$this->exists() ) {
- return 0; # UPLOAD_ERR_OK
- }
-
- return $this->fileInfo['error'];
- }
-
- /**
- * Returns whether this upload failed because of overflow of a maximum set
- * in php.ini
- *
- * @return bool
- */
- public function isIniSizeOverflow() {
- if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) {
- # PHP indicated that upload_max_filesize is exceeded
- return true;
- }
-
- $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
- if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) {
- # post_max_size is exceeded
- return true;
- }
-
- return false;
- }
-}
-
-/**
- * WebRequest clone which takes values from a provided array.
- *
- * @ingroup HTTP
- */
-class FauxRequest extends WebRequest {
- private $wasPosted = false;
- private $session = array();
-
- /**
- * @param array $data Array of *non*-urlencoded key => value pairs, the
- * fake GET/POST values
- * @param bool $wasPosted Whether to treat the data as POST
- * @param array|null $session Session array or null
- * @param string $protocol 'http' or 'https'
- * @throws MWException
- */
- public function __construct( $data = array(), $wasPosted = false,
- $session = null, $protocol = 'http'
- ) {
- if ( is_array( $data ) ) {
- $this->data = $data;
- } else {
- throw new MWException( "FauxRequest() got bogus data" );
- }
- $this->wasPosted = $wasPosted;
- if ( $session ) {
- $this->session = $session;
- }
- $this->protocol = $protocol;
- }
-
- /**
- * @param string $method
- * @throws MWException
- */
- private function notImplemented( $method ) {
- throw new MWException( "{$method}() not implemented" );
- }
-
- /**
- * @param string $name
- * @param string $default
- * @return string
- */
- public function getText( $name, $default = '' ) {
- # Override; don't recode since we're using internal data
- return (string)$this->getVal( $name, $default );
- }
-
- /**
- * @return array
- */
- public function getValues() {
- return $this->data;
- }
-
- /**
- * @return array
- */
- public function getQueryValues() {
- if ( $this->wasPosted ) {
- return array();
- } else {
- return $this->data;
- }
- }
-
- public function getMethod() {
- return $this->wasPosted ? 'POST' : 'GET';
- }
-
- /**
- * @return bool
- */
- public function wasPosted() {
- return $this->wasPosted;
- }
-
- public function getCookie( $key, $prefix = null, $default = null ) {
- return $default;
- }
-
- public function checkSessionCookie() {
- return false;
- }
-
- public function getRequestURL() {
- $this->notImplemented( __METHOD__ );
- }
-
- public function getProtocol() {
- return $this->protocol;
- }
-
- /**
- * @param string $name The name of the header to get (case insensitive).
- * @return bool|string
- */
- public function getHeader( $name ) {
- $name = strtoupper( $name );
- return isset( $this->headers[$name] ) ? $this->headers[$name] : false;
- }
-
- /**
- * @param string $name
- * @param string $val
- */
- public function setHeader( $name, $val ) {
- $name = strtoupper( $name );
- $this->headers[$name] = $val;
- }
-
- /**
- * @param string $key
- * @return array|null
- */
- public function getSessionData( $key ) {
- if ( isset( $this->session[$key] ) ) {
- return $this->session[$key];
- }
- return null;
- }
-
- /**
- * @param string $key
- * @param array $data
- */
- public function setSessionData( $key, $data ) {
- $this->session[$key] = $data;
- }
-
- /**
- * @return array|mixed|null
- */
- public function getSessionArray() {
- return $this->session;
- }
-
- /**
- * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return string
- */
- public function getRawQueryString() {
- return '';
- }
-
- /**
- * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return string
- */
- public function getRawPostString() {
- return '';
- }
-
- /**
- * FauxRequests shouldn't depend on raw request data (but that could be implemented here)
- * @return string
- */
- public function getRawInput() {
- return '';
- }
-
- /**
- * @param array $extWhitelist
- * @return bool
- */
- public function checkUrlExtension( $extWhitelist = array() ) {
- return true;
- }
-
- /**
- * @return string
- */
- protected function getRawIP() {
- return '127.0.0.1';
- }
-}
-
-/**
- * Similar to FauxRequest, but only fakes URL parameters and method
- * (POST or GET) and use the base request for the remaining stuff
- * (cookies, session and headers).
- *
- * @ingroup HTTP
- * @since 1.19
- */
-class DerivativeRequest extends FauxRequest {
- private $base;
-
- /**
- * @param WebRequest $base
- * @param array $data Array of *non*-urlencoded key => value pairs, the
- * fake GET/POST values
- * @param bool $wasPosted Whether to treat the data as POST
- */
- public function __construct( WebRequest $base, $data, $wasPosted = false ) {
- $this->base = $base;
- parent::__construct( $data, $wasPosted );
- }
-
- public function getCookie( $key, $prefix = null, $default = null ) {
- return $this->base->getCookie( $key, $prefix, $default );
- }
-
- public function checkSessionCookie() {
- return $this->base->checkSessionCookie();
- }
-
- public function getHeader( $name ) {
- return $this->base->getHeader( $name );
- }
-
- public function getAllHeaders() {
- return $this->base->getAllHeaders();
- }
-
- public function getSessionData( $key ) {
- return $this->base->getSessionData( $key );
- }
-
- public function setSessionData( $key, $data ) {
- $this->base->setSessionData( $key, $data );
- }
-
- public function getAcceptLang() {
- return $this->base->getAcceptLang();
- }
-
- public function getIP() {
- return $this->base->getIP();
- }
-
- public function getProtocol() {
- return $this->base->getProtocol();
- }
-}