MediaWiki  1.34.4
WebRequest.php
Go to the documentation of this file.
1 <?php
30 use Wikimedia\AtEase\AtEase;
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  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 
108  protected $sessionId = null;
109 
111  protected $markedAsSafe = false;
112 
116  public function __construct() {
117  $this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
118 
119  // POST overrides GET data
120  // We don't use $_REQUEST here to avoid interference from cookies...
121  $this->data = $_POST + $_GET;
122 
123  $this->queryAndPathParams = $this->queryParams = $_GET;
124  }
125 
141  public static function getPathInfo( $want = 'all' ) {
142  // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
143  // And also by Apache 2.x, double slashes are converted to single slashes.
144  // So we will use REQUEST_URI if possible.
145  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
146  // Slurp out the path portion to examine...
147  $url = $_SERVER['REQUEST_URI'];
148  if ( !preg_match( '!^https?://!', $url ) ) {
149  $url = 'http://unused' . $url;
150  }
151  AtEase::suppressWarnings();
152  $a = parse_url( $url );
153  AtEase::restoreWarnings();
154  if ( !$a ) {
155  return [];
156  }
157  $path = $a['path'] ?? '';
158 
159  global $wgScript;
160  if ( $path == $wgScript && $want !== 'all' ) {
161  // Script inside a rewrite path?
162  // Abort to keep from breaking...
163  return [];
164  }
165 
166  $router = new PathRouter;
167 
168  // Raw PATH_INFO style
169  $router->add( "$wgScript/$1" );
170 
171  if ( isset( $_SERVER['SCRIPT_NAME'] )
172  && strpos( $_SERVER['SCRIPT_NAME'], '.php' ) !== false
173  ) {
174  // Check for SCRIPT_NAME, we handle index.php explicitly
175  // But we do have some other .php files such as img_auth.php
176  // Don't let root article paths clober the parsing for them
177  $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" );
178  }
179 
180  global $wgArticlePath;
181  if ( $wgArticlePath ) {
182  $router->add( $wgArticlePath );
183  }
184 
185  global $wgActionPaths;
187  if ( $articlePaths ) {
188  $router->add( $articlePaths, [ 'action' => '$key' ] );
189  }
190 
191  global $wgVariantArticlePath;
192  if ( $wgVariantArticlePath ) {
193  $router->add( $wgVariantArticlePath,
194  [ 'variant' => '$2' ],
195  [ '$2' => MediaWikiServices::getInstance()->getContentLanguage()->
196  getVariants() ]
197  );
198  }
199 
200  Hooks::run( 'WebRequestPathInfoRouter', [ $router ] );
201 
202  $matches = $router->parse( $path );
203  } else {
204  global $wgUsePathInfo;
205  $matches = [];
206  if ( $wgUsePathInfo ) {
207  if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
208  // Mangled PATH_INFO
209  // https://bugs.php.net/bug.php?id=31892
210  // Also reported when ini_get('cgi.fix_pathinfo')==false
211  $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
212  } elseif ( !empty( $_SERVER['PATH_INFO'] ) ) {
213  // Regular old PATH_INFO yay
214  $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
215  }
216  }
217  }
218 
219  return $matches;
220  }
221 
228  public static function detectServer() {
230 
231  $proto = self::detectProtocol();
232  $stdPort = $proto === 'https' ? 443 : 80;
233 
234  $varNames = [ 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' ];
235  $host = 'localhost';
236  $port = $stdPort;
237  foreach ( $varNames as $varName ) {
238  if ( !isset( $_SERVER[$varName] ) ) {
239  continue;
240  }
241 
242  $parts = IP::splitHostAndPort( $_SERVER[$varName] );
243  if ( !$parts ) {
244  // Invalid, do not use
245  continue;
246  }
247 
248  $host = $parts[0];
249  if ( $wgAssumeProxiesUseDefaultProtocolPorts && isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
250  // T72021: Assume that upstream proxy is running on the default
251  // port based on the protocol. We have no reliable way to determine
252  // the actual port in use upstream.
253  $port = $stdPort;
254  } elseif ( $parts[1] === false ) {
255  if ( isset( $_SERVER['SERVER_PORT'] ) ) {
256  $port = $_SERVER['SERVER_PORT'];
257  } // else leave it as $stdPort
258  } else {
259  $port = $parts[1];
260  }
261  break;
262  }
263 
264  return $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort );
265  }
266 
274  public static function detectProtocol() {
275  if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
276  ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
277  $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
278  return 'https';
279  } else {
280  return 'http';
281  }
282  }
283 
291  public function getElapsedTime() {
292  return microtime( true ) - $this->requestTime;
293  }
294 
303  public static function getRequestId() {
304  // This method is called from various error handlers and should be kept simple.
305 
306  if ( self::$reqId ) {
307  return self::$reqId;
308  }
309 
310  global $wgAllowExternalReqID;
311 
312  self::$reqId = $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
313  if ( $wgAllowExternalReqID ) {
314  $id = RequestContext::getMain()->getRequest()->getHeader( 'X-Request-Id' );
315  if ( $id ) {
316  self::$reqId = $id;
317  }
318  }
319 
320  return self::$reqId;
321  }
322 
330  public static function overrideRequestId( $id ) {
331  self::$reqId = $id;
332  }
333 
338  public function getProtocol() {
339  if ( $this->protocol === null ) {
340  $this->protocol = self::detectProtocol();
341  }
342  return $this->protocol;
343  }
344 
352  public function interpolateTitle() {
353  // T18019: title interpolation on API queries is useless and sometimes harmful
354  if ( defined( 'MW_API' ) ) {
355  return;
356  }
357 
358  $matches = self::getPathInfo( 'title' );
359  foreach ( $matches as $key => $val ) {
360  $this->data[$key] = $this->queryAndPathParams[$key] = $val;
361  }
362  }
363 
374  static function extractTitle( $path, $bases, $key = false ) {
375  foreach ( (array)$bases as $keyValue => $base ) {
376  // Find the part after $wgArticlePath
377  $base = str_replace( '$1', '', $base );
378  $baseLen = strlen( $base );
379  if ( substr( $path, 0, $baseLen ) == $base ) {
380  $raw = substr( $path, $baseLen );
381  if ( $raw !== '' ) {
382  $matches = [ 'title' => rawurldecode( $raw ) ];
383  if ( $key ) {
384  $matches[$key] = $keyValue;
385  }
386  return $matches;
387  }
388  }
389  }
390  return [];
391  }
392 
400  public function normalizeUnicode( $data ) {
401  if ( is_array( $data ) ) {
402  foreach ( $data as $key => $val ) {
403  $data[$key] = $this->normalizeUnicode( $val );
404  }
405  } else {
406  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
407  $data = $contLang ? $contLang->normalize( $data ) :
408  UtfNormal\Validator::cleanUp( $data );
409  }
410  return $data;
411  }
412 
421  private function getGPCVal( $arr, $name, $default ) {
422  # PHP is so nice to not touch input data, except sometimes:
423  # https://www.php.net/variables.external#language.variables.external.dot-in-names
424  # Work around PHP *feature* to avoid *bugs* elsewhere.
425  $name = strtr( $name, '.', '_' );
426 
427  if ( !isset( $arr[$name] ) ) {
428  return $default;
429  }
430 
431  $data = $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 );
440  }
441  $data = $this->normalizeUnicode( $data );
442  }
443 
444  return $data;
445  }
446 
459  public function getRawVal( $name, $default = null ) {
460  $name = strtr( $name, '.', '_' ); // See comment in self::getGPCVal()
461  if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
462  $val = $this->data[$name];
463  } else {
464  $val = $default;
465  }
466  if ( is_null( $val ) ) {
467  return $val;
468  } else {
469  return (string)$val;
470  }
471  }
472 
483  public function getVal( $name, $default = null ) {
484  $val = $this->getGPCVal( $this->data, $name, $default );
485  if ( is_array( $val ) ) {
486  $val = $default;
487  }
488  if ( is_null( $val ) ) {
489  return $val;
490  } else {
491  return (string)$val;
492  }
493  }
494 
502  public function setVal( $key, $value ) {
503  $ret = $this->data[$key] ?? null;
504  $this->data[$key] = $value;
505  return $ret;
506  }
507 
514  public function unsetVal( $key ) {
515  if ( !isset( $this->data[$key] ) ) {
516  $ret = null;
517  } else {
518  $ret = $this->data[$key];
519  unset( $this->data[$key] );
520  }
521  return $ret;
522  }
523 
533  public function getArray( $name, $default = null ) {
534  $val = $this->getGPCVal( $this->data, $name, $default );
535  if ( is_null( $val ) ) {
536  return null;
537  } else {
538  return (array)$val;
539  }
540  }
541 
552  public function getIntArray( $name, $default = null ) {
553  $val = $this->getArray( $name, $default );
554  if ( is_array( $val ) ) {
555  $val = array_map( 'intval', $val );
556  }
557  return $val;
558  }
559 
569  public function getInt( $name, $default = 0 ) {
570  return intval( $this->getRawVal( $name, $default ) );
571  }
572 
581  public function getIntOrNull( $name ) {
582  $val = $this->getRawVal( $name );
583  return is_numeric( $val )
584  ? intval( $val )
585  : null;
586  }
587 
598  public function getFloat( $name, $default = 0.0 ) {
599  return floatval( $this->getRawVal( $name, $default ) );
600  }
601 
611  public function getBool( $name, $default = false ) {
612  return (bool)$this->getRawVal( $name, $default );
613  }
614 
624  public function getFuzzyBool( $name, $default = false ) {
625  return $this->getBool( $name, $default )
626  && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
627  }
628 
637  public function getCheck( $name ) {
638  # Checkboxes and buttons are only present when clicked
639  # Presence connotes truth, absence false
640  return $this->getRawVal( $name, null ) !== null;
641  }
642 
653  public function getText( $name, $default = '' ) {
654  $val = $this->getVal( $name, $default );
655  return str_replace( "\r\n", "\n", $val );
656  }
657 
665  public function getValues() {
666  $names = func_get_args();
667  if ( count( $names ) == 0 ) {
668  $names = array_keys( $this->data );
669  }
670 
671  $retVal = [];
672  foreach ( $names as $name ) {
673  $value = $this->getGPCVal( $this->data, $name, null );
674  if ( !is_null( $value ) ) {
675  $retVal[$name] = $value;
676  }
677  }
678  return $retVal;
679  }
680 
687  public function getValueNames( $exclude = [] ) {
688  return array_diff( array_keys( $this->getValues() ), $exclude );
689  }
690 
698  public function getQueryValues() {
700  }
701 
711  public function getQueryValuesOnly() {
712  return $this->queryParams;
713  }
714 
723  public function getPostValues() {
724  return $_POST;
725  }
726 
734  public function getRawQueryString() {
735  return $_SERVER['QUERY_STRING'];
736  }
737 
744  public function getRawPostString() {
745  if ( !$this->wasPosted() ) {
746  return '';
747  }
748  return $this->getRawInput();
749  }
750 
758  public function getRawInput() {
759  static $input = null;
760  if ( $input === null ) {
761  $input = file_get_contents( 'php://input' );
762  }
763  return $input;
764  }
765 
771  public function getMethod() {
772  return $_SERVER['REQUEST_METHOD'] ?? 'GET';
773  }
774 
784  public function wasPosted() {
785  return $this->getMethod() == 'POST';
786  }
787 
798  public function getSession() {
799  if ( $this->sessionId !== null ) {
800  $session = SessionManager::singleton()->getSessionById( (string)$this->sessionId, true, $this );
801  if ( $session ) {
802  return $session;
803  }
804  }
805 
806  $session = SessionManager::singleton()->getSessionForRequest( $this );
807  $this->sessionId = $session->getSessionId();
808  return $session;
809  }
810 
817  public function setSessionId( SessionId $sessionId ) {
818  $this->sessionId = $sessionId;
819  }
820 
827  public function getSessionId() {
828  return $this->sessionId;
829  }
830 
839  public function getCookie( $key, $prefix = null, $default = null ) {
840  if ( $prefix === null ) {
841  global $wgCookiePrefix;
842  $prefix = $wgCookiePrefix;
843  }
844  $name = $prefix . $key;
845  // Work around mangling of $_COOKIE
846  $name = strtr( $name, '.', '_' );
847  if ( isset( $_COOKIE[$name] ) ) {
848  return $_COOKIE[$name];
849  } else {
850  return $default;
851  }
852  }
853 
862  public function getCrossSiteCookie( $key, $prefix = '', $default = null ) {
864  $name = $prefix . $key;
865  // Work around mangling of $_COOKIE
866  $name = strtr( $name, '.', '_' );
867  if ( isset( $_COOKIE[$name] ) ) {
868  return $_COOKIE[$name];
869  }
871  $legacyName = $prefix . "ss0-" . $key;
872  $legacyName = strtr( $legacyName, '.', '_' );
873  if ( isset( $_COOKIE[$legacyName] ) ) {
874  return $_COOKIE[$legacyName];
875  }
876  }
877  return $default;
878  }
879 
887  public static function getGlobalRequestURL() {
888  // This method is called on fatal errors; it should not depend on anything complex.
889 
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'] )
894  ) {
895  // Probably IIS; doesn't set REQUEST_URI
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'];
901  }
902  } else {
903  // This shouldn't happen!
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/" );
907  }
908  // User-agents should not send a fragment with the URI, but
909  // if they do, and the web server passes it on to us, we
910  // need to strip it or we get false-positive redirect loops
911  // or weird output URLs
912  $hash = strpos( $base, '#' );
913  if ( $hash !== false ) {
914  $base = substr( $base, 0, $hash );
915  }
916 
917  if ( $base[0] == '/' ) {
918  // More than one slash will look like it is protocol relative
919  return preg_replace( '!^/+!', '/', $base );
920  } else {
921  // We may get paths with a host prepended; strip it.
922  return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
923  }
924  }
925 
933  public function getRequestURL() {
934  return self::getGlobalRequestURL();
935  }
936 
947  public function getFullRequestURL() {
948  // Pass an explicit PROTO constant instead of PROTO_CURRENT so that we
949  // do not rely on state from the global $wgRequest object (which it would,
950  // via wfGetServerUrl/wfExpandUrl/$wgRequest->protocol).
951  if ( $this->getProtocol() === 'http' ) {
952  return wfGetServerUrl( PROTO_HTTP ) . $this->getRequestURL();
953  } else {
954  return wfGetServerUrl( PROTO_HTTPS ) . $this->getRequestURL();
955  }
956  }
957 
963  public function appendQueryValue( $key, $value ) {
964  return $this->appendQueryArray( [ $key => $value ] );
965  }
966 
973  public function appendQueryArray( $array ) {
974  $newquery = $this->getQueryValues();
975  unset( $newquery['title'] );
976  $newquery = array_merge( $newquery, $array );
977 
978  return wfArrayToCgi( $newquery );
979  }
980 
990  public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
991  global $wgUser;
992 
993  $limit = $this->getInt( 'limit', 0 );
994  if ( $limit < 0 ) {
995  $limit = 0;
996  }
997  if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
998  $limit = $wgUser->getIntOption( $optionname );
999  }
1000  if ( $limit <= 0 ) {
1001  $limit = $deflimit;
1002  }
1003  if ( $limit > 5000 ) {
1004  $limit = 5000; # We have *some* limits...
1005  }
1006 
1007  $offset = $this->getInt( 'offset', 0 );
1008  if ( $offset < 0 ) {
1009  $offset = 0;
1010  }
1011 
1012  return [ $limit, $offset ];
1013  }
1014 
1021  public function getFileTempname( $key ) {
1022  $file = new WebRequestUpload( $this, $key );
1023  return $file->getTempName();
1024  }
1025 
1032  public function getUploadError( $key ) {
1033  $file = new WebRequestUpload( $this, $key );
1034  return $file->getError();
1035  }
1036 
1048  public function getFileName( $key ) {
1049  $file = new WebRequestUpload( $this, $key );
1050  return $file->getName();
1051  }
1052 
1059  public function getUpload( $key ) {
1060  return new WebRequestUpload( $this, $key );
1061  }
1062 
1069  public function response() {
1070  /* Lazy initialization of response object for this request */
1071  if ( !is_object( $this->response ) ) {
1072  $class = ( $this instanceof FauxRequest ) ? FauxResponse::class : WebResponse::class;
1073  $this->response = new $class();
1074  }
1075  return $this->response;
1076  }
1077 
1081  protected function initHeaders() {
1082  if ( count( $this->headers ) ) {
1083  return;
1084  }
1085 
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;
1090  }
1091  } else {
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;
1098  }
1099  }
1100  }
1101  }
1102 
1108  public function getAllHeaders() {
1109  $this->initHeaders();
1110  return $this->headers;
1111  }
1112 
1125  public function getHeader( $name, $flags = 0 ) {
1126  $this->initHeaders();
1127  $name = strtoupper( $name );
1128  if ( !isset( $this->headers[$name] ) ) {
1129  return false;
1130  }
1131  $value = $this->headers[$name];
1132  if ( $flags & self::GETHEADER_LIST ) {
1133  $value = array_map( 'trim', explode( ',', $value ) );
1134  }
1135  return $value;
1136  }
1137 
1145  public function getSessionData( $key ) {
1146  return $this->getSession()->get( $key );
1147  }
1148 
1156  public function setSessionData( $key, $data ) {
1157  $this->getSession()->set( $key, $data );
1158  }
1159 
1170  public function checkUrlExtension( $extWhitelist = [] ) {
1171  $extWhitelist[] = 'php';
1172  if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
1173  if ( !$this->wasPosted() ) {
1174  $newUrl = IEUrlExtension::fixUrlForIE6(
1175  $this->getFullRequestURL(), $extWhitelist );
1176  if ( $newUrl !== false ) {
1177  $this->doSecurityRedirect( $newUrl );
1178  return false;
1179  }
1180  }
1181  throw new HttpError( 403,
1182  'Invalid file extension found in the path info or query string.' );
1183  }
1184  return true;
1185  }
1186 
1194  protected function doSecurityRedirect( $url ) {
1195  header( 'Location: ' . $url );
1196  header( 'Content-Type: text/html' );
1197  $encUrl = htmlspecialchars( $url );
1198  echo <<<HTML
1199 <!DOCTYPE html>
1200 <html>
1201 <head>
1202 <title>Security redirect</title>
1203 </head>
1204 <body>
1205 <h1>Security redirect</h1>
1206 <p>
1207 We can't serve non-HTML content from the URL you have requested, because
1208 Internet Explorer would interpret it as an incorrect and potentially dangerous
1209 content type.</p>
1210 <p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the
1211 URL you have requested, except that "&amp;*" is appended. This prevents Internet
1212 Explorer from seeing a bogus file extension.
1213 </p>
1214 </body>
1215 </html>
1216 HTML;
1217  echo "\n";
1218  return true;
1219  }
1220 
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 ) {
1235  return [];
1236  }
1237 
1238  // Return the language codes in lower case
1239  $acceptLang = strtolower( $acceptLang );
1240 
1241  // Break up string into pieces (languages and q factors)
1242  $lang_parse = null;
1243  preg_match_all(
1244  '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
1245  $acceptLang,
1246  $lang_parse
1247  );
1248 
1249  if ( !count( $lang_parse[1] ) ) {
1250  return [];
1251  }
1252 
1253  $langcodes = $lang_parse[1];
1254  $qvalues = $lang_parse[4];
1255  $indices = range( 0, count( $lang_parse[1] ) - 1 );
1256 
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] );
1263  }
1264  }
1265 
1266  // Sort list. First by $qvalues, then by order. Reorder $langcodes the same way
1267  array_multisort( $qvalues, SORT_DESC, SORT_NUMERIC, $indices, $langcodes );
1268 
1269  // Create a list like "en" => 0.8
1270  $langs = array_combine( $langcodes, $qvalues );
1271 
1272  return $langs;
1273  }
1274 
1283  protected function getRawIP() {
1284  if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
1285  return null;
1286  }
1287 
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." );
1291  } else {
1292  $ipchain = $_SERVER['REMOTE_ADDR'];
1293  }
1294 
1295  return IP::canonicalize( $ipchain );
1296  }
1297 
1307  public function getIP() {
1308  global $wgUsePrivateIPs;
1309 
1310  # Return cached result
1311  if ( $this->ip !== null ) {
1312  return $this->ip;
1313  }
1314 
1315  # collect the originating ips
1316  $ip = $this->getRawIP();
1317  if ( !$ip ) {
1318  throw new MWException( 'Unable to determine IP.' );
1319  }
1320 
1321  # Append XFF
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 );
1329 
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 )
1339  ) {
1340  break; // IP is not valid/trusted or does not point to anything
1341  }
1342  if (
1343  IP::isPublic( $ipchain[$i + 1] ) ||
1344  $wgUsePrivateIPs ||
1345  $proxyLookup->isConfiguredProxy( $curIP ) // T50919; treat IP as sane
1346  ) {
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'." );
1353  }
1354  $ip = $nextIP;
1355  // keep traversing the chain
1356  continue;
1357  }
1358  break;
1359  }
1360  }
1361 
1362  # Allow extensions to improve our guess
1363  Hooks::run( 'GetIP', [ &$ip ] );
1364 
1365  if ( !$ip ) {
1366  throw new MWException( "Unable to determine IP." );
1367  }
1368 
1369  wfDebug( "IP: $ip\n" );
1370  $this->ip = $ip;
1371  return $ip;
1372  }
1373 
1379  public function setIP( $ip ) {
1380  $this->ip = $ip;
1381  }
1382 
1395  public function hasSafeMethod() {
1396  if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
1397  return false; // CLI mode
1398  }
1399 
1400  return in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
1401  }
1402 
1421  public function isSafeRequest() {
1422  if ( $this->markedAsSafe && $this->wasPosted() ) {
1423  return true; // marked as a "safe" POST
1424  }
1425 
1426  return $this->hasSafeMethod();
1427  }
1428 
1439  public function markAsSafeRequest() {
1440  $this->markedAsSafe = true;
1441  }
1442 }
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:1081
WebRequest\$sessionId
SessionId null $sessionId
Session ID to use for this request.
Definition: WebRequest.php:108
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
WebRequest\getSessionData
getSessionData( $key)
Get data from the session.
Definition: WebRequest.php:1145
WebRequest\$headers
array $headers
Lazy-initialized request headers indexed by upper-case header name.
Definition: WebRequest.php:65
$wgActionPaths
$wgActionPaths
Definition: img_auth.php:48
WebRequest\getValueNames
getValueNames( $exclude=[])
Returns the names of all input values excluding those in $exclude.
Definition: WebRequest.php:687
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
IP\combineHostAndPort
static combineHostAndPort( $host, $port, $defaultPort=false)
Given a host name and a port, combine them into host/port string like you might find in a URL.
Definition: IP.php:302
WebRequest\$data
array $data
The parameters from $_GET, $_POST and the path router.
Definition: WebRequest.php:47
WebRequest\getSessionId
getSessionId()
Get the session id for this request, if any.
Definition: WebRequest.php:827
$wgScript
$wgScript
The URL path to index.php.
Definition: DefaultSettings.php:206
WebRequest\$queryParams
$queryParams
The parameters from $_GET only.
Definition: WebRequest.php:59
WebRequest\appendQueryValue
appendQueryValue( $key, $value)
Definition: WebRequest.php:963
WebRequest\interpolateTitle
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
Definition: WebRequest.php:352
WebRequest\setSessionId
setSessionId(SessionId $sessionId)
Set the session for this request.
Definition: WebRequest.php:817
WebRequest\getElapsedTime
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
Definition: WebRequest.php:291
WebRequest\getIntOrNull
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
Definition: WebRequest.php:581
IP
A collection of public static functions to play with IP address and IP ranges.
Definition: IP.php:67
$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:744
WebRequest\detectProtocol
static detectProtocol()
Detect the protocol from $_SERVER.
Definition: WebRequest.php:274
WebRequest\getGPCVal
getGPCVal( $arr, $name, $default)
Fetch a value from the given array or return $default if it's not set.
Definition: WebRequest.php:421
$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:6115
$base
$base
Definition: generateLocalAutoload.php:11
HttpError
Show an error that looks like an HTTP server error.
Definition: HttpError.php:30
WebRequest\__construct
__construct()
Definition: WebRequest.php:116
WebRequest\getRawQueryString
getRawQueryString()
Return the contents of the Query with no decoding.
Definition: WebRequest.php:734
WebRequest\appendQueryArray
appendQueryArray( $array)
Appends or replaces value of query variables.
Definition: WebRequest.php:973
$wgAllowExternalReqID
$wgAllowExternalReqID
Whether to respect/honour the request ID provided by the incoming request via the X-Request-Id header...
Definition: DefaultSettings.php:8500
PathRouter\getActionPaths
static getActionPaths(array $actionPaths, $articlePath)
Definition: PathRouter.php:411
WebRequest\getFileTempname
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
Definition: WebRequest.php:1021
$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:89
WebRequest\getText
getText( $name, $default='')
Fetch a text string from the given array or return $default if it's not set.
Definition: WebRequest.php:653
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:771
MWException
MediaWiki exception.
Definition: MWException.php:26
WebRequest\getFileName
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
Definition: WebRequest.php:1048
WebRequest\getQueryValuesOnly
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
Definition: WebRequest.php:711
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:502
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:758
WebRequest\getUpload
getUpload( $key)
Return a WebRequestUpload object corresponding to the key.
Definition: WebRequest.php:1059
WebRequest\getValues
getValues()
Extracts the given named values into an array.
Definition: WebRequest.php:665
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:141
WebRequest\getFullRequestURL
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and query string.
Definition: WebRequest.php:947
WebRequest\getArray
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
Definition: WebRequest.php:533
WebRequest\$response
WebResponse $response
Lazy-init response object.
Definition: WebRequest.php:83
WebRequest\getAllHeaders
getAllHeaders()
Get an array containing all request headers.
Definition: WebRequest.php:1108
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:400
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:459
IEUrlExtension\areServerVarsBad
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...
Definition: IEUrlExtension.php:62
WebRequest\getCheck
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
Definition: WebRequest.php:637
WebRequest\getProtocol
getProtocol()
Get the current URL protocol (http or https)
Definition: WebRequest.php:338
WebRequest\getSession
getSession()
Return the session for this request.
Definition: WebRequest.php:798
WebRequest\response
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
Definition: WebRequest.php:1069
IP\splitHostAndPort
static splitHostAndPort( $both)
Given a host/port string, like one might find in the host part of a URL per RFC 2732,...
Definition: IP.php:253
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
WebRequest\getCrossSiteCookie
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
Definition: WebRequest.php:862
WebRequest\getLimitOffset
getLimitOffset( $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
Definition: WebRequest.php:990
WebRequest\checkUrlExtension
checkUrlExtension( $extWhitelist=[])
Check if Internet Explorer will detect an incorrect cache extension in PATH_INFO or QUERY_STRING.
Definition: WebRequest.php:1170
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:552
WebRequest\$requestTime
float $requestTime
The timestamp of the start of the request, with microsecond precision.
Definition: WebRequest.php:95
IEUrlExtension\fixUrlForIE6
static fixUrlForIE6( $url, $extWhitelist=[])
Returns a variant of $url which will pass isUrlExtensionBad() but has the same GET parameters,...
Definition: IEUrlExtension.php:140
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:431
WebRequest\$markedAsSafe
bool $markedAsSafe
Whether this HTTP request is "safe" (even if it is an HTTP post)
Definition: WebRequest.php:111
WebRequest\getCookie
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
Definition: WebRequest.php:839
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
WebRequest\getUploadError
getUploadError( $key)
Return the upload error or 0.
Definition: WebRequest.php:1032
WebRequest\$queryAndPathParams
array $queryAndPathParams
The parameters from $_GET.
Definition: WebRequest.php:54
$wgArticlePath
$wgArticlePath
Definition: img_auth.php:47
WebRequest\setSessionData
setSessionData( $key, $data)
Set session data.
Definition: WebRequest.php:1156
WebRequest\doSecurityRedirect
doSecurityRedirect( $url)
Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in IE 6.
Definition: WebRequest.php:1194
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:483
WebRequest\getInt
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
Definition: WebRequest.php:569
MediaWiki\Session\SessionId
Value object holding the session ID in a manner that can be globally updated.
Definition: SessionId.php:38
WebRequest\getRequestId
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:303
$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:598
WebRequest\detectServer
static detectServer()
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
Definition: WebRequest.php:228
WebRequest\getHeader
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
Definition: WebRequest.php:1125
WebRequest\getGlobalRequestURL
static getGlobalRequestURL()
Return the path and query string portion of the main request URI.
Definition: WebRequest.php:887
WebRequest\$ip
string $ip
Cached client IP address.
Definition: WebRequest.php:89
WebRequest\getPostValues
getPostValues()
Get the values passed via POST.
Definition: WebRequest.php:723
WebRequest\wasPosted
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
Definition: WebRequest.php:784
WebRequest\unsetVal
unsetVal( $key)
Unset an arbitrary value from our get/post data.
Definition: WebRequest.php:514
wfGetServerUrl
wfGetServerUrl( $proto)
Get the wiki's "server", i.e.
Definition: GlobalFunctions.php:569
WebRequest\getRequestURL
getRequestURL()
Return the path and query string portion of the request URI.
Definition: WebRequest.php:933
WebRequest\overrideRequestId
static overrideRequestId( $id)
Override the unique request ID.
Definition: WebRequest.php:330
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:374
WebResponse
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Definition: WebResponse.php:30
WebRequest\getQueryValues
getQueryValues()
Get the values passed in the query string and the path router parameters.
Definition: WebRequest.php:698
WebRequest\getFuzzyBool
getFuzzyBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
Definition: WebRequest.php:624
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
$wgVariantArticlePath
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
Definition: DefaultSettings.php:3183
PathRouter
PathRouter class.
Definition: PathRouter.php:73
$wgCookiePrefix
$wgCookiePrefix
Cookies generated by MediaWiki have names starting with this prefix.
Definition: DefaultSettings.php:6086
Language
Internationalisation code.
Definition: Language.php:37
WebRequest\getBool
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
Definition: WebRequest.php:611
$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:177
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:347
wfRandomString
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
Definition: GlobalFunctions.php:274