68 protected $headers = [];
80 private static $reqId;
117 protected $markedAsSafe =
false;
123 $this->requestTime = $_SERVER[
'REQUEST_TIME_FLOAT'];
127 $this->data = $_POST + $_GET;
129 $this->queryAndPathParams = $this->queryParams = $_GET;
156 if ( isset( $_SERVER[
'REQUEST_URI'] ) ) {
158 $url = $_SERVER[
'REQUEST_URI'];
159 if ( !preg_match(
'!^https?://!', $url ) ) {
160 $url =
'http://unused' . $url;
162 $a = parse_url( $url );
166 $path = $a[
'path'] ??
'';
178 $router->
add(
"$wgScript/$1" );
188 if ( $articlePaths ) {
189 $router->add( $articlePaths, [
'action' =>
'$key' ] );
194 $services = MediaWikiServices::getInstance();
197 [
'variant' =>
'$2' ],
198 [
'$2' => $services->getLanguageConverterFactory()
199 ->getLanguageConverter( $services->getContentLanguage() )
204 Hooks::runner()->onWebRequestPathInfoRouter( $router );
211 if ( !empty( $_SERVER[
'ORIG_PATH_INFO'] ) ) {
215 $matches[
'title'] = substr( $_SERVER[
'ORIG_PATH_INFO'], 1 );
216 } elseif ( !empty( $_SERVER[
'PATH_INFO'] ) ) {
218 $matches[
'title'] = substr( $_SERVER[
'PATH_INFO'], 1 );
238 $basePath = rtrim( $basePath,
'/' ) .
'/';
239 $requestUrl = self::getGlobalRequestURL();
240 $qpos = strpos( $requestUrl,
'?' );
241 if ( $qpos !==
false ) {
242 $requestPath = substr( $requestUrl, 0, $qpos );
244 $requestPath = $requestUrl;
246 if ( !str_starts_with( $requestPath, $basePath ) ) {
249 return rawurldecode( substr( $requestPath, strlen( $basePath ) ) );
263 public static function detectServer( $assumeProxiesUseDefaultProtocolPorts =
null ) {
264 if ( $assumeProxiesUseDefaultProtocolPorts ===
null ) {
265 $assumeProxiesUseDefaultProtocolPorts = $GLOBALS[
'wgAssumeProxiesUseDefaultProtocolPorts'];
268 $proto = self::detectProtocol();
269 $stdPort = $proto ===
'https' ? 443 : 80;
271 $varNames = [
'HTTP_HOST',
'SERVER_NAME',
'HOSTNAME',
'SERVER_ADDR' ];
274 foreach ( $varNames as $varName ) {
275 if ( !isset( $_SERVER[$varName] ) ) {
279 $parts = IPUtils::splitHostAndPort( $_SERVER[$varName] );
286 if ( $assumeProxiesUseDefaultProtocolPorts && isset( $_SERVER[
'HTTP_X_FORWARDED_PROTO'] ) ) {
291 } elseif ( $parts[1] ===
false ) {
292 if ( isset( $_SERVER[
'SERVER_PORT'] ) ) {
293 $port = $_SERVER[
'SERVER_PORT'];
301 return $proto .
'://' . IPUtils::combineHostAndPort( $host, $port, $stdPort );
312 if ( ( !empty( $_SERVER[
'HTTPS'] ) && $_SERVER[
'HTTPS'] !==
'off' ) ||
313 ( isset( $_SERVER[
'HTTP_X_FORWARDED_PROTO'] ) &&
314 $_SERVER[
'HTTP_X_FORWARDED_PROTO'] ===
'https' ) ) {
329 return microtime(
true ) - $this->requestTime;
343 if ( !self::$reqId ) {
346 $id = $_SERVER[
'HTTP_X_REQUEST_ID'] ?? $_SERVER[
'UNIQUE_ID'] ??
wfRandomString( 24 );
372 if ( $this->protocol ===
null ) {
373 $this->protocol = self::detectProtocol();
375 return $this->protocol;
386 $matches = self::getPathInfo(
'title' );
387 foreach (
$matches as $key => $val ) {
388 $this->data[$key] = $this->queryAndPathParams[$key] = $val;
403 foreach ( (array)$bases as $keyValue =>
$base ) {
406 $baseLen = strlen(
$base );
407 if ( substr(
$path, 0, $baseLen ) ==
$base ) {
408 $raw = substr(
$path, $baseLen );
410 $matches = [
'title' => rawurldecode( $raw ) ];
429 if ( is_array( $data ) ) {
430 foreach ( $data as $key => $val ) {
434 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
435 $data = $contLang->normalize( $data );
448 private function getGPCVal( $arr, $name, $default ) {
449 # PHP is so nice to not touch input data, except sometimes:
451 # Work around PHP *feature* to avoid *bugs* elsewhere.
452 $name = strtr( $name,
'.',
'_' );
454 if ( !isset( $arr[$name] ) ) {
459 # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
460 $isAsciiStr = ( is_string( $data ) && preg_match(
'/[^\x20-\x7E]/', $data ) === 0 );
461 if ( !$isAsciiStr ) {
462 if ( isset( $_GET[$name] ) && is_string( $data ) ) {
463 # Check for alternate/legacy character encoding.
464 $data = MediaWikiServices::getInstance()
465 ->getContentLanguage()
466 ->checkTitleEncoding( $data );
487 $name = strtr( $name,
'.',
'_' );
488 if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
489 $val = $this->data[$name];
494 return $val ===
null ? null : (string)$val;
513 public function getVal( $name, $default =
null ) {
514 $val = $this->getGPCVal( $this->data, $name, $default );
515 if ( is_array( $val ) ) {
519 return $val ===
null ? null : (string)$val;
538 public function getText( $name, $default =
'' ) {
539 $val = $this->
getVal( $name, $default );
540 return str_replace(
"\r\n",
"\n", $val );
551 $ret = $this->data[$key] ??
null;
552 $this->data[$key] = $value;
563 if ( !isset( $this->data[$key] ) ) {
566 $ret = $this->data[$key];
567 unset( $this->data[$key] );
581 public function getArray( $name, $default =
null ) {
582 $val = $this->getGPCVal( $this->data, $name, $default );
583 if ( $val ===
null ) {
601 $val = $this->
getArray( $name, $default );
602 if ( is_array( $val ) ) {
603 $val = array_map(
'intval', $val );
617 public function getInt( $name, $default = 0 ) {
619 return intval( $this->
getRawVal( $name, $default ) );
632 return is_numeric( $val )
647 public function getFloat( $name, $default = 0.0 ) {
649 return floatval( $this->
getRawVal( $name, $default ) );
661 public function getBool( $name, $default =
false ) {
663 return (
bool)$this->
getRawVal( $name, $default );
676 return $this->
getBool( $name, $default )
677 && strcasecmp( $this->
getRawVal( $name ),
'false' ) !== 0;
689 # Checkboxes and buttons are only present when clicked
690 # Presence connotes truth, absence false
691 return $this->
getRawVal( $name,
null ) !==
null;
702 if ( $names === [] ) {
703 $names = array_keys( $this->data );
707 foreach ( $names as $name ) {
708 $value = $this->getGPCVal( $this->data, $name,
null );
709 if ( $value !==
null ) {
710 $retVal[$name] = $value;
723 return array_diff( array_keys( $this->
getValues() ), $exclude );
734 return $this->queryAndPathParams;
747 return $this->queryParams;
770 return $_SERVER[
'QUERY_STRING'];
794 static $input =
null;
795 if ( $input ===
null ) {
796 $input = file_get_contents(
'php://input' );
807 return $_SERVER[
'REQUEST_METHOD'] ??
'GET';
834 if ( $this->sessionId !==
null ) {
835 $session = SessionManager::singleton()->getSessionById( (
string)$this->sessionId,
true, $this );
841 $session = SessionManager::singleton()->getSessionForRequest( $this );
842 $this->sessionId = $session->getSessionId();
853 $this->sessionId = $sessionId;
863 return $this->sessionId;
874 public function getCookie( $key, $prefix =
null, $default =
null ) {
875 if ( $prefix ===
null ) {
879 $name = $prefix . $key;
881 $name = strtr( $name,
'.',
'_' );
882 if ( isset( $_COOKIE[$name] ) ) {
883 return $_COOKIE[$name];
899 $name = $prefix . $key;
901 $name = strtr( $name,
'.',
'_' );
902 if ( isset( $_COOKIE[$name] ) ) {
903 return $_COOKIE[$name];
906 $legacyName = $prefix .
"ss0-" . $key;
907 $legacyName = strtr( $legacyName,
'.',
'_' );
908 if ( isset( $_COOKIE[$legacyName] ) ) {
909 return $_COOKIE[$legacyName];
925 if ( isset( $_SERVER[
'REQUEST_URI'] ) && strlen( $_SERVER[
'REQUEST_URI'] ) ) {
926 $base = $_SERVER[
'REQUEST_URI'];
927 } elseif ( isset( $_SERVER[
'HTTP_X_ORIGINAL_URL'] )
928 && strlen( $_SERVER[
'HTTP_X_ORIGINAL_URL'] )
931 $base = $_SERVER[
'HTTP_X_ORIGINAL_URL'];
932 } elseif ( isset( $_SERVER[
'SCRIPT_NAME'] ) ) {
933 $base = $_SERVER[
'SCRIPT_NAME'];
934 if ( isset( $_SERVER[
'QUERY_STRING'] ) && $_SERVER[
'QUERY_STRING'] !=
'' ) {
935 $base .=
'?' . $_SERVER[
'QUERY_STRING'];
939 throw new MWException(
"Web server doesn't provide either " .
940 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
941 "of your web server configuration to https://phabricator.wikimedia.org/" );
947 $hash = strpos(
$base,
'#' );
948 if ( $hash !==
false ) {
952 if (
$base[0] ==
'/' ) {
954 return preg_replace(
'!^/+!',
'/',
$base );
957 return preg_replace(
'!^[^:]+://[^/]+/+!',
'/',
$base );
969 return self::getGlobalRequestURL();
1010 unset( $newquery[
'title'] );
1011 $newquery = array_merge( $newquery, $array );
1027 $limit = $this->
getInt(
'limit', 0 );
1031 if ( ( $limit == 0 ) && ( $optionname !=
'' ) ) {
1032 $limit = MediaWikiServices::getInstance()
1033 ->getUserOptionsLookup()
1034 ->getIntOption( $user, $optionname );
1036 if ( $limit <= 0 ) {
1039 if ( $limit > 5000 ) {
1040 $limit = 5000; # We have *some* limits...
1043 $offset = $this->
getInt(
'offset', 0 );
1044 if ( $offset < 0 ) {
1048 return [ $limit, $offset ];
1058 return $this->
getUpload( $key )->getTempName();
1068 return $this->
getUpload( $key )->getError();
1083 return $this->
getUpload( $key )->getName();
1104 if ( !is_object( $this->
response ) ) {
1105 $class = ( $this instanceof
FauxRequest ) ? FauxResponse::class : WebResponse::class;
1108 return $this->response;
1115 if ( count( $this->headers ) ) {
1119 $this->headers = array_change_key_case( getallheaders(), CASE_UPPER );
1129 return $this->headers;
1146 $name = strtoupper( $name );
1147 if ( !isset( $this->headers[$name] ) ) {
1150 $value = $this->headers[$name];
1151 if ( $flags & self::GETHEADER_LIST ) {
1152 $value = array_map(
'trim', explode(
',', $value ) );
1193 $acceptLang = $this->
getHeader(
'Accept-Language' );
1194 if ( !$acceptLang ) {
1199 $acceptLang = strtolower( $acceptLang );
1202 if ( !preg_match_all(
1204 # a language code or a star is required
1205 ([a-z]{1,8}(?:-[a-z]{1,8})*|\*)
1206 # from here everything is optional
1209 # this accepts only numbers in the range ;q=0.000 to ;q=1.000
1211 (1(?:\.0{0,3})?|0(?:\.\d{0,3})?)?
1224 $languageCode = $match[1];
1226 $qValue = (float)( $match[2] ?? 1.0 );
1228 $langs[$languageCode] = $qValue;
1233 arsort( $langs, SORT_NUMERIC );
1244 $remoteAddr = $_SERVER[
'REMOTE_ADDR'] ??
null;
1245 if ( !$remoteAddr ) {
1248 if ( is_array( $remoteAddr ) || str_contains( $remoteAddr,
',' ) ) {
1249 throw new MWException(
'Remote IP must not contain multiple values' );
1252 return IPUtils::canonicalize( $remoteAddr );
1265 # Return cached result
1266 if ( $this->ip !==
null ) {
1270 # collect the originating IPs
1273 throw new MWException(
'Unable to determine IP.' );
1277 $forwardedFor = $this->
getHeader(
'X-Forwarded-For' );
1278 if ( $forwardedFor !==
false ) {
1279 $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1280 $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
1281 $ipchain = array_map(
'trim', explode(
',', $forwardedFor ) );
1282 $ipchain = array_reverse( $ipchain );
1283 array_unshift( $ipchain, $ip );
1285 # Step through XFF list and find the last address in the list which is a
1286 # trusted server. Set $ip to the IP address given by that trusted server,
1287 # unless the address is not sensible (e.g. private). However, prefer private
1288 # IP addresses over proxy servers controlled by this site (more sensible).
1289 # Note that some XFF values might be "unknown" with Squid/Varnish.
1290 foreach ( $ipchain as $i => $curIP ) {
1291 $curIP = IPUtils::sanitizeIP(
1292 IPUtils::canonicalize(
1293 self::canonicalizeIPv6LoopbackAddress( $curIP )
1296 if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] ===
'unknown'
1297 || !$proxyLookup->isTrustedProxy( $curIP )
1302 IPUtils::isPublic( $ipchain[$i + 1] ) ||
1305 $proxyLookup->isConfiguredProxy( $curIP )
1307 $nextIP = $ipchain[$i + 1];
1310 $nextIP = IPUtils::canonicalize(
1311 self::canonicalizeIPv6LoopbackAddress( $nextIP )
1313 if ( !$nextIP && $isConfigured ) {
1316 throw new MWException(
"Invalid IP given in XFF '$forwardedFor'." );
1329 if ( Hooks::isRegistered(
'GetIP' ) ) {
1331 Hooks::runner()->onGetIP( $ip );
1335 throw new MWException(
'Unable to determine IP.' );
1353 if ( preg_match(
'/^0*' . IPUtils::RE_IPV6_GAP .
'1$/', $ip, $m ) ) {
1381 if ( !isset( $_SERVER[
'REQUEST_METHOD'] ) ) {
1385 return in_array( $_SERVER[
'REQUEST_METHOD'], [
'GET',
'HEAD',
'OPTIONS',
'TRACE' ] );
1407 if ( $this->markedAsSafe && $this->
wasPosted() ) {
1425 $this->markedAsSafe =
true;
1441 $config = MediaWikiServices::getInstance()->getMainConfig();
1442 if ( $config->get( MainConfigNames::CdnMatchParameterOrder ) ) {
1444 return in_array( $reqUrl, $cdnUrls,
true );
1448 $reqUrlParts = explode(
'?', $reqUrl, 2 );
1449 $reqUrlBase = $reqUrlParts[0];
1450 $reqUrlParams = count( $reqUrlParts ) === 2 ? explode(
'&', $reqUrlParts[1] ) : [];
1455 sort( $reqUrlParams );
1456 foreach ( $cdnUrls as $cdnUrl ) {
1457 if ( strlen( $reqUrl ) !== strlen( $cdnUrl ) ) {
1460 $cdnUrlParts = explode(
'?', $cdnUrl, 2 );
1461 $cdnUrlBase = $cdnUrlParts[0];
1462 if ( $reqUrlBase !== $cdnUrlBase ) {
1465 $cdnUrlParams = count( $cdnUrlParts ) === 2 ? explode(
'&', $cdnUrlParts[1] ) : [];
1466 sort( $cdnUrlParams );
1467 if ( $reqUrlParams === $cdnUrlParams ) {
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
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.
A class containing constants representing the names of configuration variables.
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...
matchURLForCDN(array $cdnUrls)
Determine if the request URL matches one of a given set of canonical CDN URLs.
markAsSafeRequest()
Mark this request as identified as being nullipotent even if it is a POST request.
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
string[] $queryParams
The parameters from $_GET only.
getLimitOffsetForUser(UserIdentity $user, $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
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.
isSafeRequest()
Whether this request should be identified as being "safe".
getSession()
Return the session for this request.
getRawInput()
Return the raw request body, with no processing.
getValues(... $names)
Extracts the (given) named values into an array.
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 text string and partially normalized it.
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
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.
static getRequestId()
Get the current 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.
static getRequestPathSuffix( $basePath)
If the request URL matches a given base path, extract the path part of the request URL after that bas...
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.
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and 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.
string[] $headers
Lazy-initialized request headers indexed by upper-case header name.
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.
getSessionId()
Get the session id for this request, if any.
getAcceptLang()
Parse the Accept-Language header sent by the client into an array.
static canonicalizeIPv6LoopbackAddress( $ip)
Converts ::1 (IPv6 loopback address) to 127.0.0.1 (IPv4 loopback address); assists in matching truste...
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,...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
static detectServer( $assumeProxiesUseDefaultProtocolPorts=null)
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
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)
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.
hasSafeMethod()
Check if this request uses a "safe" HTTP method.
getRawVal( $name, $default=null)
Fetch a string WITHOUT any Unicode or line break normalization.
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.
getRawIP()
Fetch the raw IP from the request.
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.
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 and return it in normalized form.
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.
string[] $queryAndPathParams
The parameters from $_GET.
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 ...
$wgUsePathInfo
Config variable stub for the UsePathInfo setting, for use by phpdoc and IDEs.
$wgUseSameSiteLegacyCookies
Config variable stub for the UseSameSiteLegacyCookies setting, for use by phpdoc and IDEs.
$wgScript
Config variable stub for the Script setting, for use by phpdoc and IDEs.
$wgActionPaths
Config variable stub for the ActionPaths setting, for use by phpdoc and IDEs.
$wgArticlePath
Config variable stub for the ArticlePath setting, for use by phpdoc and IDEs.
$wgAllowExternalReqID
Config variable stub for the AllowExternalReqID setting, for use by phpdoc and IDEs.
$wgVariantArticlePath
Config variable stub for the VariantArticlePath setting, for use by phpdoc and IDEs.
$wgCookiePrefix
Config variable stub for the CookiePrefix setting, for use by phpdoc and IDEs.
$wgUsePrivateIPs
Config variable stub for the UsePrivateIPs setting, for use by phpdoc and IDEs.