MediaWiki REL1_34
WebRequest.php
Go to the documentation of this file.
1<?php
30use 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
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
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
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() {
699 return $this->queryAndPathParams;
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() ) {
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>
1207We can't serve non-HTML content from the URL you have requested, because
1208Internet Explorer would interpret it as an incorrect and potentially dangerous
1209content type.</p>
1210<p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the
1211URL you have requested, except that "&amp;*" is appended. This prevents Internet
1212Explorer from seeing a bogus file extension.
1213</p>
1214</body>
1215</html>
1216HTML;
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}
$wgUsePathInfo
Whether to support URLs like index.php/Page_title These often break when PHP is set up in CGI mode.
$wgScript
The URL path to index.php.
bool $wgUseSameSiteLegacyCookies
If true, when a cross-site cookie with SameSite=None is sent, a legacy cookie with an "ss0" prefix wi...
$wgAllowExternalReqID
Whether to respect/honour the request ID provided by the incoming request via the X-Request-Id header...
bool $wgAssumeProxiesUseDefaultProtocolPorts
When the wiki is running behind a proxy and this is set to true, assumes that the proxy exposes the w...
$wgVariantArticlePath
Like $wgArticlePath, but on multi-variant wikis, this provides a path format that describes which par...
$wgCookiePrefix
Cookies generated by MediaWiki have names starting with this prefix.
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfGetServerUrl( $proto)
Get the wiki's "server", i.e.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
WebRequest clone which takes values from a provided array.
Show an error that looks like an HTTP server error.
Definition HttpError.php:30
static areServerVarsBad( $vars, $extWhitelist=[])
Check a subset of $_SERVER (or the whole of $_SERVER if you like) to see if it indicates that the req...
static fixUrlForIE6( $url, $extWhitelist=[])
Returns a variant of $url which will pass isUrlExtensionBad() but has the same GET parameters,...
A collection of public static functions to play with IP address and IP ranges.
Definition IP.php:67
Internationalisation code.
Definition Language.php:37
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Value object holding the session ID in a manner that can be globally updated.
Definition SessionId.php:38
This serves as the entry point to the MediaWiki session handling system.
Manages data for an an authenticated session.
Definition Session.php:48
PathRouter class.
static getActionPaths(array $actionPaths, $articlePath)
add( $path, $params=[], $options=[])
Add a new path pattern to the path router.
Object to access the $_FILES array.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
array $headers
Lazy-initialized request headers indexed by upper-case header name.
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
getValueNames( $exclude=[])
Returns the names of all input values excluding those in $exclude.
bool $markedAsSafe
Whether this HTTP request is "safe" (even if it is an HTTP post)
getUpload( $key)
Return a WebRequestUpload object corresponding to the key.
string $protocol
Cached URL protocol.
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
getPostValues()
Get the values passed via POST.
static detectProtocol()
Detect the protocol from $_SERVER.
getSession()
Return the session for this request.
getRawInput()
Return the raw request body, with no processing.
getRawQueryString()
Return the contents of the Query with no decoding.
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
getVal( $name, $default=null)
Fetch a scalar from the input or return $default if it's not set.
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
WebResponse $response
Lazy-init response object.
getUploadError( $key)
Return the upload error or 0.
getAllHeaders()
Get an array containing all request headers.
getFuzzyBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
getGPCVal( $arr, $name, $default)
Fetch a value from the given array or return $default if it's not set.
static getRequestId()
Get the unique request ID.
getProtocol()
Get the current URL protocol (http or https)
getMethod()
Get the HTTP method used for this request.
initHeaders()
Initialise the header list.
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
string $ip
Cached client IP address.
static getGlobalRequestURL()
Return the path and query string portion of the main request URI.
setVal( $key, $value)
Set an arbitrary value into our get/post data.
static string $reqId
The unique request ID.
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and query string.
checkUrlExtension( $extWhitelist=[])
Check if Internet Explorer will detect an incorrect cache extension in PATH_INFO or QUERY_STRING.
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
float $requestTime
The timestamp of the start of the request, with microsecond precision.
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
appendQueryArray( $array)
Appends or replaces value of query variables.
static detectServer()
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
getSessionId()
Get the session id for this request, if any.
getRawPostString()
Return the contents of the POST with no decoding.
getQueryValues()
Get the values passed in the query string and the path router parameters.
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
$queryParams
The parameters from $_GET only.
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
setSessionData( $key, $data)
Set session data.
doSecurityRedirect( $url)
Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in IE 6.
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
getRawVal( $name, $default=null)
Fetch a scalar from the input without normalization, or return $default if it's not set.
getIntArray( $name, $default=null)
Fetch an array of integers, or return $default if it's not set.
appendQueryValue( $key, $value)
normalizeUnicode( $data)
Recursively normalizes UTF-8 strings in the given array.
static overrideRequestId( $id)
Override the unique request ID.
unsetVal( $key)
Unset an arbitrary value from our get/post data.
static getPathInfo( $want='all')
Extract relevant query arguments from the http request uri's path to be merged with the normal php pr...
SessionId null $sessionId
Session ID to use for this request.
array $queryAndPathParams
The parameters from $_GET.
setSessionId(SessionId $sessionId)
Set the session for this request.
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
array $data
The parameters from $_GET, $_POST and the path router.
getLimitOffset( $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
static extractTitle( $path, $bases, $key=false)
URL rewriting function; tries to extract page title and, optionally, one other fixed parameter value ...
getText( $name, $default='')
Fetch a text string from the given array or return $default if it's not set.
getRequestURL()
Return the path and query string portion of the request URI.
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
getSessionData( $key)
Get data from the session.
getValues()
Extracts the given named values into an array.
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
const PROTO_HTTPS
Definition Defines.php:209
const PROTO_HTTP
Definition Defines.php:208
$wgActionPaths
Definition img_auth.php:48
$wgArticlePath
Definition img_auth.php:47
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42