30use Wikimedia\AtEase\AtEase;
65 protected $headers = [];
111 protected $markedAsSafe =
false;
117 $this->requestTime = $_SERVER[
'REQUEST_TIME_FLOAT'];
121 $this->data = $_POST + $_GET;
123 $this->queryAndPathParams = $this->queryParams = $_GET;
145 if ( isset( $_SERVER[
'REQUEST_URI'] ) ) {
147 $url = $_SERVER[
'REQUEST_URI'];
148 if ( !preg_match(
'!^https?://!', $url ) ) {
149 $url =
'http://unused' . $url;
151 AtEase::suppressWarnings();
152 $a = parse_url( $url );
153 AtEase::restoreWarnings();
157 $path = $a[
'path'] ??
'';
169 $router->
add(
"$wgScript/$1" );
171 if ( isset( $_SERVER[
'SCRIPT_NAME'] )
172 && strpos( $_SERVER[
'SCRIPT_NAME'],
'.php' ) !==
false
177 $router->add( $_SERVER[
'SCRIPT_NAME'] .
"/$1" );
187 if ( $articlePaths ) {
188 $router->add( $articlePaths, [
'action' =>
'$key' ] );
194 [
'variant' =>
'$2' ],
195 [
'$2' => MediaWikiServices::getInstance()->getContentLanguage()->
200 Hooks::run(
'WebRequestPathInfoRouter', [ $router ] );
207 if ( !empty( $_SERVER[
'ORIG_PATH_INFO'] ) ) {
211 $matches[
'title'] = substr( $_SERVER[
'ORIG_PATH_INFO'], 1 );
212 } elseif ( !empty( $_SERVER[
'PATH_INFO'] ) ) {
214 $matches[
'title'] = substr( $_SERVER[
'PATH_INFO'], 1 );
231 $proto = self::detectProtocol();
232 $stdPort = $proto ===
'https' ? 443 : 80;
234 $varNames = [
'HTTP_HOST',
'SERVER_NAME',
'HOSTNAME',
'SERVER_ADDR' ];
237 foreach ( $varNames as $varName ) {
238 if ( !isset( $_SERVER[$varName] ) ) {
242 $parts = IP::splitHostAndPort( $_SERVER[$varName] );
254 } elseif ( $parts[1] ===
false ) {
255 if ( isset( $_SERVER[
'SERVER_PORT'] ) ) {
256 $port = $_SERVER[
'SERVER_PORT'];
264 return $proto .
'://' . IP::combineHostAndPort( $host, $port, $stdPort );
275 if ( ( !empty( $_SERVER[
'HTTPS'] ) && $_SERVER[
'HTTPS'] !==
'off' ) ||
276 ( isset( $_SERVER[
'HTTP_X_FORWARDED_PROTO'] ) &&
277 $_SERVER[
'HTTP_X_FORWARDED_PROTO'] ===
'https' ) ) {
292 return microtime(
true ) - $this->requestTime;
306 if ( self::$reqId ) {
314 $id = RequestContext::getMain()->getRequest()->getHeader(
'X-Request-Id' );
339 if ( $this->protocol ===
null ) {
340 $this->protocol = self::detectProtocol();
342 return $this->protocol;
354 if ( defined(
'MW_API' ) ) {
358 $matches = self::getPathInfo(
'title' );
359 foreach (
$matches as $key => $val ) {
360 $this->data[$key] = $this->queryAndPathParams[$key] = $val;
375 foreach ( (array)$bases as $keyValue =>
$base ) {
378 $baseLen = strlen(
$base );
379 if ( substr(
$path, 0, $baseLen ) ==
$base ) {
380 $raw = substr(
$path, $baseLen );
382 $matches = [
'title' => rawurldecode( $raw ) ];
401 if ( is_array( $data ) ) {
402 foreach ( $data as $key => $val ) {
406 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
407 $data = $contLang ? $contLang->normalize( $data ) :
408 UtfNormal\Validator::cleanUp( $data );
422 # PHP is so nice to not touch input data, except sometimes:
424 # Work around PHP *feature* to avoid *bugs* elsewhere.
425 $name = strtr( $name,
'.',
'_' );
427 if ( !isset( $arr[$name] ) ) {
432 # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
433 $isAsciiStr = ( is_string( $data ) && preg_match(
'/[^\x20-\x7E]/', $data ) === 0 );
434 if ( !$isAsciiStr ) {
435 if ( isset( $_GET[$name] ) && is_string( $data ) ) {
436 # Check for alternate/legacy character encoding.
437 $data = MediaWikiServices::getInstance()
438 ->getContentLanguage()
439 ->checkTitleEncoding( $data );
460 $name = strtr( $name,
'.',
'_' );
461 if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
462 $val = $this->data[$name];
466 if ( is_null( $val ) ) {
483 public function getVal( $name, $default =
null ) {
484 $val = $this->
getGPCVal( $this->data, $name, $default );
485 if ( is_array( $val ) ) {
488 if ( is_null( $val ) ) {
503 $ret = $this->data[$key] ??
null;
504 $this->data[$key] = $value;
515 if ( !isset( $this->data[$key] ) ) {
518 $ret = $this->data[$key];
519 unset( $this->data[$key] );
533 public function getArray( $name, $default =
null ) {
534 $val = $this->
getGPCVal( $this->data, $name, $default );
535 if ( is_null( $val ) ) {
553 $val = $this->
getArray( $name, $default );
554 if ( is_array( $val ) ) {
555 $val = array_map(
'intval', $val );
569 public function getInt( $name, $default = 0 ) {
570 return intval( $this->
getRawVal( $name, $default ) );
583 return is_numeric( $val )
598 public function getFloat( $name, $default = 0.0 ) {
599 return floatval( $this->
getRawVal( $name, $default ) );
611 public function getBool( $name, $default =
false ) {
612 return (
bool)$this->
getRawVal( $name, $default );
625 return $this->
getBool( $name, $default )
626 && strcasecmp( $this->
getRawVal( $name ),
'false' ) !== 0;
638 # Checkboxes and buttons are only present when clicked
639 # Presence connotes truth, absence false
640 return $this->
getRawVal( $name,
null ) !==
null;
653 public function getText( $name, $default =
'' ) {
654 $val = $this->
getVal( $name, $default );
655 return str_replace(
"\r\n",
"\n", $val );
666 $names = func_get_args();
667 if ( count( $names ) == 0 ) {
668 $names = array_keys( $this->data );
672 foreach ( $names as $name ) {
673 $value = $this->
getGPCVal( $this->data, $name,
null );
674 if ( !is_null( $value ) ) {
675 $retVal[$name] = $value;
688 return array_diff( array_keys( $this->
getValues() ), $exclude );
699 return $this->queryAndPathParams;
712 return $this->queryParams;
735 return $_SERVER[
'QUERY_STRING'];
759 static $input =
null;
760 if ( $input ===
null ) {
761 $input = file_get_contents(
'php://input' );
772 return $_SERVER[
'REQUEST_METHOD'] ??
'GET';
799 if ( $this->sessionId !==
null ) {
800 $session = SessionManager::singleton()->getSessionById( (
string)$this->sessionId,
true, $this );
806 $session = SessionManager::singleton()->getSessionForRequest( $this );
807 $this->sessionId = $session->getSessionId();
818 $this->sessionId = $sessionId;
828 return $this->sessionId;
839 public function getCookie( $key, $prefix =
null, $default =
null ) {
840 if ( $prefix ===
null ) {
844 $name = $prefix . $key;
846 $name = strtr( $name,
'.',
'_' );
847 if ( isset( $_COOKIE[$name] ) ) {
848 return $_COOKIE[$name];
864 $name = $prefix . $key;
866 $name = strtr( $name,
'.',
'_' );
867 if ( isset( $_COOKIE[$name] ) ) {
868 return $_COOKIE[$name];
871 $legacyName = $prefix .
"ss0-" . $key;
872 $legacyName = strtr( $legacyName,
'.',
'_' );
873 if ( isset( $_COOKIE[$legacyName] ) ) {
874 return $_COOKIE[$legacyName];
890 if ( isset( $_SERVER[
'REQUEST_URI'] ) && strlen( $_SERVER[
'REQUEST_URI'] ) ) {
891 $base = $_SERVER[
'REQUEST_URI'];
892 } elseif ( isset( $_SERVER[
'HTTP_X_ORIGINAL_URL'] )
893 && strlen( $_SERVER[
'HTTP_X_ORIGINAL_URL'] )
896 $base = $_SERVER[
'HTTP_X_ORIGINAL_URL'];
897 } elseif ( isset( $_SERVER[
'SCRIPT_NAME'] ) ) {
898 $base = $_SERVER[
'SCRIPT_NAME'];
899 if ( isset( $_SERVER[
'QUERY_STRING'] ) && $_SERVER[
'QUERY_STRING'] !=
'' ) {
900 $base .=
'?' . $_SERVER[
'QUERY_STRING'];
904 throw new MWException(
"Web server doesn't provide either " .
905 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
906 "of your web server configuration to https://phabricator.wikimedia.org/" );
912 $hash = strpos(
$base,
'#' );
913 if ( $hash !==
false ) {
917 if (
$base[0] ==
'/' ) {
919 return preg_replace(
'!^/+!',
'/',
$base );
922 return preg_replace(
'!^[^:]+://[^/]+/+!',
'/',
$base );
934 return self::getGlobalRequestURL();
975 unset( $newquery[
'title'] );
976 $newquery = array_merge( $newquery, $array );
993 $limit = $this->
getInt(
'limit', 0 );
997 if ( ( $limit == 0 ) && ( $optionname !=
'' ) ) {
998 $limit = $wgUser->getIntOption( $optionname );
1000 if ( $limit <= 0 ) {
1003 if ( $limit > 5000 ) {
1004 $limit = 5000; # We have *some* limits...
1007 $offset = $this->
getInt(
'offset', 0 );
1008 if ( $offset < 0 ) {
1012 return [ $limit, $offset ];
1023 return $file->getTempName();
1034 return $file->getError();
1050 return $file->getName();
1071 if ( !is_object( $this->
response ) ) {
1072 $class = ( $this instanceof
FauxRequest ) ? FauxResponse::class : WebResponse::class;
1082 if ( count( $this->headers ) ) {
1086 $apacheHeaders = function_exists(
'apache_request_headers' ) ? apache_request_headers() :
false;
1087 if ( $apacheHeaders ) {
1088 foreach ( $apacheHeaders as $tempName => $tempValue ) {
1089 $this->headers[strtoupper( $tempName )] = $tempValue;
1092 foreach ( $_SERVER as $name => $value ) {
1093 if ( substr( $name, 0, 5 ) ===
'HTTP_' ) {
1094 $name = str_replace(
'_',
'-', substr( $name, 5 ) );
1095 $this->headers[$name] = $value;
1096 } elseif ( $name ===
'CONTENT_LENGTH' ) {
1097 $this->headers[
'CONTENT-LENGTH'] = $value;
1110 return $this->headers;
1127 $name = strtoupper( $name );
1128 if ( !isset( $this->headers[$name] ) ) {
1131 $value = $this->headers[$name];
1132 if ( $flags & self::GETHEADER_LIST ) {
1133 $value = array_map(
'trim', explode(
',', $value ) );
1171 $extWhitelist[] =
'php';
1176 if ( $newUrl !==
false ) {
1182 'Invalid file extension found in the path info or query string.' );
1195 header(
'Location: ' . $url );
1196 header(
'Content-Type: text/html' );
1197 $encUrl = htmlspecialchars( $url );
1202<title>Security redirect</title>
1205<h1>Security redirect</h1>
1207We can
't serve non-HTML content from the URL you have requested, because
1208Internet Explorer would interpret it as an incorrect and potentially dangerous
1210<p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the
1211URL you have requested, except that "&*" is appended. This prevents Internet
1212Explorer from seeing a bogus file extension.
1230 public function getAcceptLang() {
1231 // Modified version of code found at
1232 // http://www.thefutureoftheweb.com/blog/use-accept-language-header
1233 $acceptLang = $this->getHeader( 'Accept-
Language' );
1234 if ( !$acceptLang ) {
1238 // Return the language codes in lower case
1239 $acceptLang = strtolower( $acceptLang );
1241 // Break up string into pieces (languages and q factors)
1244 '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/
',
1249 if ( !count( $lang_parse[1] ) ) {
1253 $langcodes = $lang_parse[1];
1254 $qvalues = $lang_parse[4];
1255 $indices = range( 0, count( $lang_parse[1] ) - 1 );
1257 // Set default q factor to 1
1258 foreach ( $indices as $index ) {
1259 if ( $qvalues[$index] === '' ) {
1260 $qvalues[$index] = 1;
1261 } elseif ( $qvalues[$index] == 0 ) {
1262 unset( $langcodes[$index], $qvalues[$index], $indices[$index] );
1266 // Sort list. First by $qvalues, then by order. Reorder $langcodes the same way
1267 array_multisort( $qvalues, SORT_DESC, SORT_NUMERIC, $indices, $langcodes );
1269 // Create a list like "en" => 0.8
1270 $langs = array_combine( $langcodes, $qvalues );
1283 protected function getRawIP() {
1284 if ( !isset( $_SERVER['REMOTE_ADDR
'] ) ) {
1288 if ( is_array( $_SERVER['REMOTE_ADDR
'] ) || strpos( $_SERVER['REMOTE_ADDR
'], ',
' ) !== false ) {
1289 throw new MWException( __METHOD__
1290 . " : Could not determine the remote IP address due to multiple values." );
1292 $ipchain = $_SERVER['REMOTE_ADDR
'];
1295 return IP::canonicalize( $ipchain );
1307 public function getIP() {
1308 global $wgUsePrivateIPs;
1310 # Return cached result
1311 if ( $this->ip !== null ) {
1315 # collect the originating ips
1316 $ip = $this->getRawIP();
1318 throw new MWException( 'Unable to determine
IP.
' );
1322 $forwardedFor = $this->getHeader( 'X-Forwarded-For
' );
1323 if ( $forwardedFor !== false ) {
1324 $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1325 $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
1326 $ipchain = array_map( 'trim
', explode( ',
', $forwardedFor ) );
1327 $ipchain = array_reverse( $ipchain );
1328 array_unshift( $ipchain, $ip );
1330 # Step through XFF list and find the last address in the list which is a
1331 # trusted server. Set $ip to the IP address given by that trusted server,
1332 # unless the address is not sensible (e.g. private). However, prefer private
1333 # IP addresses over proxy servers controlled by this site (more sensible).
1334 # Note that some XFF values might be "unknown" with Squid/Varnish.
1335 foreach ( $ipchain as $i => $curIP ) {
1336 $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
1337 if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown
'
1338 || !$proxyLookup->isTrustedProxy( $curIP )
1340 break; // IP is not valid/trusted or does not point to anything
1343 IP::isPublic( $ipchain[$i + 1] ) ||
1345 $proxyLookup->isConfiguredProxy( $curIP ) // T50919; treat IP as sane
1347 // Follow the next IP according to the proxy
1348 $nextIP = IP::canonicalize( $ipchain[$i + 1] );
1349 if ( !$nextIP && $isConfigured ) {
1350 // We have not yet made it past CDN/proxy servers of this site,
1351 // so either they are misconfigured or there is some IP spoofing.
1352 throw new MWException( "Invalid IP given in XFF '$forwardedFor
'." );
1355 // keep traversing the chain
1362 # Allow extensions to improve our guess
1363 Hooks::run( 'GetIP
', [ &$ip ] );
1366 throw new MWException( "Unable to determine IP." );
1369 wfDebug( "IP: $ip\n" );
1379 public function setIP( $ip ) {
1395 public function hasSafeMethod() {
1396 if ( !isset( $_SERVER['REQUEST_METHOD
'] ) ) {
1397 return false; // CLI mode
1400 return in_array( $_SERVER['REQUEST_METHOD
'], [ 'GET
', 'HEAD
', 'OPTIONS
', 'TRACE
' ] );
1421 public function isSafeRequest() {
1422 if ( $this->markedAsSafe && $this->wasPosted() ) {
1423 return true; // marked as a "safe" POST
1426 return $this->hasSafeMethod();
1439 public function markAsSafeRequest() {
1440 $this->markedAsSafe = true;
$wgUsePathInfo
Whether to support URLs like index.php/Page_title These often break when PHP is set up in CGI mode.
$wgScript
The URL path to index.php.
bool $wgUseSameSiteLegacyCookies
If true, when a cross-site cookie with SameSite=None is sent, a legacy cookie with an "ss0" prefix wi...
$wgAllowExternalReqID
Whether to respect/honour the request ID provided by the incoming request via the X-Request-Id header...
bool $wgAssumeProxiesUseDefaultProtocolPorts
When the wiki is running behind a proxy and this is set to true, assumes that the proxy exposes the w...
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgCookiePrefix
Cookies generated by MediaWiki have names starting with this prefix.
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfGetServerUrl( $proto)
Get the wiki's "server", i.e.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
WebRequest clone which takes values from a provided array.
Show an error that looks like an HTTP server error.
static areServerVarsBad( $vars, $extWhitelist=[])
Check a subset of $_SERVER (or the whole of $_SERVER if you like) to see if it indicates that the req...
static fixUrlForIE6( $url, $extWhitelist=[])
Returns a variant of $url which will pass isUrlExtensionBad() but has the same GET parameters,...
A collection of public static functions to play with IP address and IP ranges.
Internationalisation code.
static getActionPaths(array $actionPaths, $articlePath)
add( $path, $params=[], $options=[])
Add a new path pattern to the path router.
Object to access the $_FILES array.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
array $headers
Lazy-initialized request headers indexed by upper-case header name.
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
getValueNames( $exclude=[])
Returns the names of all input values excluding those in $exclude.
bool $markedAsSafe
Whether this HTTP request is "safe" (even if it is an HTTP post)
getUpload( $key)
Return a WebRequestUpload object corresponding to the key.
string $protocol
Cached URL protocol.
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
getPostValues()
Get the values passed via POST.
static detectProtocol()
Detect the protocol from $_SERVER.
getSession()
Return the session for this request.
getRawInput()
Return the raw request body, with no processing.
getRawQueryString()
Return the contents of the Query with no decoding.
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
WebResponse $response
Lazy-init response object.
getUploadError( $key)
Return the upload error or 0.
getAllHeaders()
Get an array containing all request headers.
getFuzzyBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
getGPCVal( $arr, $name, $default)
Fetch a value from the given array or return $default if it's not set.
static getRequestId()
Get the unique request ID.
getProtocol()
Get the current URL protocol (http or https)
getMethod()
Get the HTTP method used for this request.
initHeaders()
Initialise the header list.
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
string $ip
Cached client IP address.
static getGlobalRequestURL()
Return the path and query string portion of the main request URI.
setVal( $key, $value)
Set an arbitrary value into our get/post data.
static string $reqId
The unique request ID.
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and query string.
checkUrlExtension( $extWhitelist=[])
Check if Internet Explorer will detect an incorrect cache extension in PATH_INFO or QUERY_STRING.
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
float $requestTime
The timestamp of the start of the request, with microsecond precision.
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
appendQueryArray( $array)
Appends or replaces value of query variables.
static detectServer()
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
getSessionId()
Get the session id for this request, if any.
getRawPostString()
Return the contents of the POST with no decoding.
getQueryValues()
Get the values passed in the query string and the path router parameters.
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
$queryParams
The parameters from $_GET only.
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
setSessionData( $key, $data)
Set session data.
doSecurityRedirect( $url)
Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in IE 6.
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it's not set.
getIntArray( $name, $default=null)
Fetch an array of integers, or return $default if it's not set.
appendQueryValue( $key, $value)
normalizeUnicode( $data)
Recursively normalizes UTF-8 strings in the given array.
static overrideRequestId( $id)
Override the unique request ID.
unsetVal( $key)
Unset an arbitrary value from our get/post data.
static getPathInfo( $want='all')
Extract relevant query arguments from the http request uri's path to be merged with the normal php pr...
SessionId null $sessionId
Session ID to use for this request.
array $queryAndPathParams
The parameters from $_GET.
setSessionId(SessionId $sessionId)
Set the session for this request.
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
array $data
The parameters from $_GET, $_POST and the path router.
getLimitOffset( $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
static extractTitle( $path, $bases, $key=false)
URL rewriting function; tries to extract page title and, optionally, one other fixed parameter value ...
getText( $name, $default='')
Fetch a text string from the given array or return $default if it's not set.
getRequestURL()
Return the path and query string portion of the request URI.
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
getSessionData( $key)
Get data from the session.
getValues()
Extracts the given named values into an array.
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.