MediaWiki  master
WebRequest.php
Go to the documentation of this file.
1 <?php
30 use Wikimedia\IPUtils;
31 
32 // The point of this class is to be a wrapper around super globals
33 // phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
34 
42 class WebRequest {
47  protected $data;
48 
55 
59  protected $queryParams;
60 
65  protected $headers = [];
66 
71  public const GETHEADER_LIST = 1;
72 
77  private static $reqId;
78 
83  private $response;
84 
89  private $ip;
90 
95  protected $requestTime;
96 
101  protected $protocol;
102 
111  protected $sessionId = null;
112 
114  protected $markedAsSafe = false;
115 
119  public function __construct() {
120  $this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
121 
122  // POST overrides GET data
123  // We don't use $_REQUEST here to avoid interference from cookies...
124  $this->data = $_POST + $_GET;
125 
126  $this->queryAndPathParams = $this->queryParams = $_GET;
127  }
128 
148  public static function getPathInfo( $want = 'all' ) {
149  // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
150  // And also by Apache 2.x, double slashes are converted to single slashes.
151  // So we will use REQUEST_URI if possible.
152  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
153  // Slurp out the path portion to examine...
154  $url = $_SERVER['REQUEST_URI'];
155  if ( !preg_match( '!^https?://!', $url ) ) {
156  $url = 'http://unused' . $url;
157  }
158  $a = parse_url( $url );
159  if ( !$a ) {
160  return [];
161  }
162  $path = $a['path'] ?? '';
163 
164  global $wgScript;
165  if ( $path == $wgScript && $want !== 'all' ) {
166  // Script inside a rewrite path?
167  // Abort to keep from breaking...
168  return [];
169  }
170 
171  $router = new PathRouter;
172 
173  // Raw PATH_INFO style
174  $router->add( "$wgScript/$1" );
175 
176  global $wgArticlePath;
177  if ( $wgArticlePath ) {
178  $router->validateRoute( $wgArticlePath, 'wgArticlePath' );
179  $router->add( $wgArticlePath );
180  }
181 
182  global $wgActionPaths;
184  if ( $articlePaths ) {
185  $router->add( $articlePaths, [ 'action' => '$key' ] );
186  }
187 
188  global $wgVariantArticlePath;
189  if ( $wgVariantArticlePath ) {
190  $router->validateRoute( $wgVariantArticlePath, 'wgVariantArticlePath' );
191  $router->add( $wgVariantArticlePath,
192  [ 'variant' => '$2' ],
193  [ '$2' => MediaWikiServices::getInstance()->getContentLanguage()->
194  getVariants() ]
195  );
196  }
197 
198  Hooks::runner()->onWebRequestPathInfoRouter( $router );
199 
200  $matches = $router->parse( $path );
201  } else {
202  global $wgUsePathInfo;
203  $matches = [];
204  if ( $wgUsePathInfo ) {
205  if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
206  // Mangled PATH_INFO
207  // https://bugs.php.net/bug.php?id=31892
208  // Also reported when ini_get('cgi.fix_pathinfo')==false
209  $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
210  } elseif ( !empty( $_SERVER['PATH_INFO'] ) ) {
211  // Regular old PATH_INFO yay
212  $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
213  }
214  }
215  }
216 
217  return $matches;
218  }
219 
231  public static function getRequestPathSuffix( $basePath ) {
232  $basePath = rtrim( $basePath, '/' ) . '/';
233  $requestUrl = self::getGlobalRequestURL();
234  $qpos = strpos( $requestUrl, '?' );
235  if ( $qpos !== false ) {
236  $requestPath = substr( $requestUrl, 0, $qpos );
237  } else {
238  $requestPath = $requestUrl;
239  }
240  if ( substr( $requestPath, 0, strlen( $basePath ) ) !== $basePath ) {
241  return false;
242  }
243  return rawurldecode( substr( $requestPath, strlen( $basePath ) ) );
244  }
245 
252  public static function detectServer() {
254 
255  $proto = self::detectProtocol();
256  $stdPort = $proto === 'https' ? 443 : 80;
257 
258  $varNames = [ 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' ];
259  $host = 'localhost';
260  $port = $stdPort;
261  foreach ( $varNames as $varName ) {
262  if ( !isset( $_SERVER[$varName] ) ) {
263  continue;
264  }
265 
266  $parts = IPUtils::splitHostAndPort( $_SERVER[$varName] );
267  if ( !$parts ) {
268  // Invalid, do not use
269  continue;
270  }
271 
272  $host = $parts[0];
273  if ( $wgAssumeProxiesUseDefaultProtocolPorts && isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
274  // T72021: Assume that upstream proxy is running on the default
275  // port based on the protocol. We have no reliable way to determine
276  // the actual port in use upstream.
277  $port = $stdPort;
278  } elseif ( $parts[1] === false ) {
279  if ( isset( $_SERVER['SERVER_PORT'] ) ) {
280  $port = $_SERVER['SERVER_PORT'];
281  } // else leave it as $stdPort
282  } else {
283  $port = $parts[1];
284  }
285  break;
286  }
287 
288  return $proto . '://' . IPUtils::combineHostAndPort( $host, $port, $stdPort );
289  }
290 
298  public static function detectProtocol() {
299  if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
300  ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
301  $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
302  return 'https';
303  } else {
304  return 'http';
305  }
306  }
307 
315  public function getElapsedTime() {
316  return microtime( true ) - $this->requestTime;
317  }
318 
327  public static function getRequestId() {
328  // This method is called from various error handlers and should be kept simple.
329 
330  if ( !self::$reqId ) {
331  global $wgAllowExternalReqID;
333  ? RequestContext::getMain()->getRequest()->getHeader( 'X-Request-Id' )
334  : null;
335  if ( !$id ) {
336  $id = $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
337  }
338  self::$reqId = $id;
339  }
340 
341  return self::$reqId;
342  }
343 
351  public static function overrideRequestId( $id ) {
352  self::$reqId = $id;
353  }
354 
359  public function getProtocol() {
360  if ( $this->protocol === null ) {
361  $this->protocol = self::detectProtocol();
362  }
363  return $this->protocol;
364  }
365 
373  public function interpolateTitle() {
374  // T18019: title interpolation on API queries is useless and sometimes harmful
375  if ( defined( 'MW_API' ) ) {
376  return;
377  }
378 
379  $matches = self::getPathInfo( 'title' );
380  foreach ( $matches as $key => $val ) {
381  $this->data[$key] = $this->queryAndPathParams[$key] = $val;
382  }
383  }
384 
395  public static function extractTitle( $path, $bases, $key = false ) {
396  foreach ( (array)$bases as $keyValue => $base ) {
397  // Find the part after $wgArticlePath
398  $base = str_replace( '$1', '', $base );
399  $baseLen = strlen( $base );
400  if ( substr( $path, 0, $baseLen ) == $base ) {
401  $raw = substr( $path, $baseLen );
402  if ( $raw !== '' ) {
403  $matches = [ 'title' => rawurldecode( $raw ) ];
404  if ( $key ) {
405  $matches[$key] = $keyValue;
406  }
407  return $matches;
408  }
409  }
410  }
411  return [];
412  }
413 
421  public function normalizeUnicode( $data ) {
422  if ( is_array( $data ) ) {
423  foreach ( $data as $key => $val ) {
424  $data[$key] = $this->normalizeUnicode( $val );
425  }
426  } else {
427  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
428  $data = $contLang->normalize( $data );
429  }
430  return $data;
431  }
432 
441  private function getGPCVal( $arr, $name, $default ) {
442  # PHP is so nice to not touch input data, except sometimes:
443  # https://www.php.net/variables.external#language.variables.external.dot-in-names
444  # Work around PHP *feature* to avoid *bugs* elsewhere.
445  $name = strtr( $name, '.', '_' );
446 
447  if ( !isset( $arr[$name] ) ) {
448  return $default;
449  }
450 
451  $data = $arr[$name];
452  # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
453  $isAsciiStr = ( is_string( $data ) && preg_match( '/[^\x20-\x7E]/', $data ) === 0 );
454  if ( !$isAsciiStr ) {
455  if ( isset( $_GET[$name] ) && is_string( $data ) ) {
456  # Check for alternate/legacy character encoding.
457  $data = MediaWikiServices::getInstance()
458  ->getContentLanguage()
459  ->checkTitleEncoding( $data );
460  }
461  $data = $this->normalizeUnicode( $data );
462  }
463 
464  return $data;
465  }
466 
479  public function getRawVal( $name, $default = null ) {
480  $name = strtr( $name, '.', '_' ); // See comment in self::getGPCVal()
481  if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
482  $val = $this->data[$name];
483  } else {
484  $val = $default;
485  }
486  if ( $val === null ) {
487  return $val;
488  } else {
489  return (string)$val;
490  }
491  }
492 
503  public function getVal( $name, $default = null ) {
504  $val = $this->getGPCVal( $this->data, $name, $default );
505  if ( is_array( $val ) ) {
506  $val = $default;
507  }
508  if ( $val === null ) {
509  return $val;
510  } else {
511  return (string)$val;
512  }
513  }
514 
522  public function setVal( $key, $value ) {
523  $ret = $this->data[$key] ?? null;
524  $this->data[$key] = $value;
525  return $ret;
526  }
527 
534  public function unsetVal( $key ) {
535  if ( !isset( $this->data[$key] ) ) {
536  $ret = null;
537  } else {
538  $ret = $this->data[$key];
539  unset( $this->data[$key] );
540  }
541  return $ret;
542  }
543 
553  public function getArray( $name, $default = null ) {
554  $val = $this->getGPCVal( $this->data, $name, $default );
555  if ( $val === null ) {
556  return null;
557  } else {
558  return (array)$val;
559  }
560  }
561 
572  public function getIntArray( $name, $default = null ) {
573  $val = $this->getArray( $name, $default );
574  if ( is_array( $val ) ) {
575  $val = array_map( 'intval', $val );
576  }
577  return $val;
578  }
579 
589  public function getInt( $name, $default = 0 ) {
590  return intval( $this->getRawVal( $name, $default ) );
591  }
592 
601  public function getIntOrNull( $name ) {
602  $val = $this->getRawVal( $name );
603  return is_numeric( $val )
604  ? intval( $val )
605  : null;
606  }
607 
618  public function getFloat( $name, $default = 0.0 ) {
619  return floatval( $this->getRawVal( $name, $default ) );
620  }
621 
631  public function getBool( $name, $default = false ) {
632  return (bool)$this->getRawVal( $name, $default );
633  }
634 
644  public function getFuzzyBool( $name, $default = false ) {
645  return $this->getBool( $name, $default )
646  && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
647  }
648 
657  public function getCheck( $name ) {
658  # Checkboxes and buttons are only present when clicked
659  # Presence connotes truth, absence false
660  return $this->getRawVal( $name, null ) !== null;
661  }
662 
673  public function getText( $name, $default = '' ) {
674  $val = $this->getVal( $name, $default );
675  return str_replace( "\r\n", "\n", $val );
676  }
677 
685  public function getValues() {
686  $names = func_get_args();
687  if ( count( $names ) == 0 ) {
688  $names = array_keys( $this->data );
689  }
690 
691  $retVal = [];
692  foreach ( $names as $name ) {
693  $value = $this->getGPCVal( $this->data, $name, null );
694  if ( $value !== null ) {
695  $retVal[$name] = $value;
696  }
697  }
698  return $retVal;
699  }
700 
707  public function getValueNames( $exclude = [] ) {
708  return array_diff( array_keys( $this->getValues() ), $exclude );
709  }
710 
718  public function getQueryValues() {
720  }
721 
731  public function getQueryValuesOnly() {
732  return $this->queryParams;
733  }
734 
743  public function getPostValues() {
744  return $_POST;
745  }
746 
754  public function getRawQueryString() {
755  return $_SERVER['QUERY_STRING'];
756  }
757 
764  public function getRawPostString() {
765  if ( !$this->wasPosted() ) {
766  return '';
767  }
768  return $this->getRawInput();
769  }
770 
778  public function getRawInput() {
779  static $input = null;
780  if ( $input === null ) {
781  $input = file_get_contents( 'php://input' );
782  }
783  return $input;
784  }
785 
791  public function getMethod() {
792  return $_SERVER['REQUEST_METHOD'] ?? 'GET';
793  }
794 
804  public function wasPosted() {
805  return $this->getMethod() == 'POST';
806  }
807 
818  public function getSession() {
819  if ( $this->sessionId !== null ) {
820  $session = SessionManager::singleton()->getSessionById( (string)$this->sessionId, true, $this );
821  if ( $session ) {
822  return $session;
823  }
824  }
825 
826  $session = SessionManager::singleton()->getSessionForRequest( $this );
827  $this->sessionId = $session->getSessionId();
828  return $session;
829  }
830 
837  public function setSessionId( SessionId $sessionId ) {
838  $this->sessionId = $sessionId;
839  }
840 
847  public function getSessionId() {
848  return $this->sessionId;
849  }
850 
859  public function getCookie( $key, $prefix = null, $default = null ) {
860  if ( $prefix === null ) {
861  global $wgCookiePrefix;
862  $prefix = $wgCookiePrefix;
863  }
864  $name = $prefix . $key;
865  // Work around mangling of $_COOKIE
866  $name = strtr( $name, '.', '_' );
867  if ( isset( $_COOKIE[$name] ) ) {
868  return $_COOKIE[$name];
869  } else {
870  return $default;
871  }
872  }
873 
882  public function getCrossSiteCookie( $key, $prefix = '', $default = null ) {
884  $name = $prefix . $key;
885  // Work around mangling of $_COOKIE
886  $name = strtr( $name, '.', '_' );
887  if ( isset( $_COOKIE[$name] ) ) {
888  return $_COOKIE[$name];
889  }
891  $legacyName = $prefix . "ss0-" . $key;
892  $legacyName = strtr( $legacyName, '.', '_' );
893  if ( isset( $_COOKIE[$legacyName] ) ) {
894  return $_COOKIE[$legacyName];
895  }
896  }
897  return $default;
898  }
899 
907  public static function getGlobalRequestURL() {
908  // This method is called on fatal errors; it should not depend on anything complex.
909 
910  if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
911  $base = $_SERVER['REQUEST_URI'];
912  } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] )
913  && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] )
914  ) {
915  // Probably IIS; doesn't set REQUEST_URI
916  $base = $_SERVER['HTTP_X_ORIGINAL_URL'];
917  } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
918  $base = $_SERVER['SCRIPT_NAME'];
919  if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
920  $base .= '?' . $_SERVER['QUERY_STRING'];
921  }
922  } else {
923  // This shouldn't happen!
924  throw new MWException( "Web server doesn't provide either " .
925  "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
926  "of your web server configuration to https://phabricator.wikimedia.org/" );
927  }
928  // User-agents should not send a fragment with the URI, but
929  // if they do, and the web server passes it on to us, we
930  // need to strip it or we get false-positive redirect loops
931  // or weird output URLs
932  $hash = strpos( $base, '#' );
933  if ( $hash !== false ) {
934  $base = substr( $base, 0, $hash );
935  }
936 
937  if ( $base[0] == '/' ) {
938  // More than one slash will look like it is protocol relative
939  return preg_replace( '!^/+!', '/', $base );
940  } else {
941  // We may get paths with a host prepended; strip it.
942  return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
943  }
944  }
945 
953  public function getRequestURL() {
954  return self::getGlobalRequestURL();
955  }
956 
967  public function getFullRequestURL() {
968  // Pass an explicit PROTO constant instead of PROTO_CURRENT so that we
969  // do not rely on state from the global $wgRequest object (which it would,
970  // via wfGetServerUrl/wfExpandUrl/$wgRequest->protocol).
971  if ( $this->getProtocol() === 'http' ) {
972  return wfGetServerUrl( PROTO_HTTP ) . $this->getRequestURL();
973  } else {
974  return wfGetServerUrl( PROTO_HTTPS ) . $this->getRequestURL();
975  }
976  }
977 
983  public function appendQueryValue( $key, $value ) {
984  return $this->appendQueryArray( [ $key => $value ] );
985  }
986 
993  public function appendQueryArray( $array ) {
994  $newquery = $this->getQueryValues();
995  unset( $newquery['title'] );
996  $newquery = array_merge( $newquery, $array );
997 
998  return wfArrayToCgi( $newquery );
999  }
1000 
1010  public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
1011  wfDeprecated( __METHOD__, '1.35' );
1012 
1013  global $wgUser;
1014  return $this->getLimitOffsetForUser( $wgUser, $deflimit, $optionname );
1015  }
1016 
1027  public function getLimitOffsetForUser( User $user, $deflimit = 50, $optionname = 'rclimit' ) {
1028  $limit = $this->getInt( 'limit', 0 );
1029  if ( $limit < 0 ) {
1030  $limit = 0;
1031  }
1032  if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
1033  $limit = $user->getIntOption( $optionname );
1034  }
1035  if ( $limit <= 0 ) {
1036  $limit = $deflimit;
1037  }
1038  if ( $limit > 5000 ) {
1039  $limit = 5000; # We have *some* limits...
1040  }
1041 
1042  $offset = $this->getInt( 'offset', 0 );
1043  if ( $offset < 0 ) {
1044  $offset = 0;
1045  }
1046 
1047  return [ $limit, $offset ];
1048  }
1049 
1056  public function getFileTempname( $key ) {
1057  $file = new WebRequestUpload( $this, $key );
1058  return $file->getTempName();
1059  }
1060 
1067  public function getUploadError( $key ) {
1068  $file = new WebRequestUpload( $this, $key );
1069  return $file->getError();
1070  }
1071 
1083  public function getFileName( $key ) {
1084  $file = new WebRequestUpload( $this, $key );
1085  return $file->getName();
1086  }
1087 
1094  public function getUpload( $key ) {
1095  return new WebRequestUpload( $this, $key );
1096  }
1097 
1104  public function response() {
1105  /* Lazy initialization of response object for this request */
1106  if ( !is_object( $this->response ) ) {
1107  $class = ( $this instanceof FauxRequest ) ? FauxResponse::class : WebResponse::class;
1108  $this->response = new $class();
1109  }
1110  return $this->response;
1111  }
1112 
1116  protected function initHeaders() {
1117  if ( count( $this->headers ) ) {
1118  return;
1119  }
1120 
1121  $this->headers = array_change_key_case( getallheaders(), CASE_UPPER );
1122  }
1123 
1129  public function getAllHeaders() {
1130  $this->initHeaders();
1131  return $this->headers;
1132  }
1133 
1146  public function getHeader( $name, $flags = 0 ) {
1147  $this->initHeaders();
1148  $name = strtoupper( $name );
1149  if ( !isset( $this->headers[$name] ) ) {
1150  return false;
1151  }
1152  $value = $this->headers[$name];
1153  if ( $flags & self::GETHEADER_LIST ) {
1154  $value = array_map( 'trim', explode( ',', $value ) );
1155  }
1156  return $value;
1157  }
1158 
1166  public function getSessionData( $key ) {
1167  return $this->getSession()->get( $key );
1168  }
1169 
1177  public function setSessionData( $key, $data ) {
1178  $this->getSession()->set( $key, $data );
1179  }
1180 
1190  public function checkUrlExtension( $extWhitelist = [] ) {
1191  wfDeprecated( __METHOD__, '1.35' );
1192  return true;
1193  }
1194 
1208  public function getAcceptLang() {
1209  // Modified version of code found at
1210  // http://www.thefutureoftheweb.com/blog/use-accept-language-header
1211  $acceptLang = $this->getHeader( 'Accept-Language' );
1212  if ( !$acceptLang ) {
1213  return [];
1214  }
1215 
1216  // Return the language codes in lower case
1217  $acceptLang = strtolower( $acceptLang );
1218 
1219  // Break up string into pieces (languages and q factors)
1220  if ( !preg_match_all(
1221  '/
1222  # a language code or a star is required
1223  ([a-z]{1,8}(?:-[a-z]{1,8})*|\*)
1224  # from here everything is optional
1225  \s*
1226  (?:
1227  # this accepts only numbers in the range ;q=0.000 to ;q=1.000
1228  ;\s*q\s*=\s*
1229  (1(?:\.0{0,3})?|0(?:\.\d{0,3})?)?
1230  )?
1231  /x',
1232  $acceptLang,
1233  $matches,
1234  PREG_SET_ORDER
1235  ) ) {
1236  return [];
1237  }
1238 
1239  // Create a list like "en" => 0.8
1240  $langs = [];
1241  foreach ( $matches as $match ) {
1242  $languageCode = $match[1];
1243  // When not present, the default value is 1
1244  $qValue = (float)( $match[2] ?? 1.0 );
1245  if ( $qValue ) {
1246  $langs[$languageCode] = $qValue;
1247  }
1248  }
1249 
1250  // Sort list by qValue
1251  arsort( $langs, SORT_NUMERIC );
1252  return $langs;
1253  }
1254 
1263  protected function getRawIP() {
1264  if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
1265  return null;
1266  }
1267 
1268  if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) {
1269  throw new MWException( __METHOD__
1270  . " : Could not determine the remote IP address due to multiple values." );
1271  } else {
1272  $ipchain = $_SERVER['REMOTE_ADDR'];
1273  }
1274 
1275  return IPUtils::canonicalize( $ipchain );
1276  }
1277 
1287  public function getIP() {
1288  global $wgUsePrivateIPs;
1289 
1290  # Return cached result
1291  if ( $this->ip !== null ) {
1292  return $this->ip;
1293  }
1294 
1295  # collect the originating ips
1296  $ip = $this->getRawIP();
1297  if ( !$ip ) {
1298  throw new MWException( 'Unable to determine IP.' );
1299  }
1300 
1301  # Append XFF
1302  $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
1303  if ( $forwardedFor !== false ) {
1304  $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1305  $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
1306  $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
1307  $ipchain = array_reverse( $ipchain );
1308  array_unshift( $ipchain, $ip );
1309 
1310  # Step through XFF list and find the last address in the list which is a
1311  # trusted server. Set $ip to the IP address given by that trusted server,
1312  # unless the address is not sensible (e.g. private). However, prefer private
1313  # IP addresses over proxy servers controlled by this site (more sensible).
1314  # Note that some XFF values might be "unknown" with Squid/Varnish.
1315  foreach ( $ipchain as $i => $curIP ) {
1316  $curIP = IPUtils::sanitizeIP( IPUtils::canonicalize( $curIP ) );
1317  if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
1318  || !$proxyLookup->isTrustedProxy( $curIP )
1319  ) {
1320  break; // IP is not valid/trusted or does not point to anything
1321  }
1322  if (
1323  IPUtils::isPublic( $ipchain[$i + 1] ) ||
1324  $wgUsePrivateIPs ||
1325  $proxyLookup->isConfiguredProxy( $curIP ) // T50919; treat IP as sane
1326  ) {
1327  // Follow the next IP according to the proxy
1328  $nextIP = IPUtils::canonicalize( $ipchain[$i + 1] );
1329  if ( !$nextIP && $isConfigured ) {
1330  // We have not yet made it past CDN/proxy servers of this site,
1331  // so either they are misconfigured or there is some IP spoofing.
1332  throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
1333  }
1334  $ip = $nextIP;
1335  // keep traversing the chain
1336  continue;
1337  }
1338  break;
1339  }
1340  }
1341 
1342  # Allow extensions to improve our guess
1343  Hooks::runner()->onGetIP( $ip );
1344 
1345  if ( !$ip ) {
1346  throw new MWException( "Unable to determine IP." );
1347  }
1348 
1349  $this->ip = $ip;
1350  return $ip;
1351  }
1352 
1358  public function setIP( $ip ) {
1359  $this->ip = $ip;
1360  }
1361 
1374  public function hasSafeMethod() {
1375  if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
1376  return false; // CLI mode
1377  }
1378 
1379  return in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
1380  }
1381 
1400  public function isSafeRequest() {
1401  if ( $this->markedAsSafe && $this->wasPosted() ) {
1402  return true; // marked as a "safe" POST
1403  }
1404 
1405  return $this->hasSafeMethod();
1406  }
1407 
1418  public function markAsSafeRequest() {
1419  $this->markedAsSafe = true;
1420  }
1421 }
PathRouter\add
add( $path, $params=[], $options=[])
Add a new path pattern to the path router.
Definition: PathRouter.php:158
WebRequest\initHeaders
initHeaders()
Initialise the header list.
Definition: WebRequest.php:1116
WebRequest\$sessionId
SessionId null $sessionId
Session ID to use for this request.
Definition: WebRequest.php:111
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:35
WebRequest\getSessionData
getSessionData( $key)
Get data from the session.
Definition: WebRequest.php:1166
WebRequest\$headers
array $headers
Lazy-initialized request headers indexed by upper-case header name.
Definition: WebRequest.php:65
WebRequest\getValueNames
getValueNames( $exclude=[])
Returns the names of all input values excluding those in $exclude.
Definition: WebRequest.php:707
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
WebRequest\$data
array $data
The parameters from $_GET, $_POST and the path router.
Definition: WebRequest.php:47
WebRequest\getRequestPathSuffix
static getRequestPathSuffix( $basePath)
If the request URL matches a given base path, extract the path part of the request URL after that bas...
Definition: WebRequest.php:231
WebRequest\getSessionId
getSessionId()
Get the session id for this request, if any.
Definition: WebRequest.php:847
$wgScript
$wgScript
The URL path to index.php.
Definition: DefaultSettings.php:207
WebRequest\$queryParams
$queryParams
The parameters from $_GET only.
Definition: WebRequest.php:59
User\getIntOption
getIntOption( $oname, $defaultOverride=0)
Get the user's current setting for a given option, as an integer value.
Definition: User.php:2549
WebRequest\appendQueryValue
appendQueryValue( $key, $value)
Definition: WebRequest.php:983
WebRequest\interpolateTitle
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
Definition: WebRequest.php:373
WebRequest\setSessionId
setSessionId(SessionId $sessionId)
Set the session for this request.
Definition: WebRequest.php:837
WebRequest\getAcceptLang
getAcceptLang()
Parse the Accept-Language header sent by the client into an array.
Definition: WebRequest.php:1208
WebRequest\getElapsedTime
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
Definition: WebRequest.php:315
WebRequest\getIntOrNull
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
Definition: WebRequest.php:601
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
WebRequest\getRawPostString
getRawPostString()
Return the contents of the POST with no decoding.
Definition: WebRequest.php:764
WebRequest\detectProtocol
static detectProtocol()
Detect the protocol from $_SERVER.
Definition: WebRequest.php:298
WebRequest\getGPCVal
getGPCVal( $arr, $name, $default)
Fetch a value from the given array or return $default if it's not set.
Definition: WebRequest.php:441
$wgUseSameSiteLegacyCookies
bool $wgUseSameSiteLegacyCookies
If true, when a cross-site cookie with SameSite=None is sent, a legacy cookie with an "ss0" prefix wi...
Definition: DefaultSettings.php:6512
WebRequest\hasSafeMethod
hasSafeMethod()
Check if this request uses a "safe" HTTP method.
Definition: WebRequest.php:1374
$base
$base
Definition: generateLocalAutoload.php:11
WebRequest\__construct
__construct()
Definition: WebRequest.php:119
WebRequest\getRawQueryString
getRawQueryString()
Return the contents of the Query with no decoding.
Definition: WebRequest.php:754
WebRequest\appendQueryArray
appendQueryArray( $array)
Appends or replaces value of query variables.
Definition: WebRequest.php:993
$wgAllowExternalReqID
$wgAllowExternalReqID
Whether to respect/honour the request ID provided by the incoming request via the X-Request-Id header...
Definition: DefaultSettings.php:8928
PathRouter\getActionPaths
static getActionPaths(array $actionPaths, $articlePath)
Definition: PathRouter.php:430
WebRequest\getFileTempname
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
Definition: WebRequest.php:1056
$wgAssumeProxiesUseDefaultProtocolPorts
bool $wgAssumeProxiesUseDefaultProtocolPorts
When the wiki is running behind a proxy and this is set to true, assumes that the proxy exposes the w...
Definition: DefaultSettings.php:135
WebRequest\isSafeRequest
isSafeRequest()
Whether this request should be identified as being "safe".
Definition: WebRequest.php:1400
WebRequest\getText
getText( $name, $default='')
Fetch a text string from the given array or return $default if it's not set.
Definition: WebRequest.php:673
WebRequest\$protocol
string $protocol
Cached URL protocol.
Definition: WebRequest.php:101
WebRequest\getMethod
getMethod()
Get the HTTP method used for this request.
Definition: WebRequest.php:791
MWException
MediaWiki exception.
Definition: MWException.php:29
WebRequest\getFileName
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
Definition: WebRequest.php:1083
WebRequest\getQueryValuesOnly
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
Definition: WebRequest.php:731
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1026
MediaWiki\Session\Session
Manages data for an an authenticated session.
Definition: Session.php:48
WebRequest\setVal
setVal( $key, $value)
Set an arbitrary value into our get/post data.
Definition: WebRequest.php:522
WebRequest\$reqId
static string $reqId
The unique request ID.
Definition: WebRequest.php:77
$matches
$matches
Definition: NoLocalSettings.php:24
WebRequest\getRawInput
getRawInput()
Return the raw request body, with no processing.
Definition: WebRequest.php:778
WebRequest\getUpload
getUpload( $key)
Return a WebRequestUpload object corresponding to the key.
Definition: WebRequest.php:1094
WebRequest\getValues
getValues()
Extracts the given named values into an array.
Definition: WebRequest.php:685
WebRequest\getPathInfo
static getPathInfo( $want='all')
Extract relevant query arguments from the http request uri's path to be merged with the normal php pr...
Definition: WebRequest.php:148
WebRequest\getFullRequestURL
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and query string.
Definition: WebRequest.php:967
WebRequest\getArray
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
Definition: WebRequest.php:553
WebRequest\$response
WebResponse $response
Lazy-init response object.
Definition: WebRequest.php:83
WebRequest\markAsSafeRequest
markAsSafeRequest()
Mark this request as identified as being nullipotent even if it is a POST request.
Definition: WebRequest.php:1418
WebRequest\getAllHeaders
getAllHeaders()
Get an array containing all request headers.
Definition: WebRequest.php:1129
WebRequestUpload
Object to access the $_FILES array.
Definition: WebRequestUpload.php:30
PROTO_HTTPS
const PROTO_HTTPS
Definition: Defines.php:209
WebRequest\normalizeUnicode
normalizeUnicode( $data)
Recursively normalizes UTF-8 strings in the given array.
Definition: WebRequest.php:421
WebRequest\getRawVal
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it's not set.
Definition: WebRequest.php:479
WebRequest\getCheck
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
Definition: WebRequest.php:657
WebRequest\getProtocol
getProtocol()
Get the current URL protocol (http or https)
Definition: WebRequest.php:359
WebRequest\setIP
setIP( $ip)
Definition: WebRequest.php:1358
WebRequest\getSession
getSession()
Return the session for this request.
Definition: WebRequest.php:818
WebRequest\response
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
Definition: WebRequest.php:1104
$wgUsePrivateIPs
$wgUsePrivateIPs
Should forwarded Private IPs be accepted?
Definition: DefaultSettings.php:3077
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:52
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
WebRequest\getCrossSiteCookie
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
Definition: WebRequest.php:882
WebRequest\getLimitOffset
getLimitOffset( $deflimit=50, $optionname='rclimit')
Same as ::getLimitOffsetForUser, but without a user parameter, instead using $wgUser.
Definition: WebRequest.php:1010
WebRequest\checkUrlExtension
checkUrlExtension( $extWhitelist=[])
This function formerly did a security check to prevent an XSS vulnerability in IE6,...
Definition: WebRequest.php:1190
PROTO_HTTP
const PROTO_HTTP
Definition: Defines.php:208
WebRequest\getIntArray
getIntArray( $name, $default=null)
Fetch an array of integers, or return $default if it's not set.
Definition: WebRequest.php:572
WebRequest\$requestTime
float $requestTime
The timestamp of the start of the request, with microsecond precision.
Definition: WebRequest.php:95
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:453
WebRequest\$markedAsSafe
bool $markedAsSafe
Whether this HTTP request is "safe" (even if it is an HTTP post)
Definition: WebRequest.php:114
WebRequest\getCookie
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
Definition: WebRequest.php:859
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
WebRequest\getIP
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
Definition: WebRequest.php:1287
WebRequest\getUploadError
getUploadError( $key)
Return the upload error or 0.
Definition: WebRequest.php:1067
WebRequest\$queryAndPathParams
array $queryAndPathParams
The parameters from $_GET.
Definition: WebRequest.php:54
$wgArticlePath
$wgArticlePath
The URL path for primary article page views.
Definition: DefaultSettings.php:267
WebRequest\setSessionData
setSessionData( $key, $data)
Set session data.
Definition: WebRequest.php:1177
$wgActionPaths
$wgActionPaths
To set 'pretty' URL paths for actions other than plain page views, add to this array.
Definition: DefaultSettings.php:446
WebRequest\GETHEADER_LIST
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
Definition: WebRequest.php:71
WebRequest\getVal
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
Definition: WebRequest.php:503
WebRequest\getInt
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
Definition: WebRequest.php:589
MediaWiki\Session\SessionId
Value object holding the session ID in a manner that can be globally updated.
Definition: SessionId.php:40
WebRequest\getRequestId
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:327
WebRequest\getLimitOffsetForUser
getLimitOffsetForUser(User $user, $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
Definition: WebRequest.php:1027
$path
$path
Definition: NoLocalSettings.php:25
WebRequest\getFloat
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
Definition: WebRequest.php:618
WebRequest\detectServer
static detectServer()
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
Definition: WebRequest.php:252
WebRequest\getHeader
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
Definition: WebRequest.php:1146
$basePath
$basePath
Definition: addSite.php:5
WebRequest\getGlobalRequestURL
static getGlobalRequestURL()
Return the path and query string portion of the main request URI.
Definition: WebRequest.php:907
WebRequest\$ip
string $ip
Cached client IP address.
Definition: WebRequest.php:89
WebRequest\getPostValues
getPostValues()
Get the values passed via POST.
Definition: WebRequest.php:743
WebRequest\wasPosted
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
Definition: WebRequest.php:804
WebRequest\unsetVal
unsetVal( $key)
Unset an arbitrary value from our get/post data.
Definition: WebRequest.php:534
wfGetServerUrl
wfGetServerUrl( $proto)
Get the wiki's "server", i.e.
Definition: GlobalFunctions.php:568
WebRequest\getRequestURL
getRequestURL()
Return the path and query string portion of the request URI.
Definition: WebRequest.php:953
WebRequest\overrideRequestId
static overrideRequestId( $id)
Override the unique request ID.
Definition: WebRequest.php:351
WebRequest\extractTitle
static extractTitle( $path, $bases, $key=false)
URL rewriting function; tries to extract page title and, optionally, one other fixed parameter value ...
Definition: WebRequest.php:395
WebResponse
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Definition: WebResponse.php:30
WebRequest\getRawIP
getRawIP()
Fetch the raw IP from the request.
Definition: WebRequest.php:1263
WebRequest\getQueryValues
getQueryValues()
Get the values passed in the query string and the path router parameters.
Definition: WebRequest.php:718
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
WebRequest\getFuzzyBool
getFuzzyBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
Definition: WebRequest.php:644
$wgVariantArticlePath
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
Definition: DefaultSettings.php:3297
PathRouter
PathRouter class.
Definition: PathRouter.php:73
$wgCookiePrefix
$wgCookiePrefix
Cookies generated by MediaWiki have names starting with this prefix.
Definition: DefaultSettings.php:6483
WebRequest\getBool
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
Definition: WebRequest.php:631
$wgUsePathInfo
$wgUsePathInfo
Whether to support URLs like index.php/Page_title These often break when PHP is set up in CGI mode.
Definition: DefaultSettings.php:198
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:346
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:273