MediaWiki REL1_37
WebRequest.php
Go to the documentation of this file.
1<?php
31use Wikimedia\IPUtils;
32
33// The point of this class is to be a wrapper around super globals
34// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
35
48 protected $data;
49
56
61 protected $queryParams;
62
67 protected $headers = [];
68
73 public const GETHEADER_LIST = 1;
74
79 private static $reqId;
80
85 private $response;
86
91 private $ip;
92
97 protected $requestTime;
98
103 protected $protocol;
104
113 protected $sessionId = null;
114
116 protected $markedAsSafe = false;
117
121 public function __construct() {
122 $this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
123
124 // POST overrides GET data
125 // We don't use $_REQUEST here to avoid interference from cookies...
126 $this->data = $_POST + $_GET;
127
128 $this->queryAndPathParams = $this->queryParams = $_GET;
129 }
130
151 protected static function getPathInfo( $want = 'all' ) {
152 // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
153 // And also by Apache 2.x, double slashes are converted to single slashes.
154 // So we will use REQUEST_URI if possible.
155 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
156 // Slurp out the path portion to examine...
157 $url = $_SERVER['REQUEST_URI'];
158 if ( !preg_match( '!^https?://!', $url ) ) {
159 $url = 'http://unused' . $url;
160 }
161 $a = parse_url( $url );
162 if ( !$a ) {
163 return [];
164 }
165 $path = $a['path'] ?? '';
166
167 global $wgScript;
168 if ( $path == $wgScript && $want !== 'all' ) {
169 // Script inside a rewrite path?
170 // Abort to keep from breaking...
171 return [];
172 }
173
174 $router = new PathRouter;
175
176 // Raw PATH_INFO style
177 $router->add( "$wgScript/$1" );
178
179 global $wgArticlePath;
180 if ( $wgArticlePath ) {
181 $router->validateRoute( $wgArticlePath, 'wgArticlePath' );
182 $router->add( $wgArticlePath );
183 }
184
185 global $wgActionPaths;
186 $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
187 if ( $articlePaths ) {
188 $router->add( $articlePaths, [ 'action' => '$key' ] );
189 }
190
192 if ( $wgVariantArticlePath ) {
193 $services = MediaWikiServices::getInstance();
194 $router->validateRoute( $wgVariantArticlePath, 'wgVariantArticlePath' );
195 $router->add( $wgVariantArticlePath,
196 [ 'variant' => '$2' ],
197 [ '$2' => $services->getLanguageConverterFactory()
198 ->getLanguageConverter( $services->getContentLanguage() )
199 ->getVariants() ]
200 );
201 }
202
203 Hooks::runner()->onWebRequestPathInfoRouter( $router );
204
205 $matches = $router->parse( $path );
206 } else {
207 global $wgUsePathInfo;
208 $matches = [];
209 if ( $wgUsePathInfo ) {
210 if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
211 // Mangled PATH_INFO
212 // https://bugs.php.net/bug.php?id=31892
213 // Also reported when ini_get('cgi.fix_pathinfo')==false
214 $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
215 } elseif ( !empty( $_SERVER['PATH_INFO'] ) ) {
216 // Regular old PATH_INFO yay
217 $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
218 }
219 }
220 }
221
222 return $matches;
223 }
224
236 public static function getRequestPathSuffix( $basePath ) {
237 $basePath = rtrim( $basePath, '/' ) . '/';
238 $requestUrl = self::getGlobalRequestURL();
239 $qpos = strpos( $requestUrl, '?' );
240 if ( $qpos !== false ) {
241 $requestPath = substr( $requestUrl, 0, $qpos );
242 } else {
243 $requestPath = $requestUrl;
244 }
245 if ( substr( $requestPath, 0, strlen( $basePath ) ) !== $basePath ) {
246 return false;
247 }
248 return rawurldecode( substr( $requestPath, strlen( $basePath ) ) );
249 }
250
257 public static function detectServer() {
259
260 $proto = self::detectProtocol();
261 $stdPort = $proto === 'https' ? 443 : 80;
262
263 $varNames = [ 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' ];
264 $host = 'localhost';
265 $port = $stdPort;
266 foreach ( $varNames as $varName ) {
267 if ( !isset( $_SERVER[$varName] ) ) {
268 continue;
269 }
270
271 $parts = IPUtils::splitHostAndPort( $_SERVER[$varName] );
272 if ( !$parts ) {
273 // Invalid, do not use
274 continue;
275 }
276
277 $host = $parts[0];
278 if ( $wgAssumeProxiesUseDefaultProtocolPorts && isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
279 // T72021: Assume that upstream proxy is running on the default
280 // port based on the protocol. We have no reliable way to determine
281 // the actual port in use upstream.
282 $port = $stdPort;
283 } elseif ( $parts[1] === false ) {
284 if ( isset( $_SERVER['SERVER_PORT'] ) ) {
285 $port = $_SERVER['SERVER_PORT'];
286 } // else leave it as $stdPort
287 } else {
288 $port = $parts[1];
289 }
290 break;
291 }
292
293 return $proto . '://' . IPUtils::combineHostAndPort( $host, $port, $stdPort );
294 }
295
303 public static function detectProtocol() {
304 if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
305 ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
306 $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
307 return 'https';
308 } else {
309 return 'http';
310 }
311 }
312
320 public function getElapsedTime() {
321 return microtime( true ) - $this->requestTime;
322 }
323
333 public static function getRequestId() {
334 // This method is called from various error handlers and MUST be kept simple and stateless.
335 if ( !self::$reqId ) {
337 if ( $wgAllowExternalReqID ) {
338 $id = $_SERVER['HTTP_X_REQUEST_ID'] ?? $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
339 } else {
340 $id = $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
341 }
342 self::$reqId = $id;
343 }
344
345 return self::$reqId;
346 }
347
355 public static function overrideRequestId( $id ) {
356 self::$reqId = $id;
357 }
358
363 public function getProtocol() {
364 if ( $this->protocol === null ) {
365 $this->protocol = self::detectProtocol();
366 }
367 return $this->protocol;
368 }
369
377 public function interpolateTitle() {
378 $matches = self::getPathInfo( 'title' );
379 foreach ( $matches as $key => $val ) {
380 $this->data[$key] = $this->queryAndPathParams[$key] = $val;
381 }
382 }
383
394 public static function extractTitle( $path, $bases, $key = false ) {
395 foreach ( (array)$bases as $keyValue => $base ) {
396 // Find the part after $wgArticlePath
397 $base = str_replace( '$1', '', $base );
398 $baseLen = strlen( $base );
399 if ( substr( $path, 0, $baseLen ) == $base ) {
400 $raw = substr( $path, $baseLen );
401 if ( $raw !== '' ) {
402 $matches = [ 'title' => rawurldecode( $raw ) ];
403 if ( $key ) {
404 $matches[$key] = $keyValue;
405 }
406 return $matches;
407 }
408 }
409 }
410 return [];
411 }
412
420 public function normalizeUnicode( $data ) {
421 if ( is_array( $data ) ) {
422 foreach ( $data as $key => $val ) {
423 $data[$key] = $this->normalizeUnicode( $val );
424 }
425 } else {
426 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
427 $data = $contLang->normalize( $data );
428 }
429 return $data;
430 }
431
440 private function getGPCVal( $arr, $name, $default ) {
441 # PHP is so nice to not touch input data, except sometimes:
442 # https://www.php.net/variables.external#language.variables.external.dot-in-names
443 # Work around PHP *feature* to avoid *bugs* elsewhere.
444 $name = strtr( $name, '.', '_' );
445
446 if ( !isset( $arr[$name] ) ) {
447 return $default;
448 }
449
450 $data = $arr[$name];
451 # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
452 $isAsciiStr = ( is_string( $data ) && preg_match( '/[^\x20-\x7E]/', $data ) === 0 );
453 if ( !$isAsciiStr ) {
454 if ( isset( $_GET[$name] ) && is_string( $data ) ) {
455 # Check for alternate/legacy character encoding.
456 $data = MediaWikiServices::getInstance()
457 ->getContentLanguage()
458 ->checkTitleEncoding( $data );
459 }
460 $data = $this->normalizeUnicode( $data );
461 }
462
463 return $data;
464 }
465
478 public function getRawVal( $name, $default = null ) {
479 $name = strtr( $name, '.', '_' ); // See comment in self::getGPCVal()
480 if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
481 $val = $this->data[$name];
482 } else {
483 $val = $default;
484 }
485
486 return $val === null ? null : (string)$val;
487 }
488
505 public function getVal( $name, $default = null ) {
506 $val = $this->getGPCVal( $this->data, $name, $default );
507 if ( is_array( $val ) ) {
508 $val = $default;
509 }
510
511 return $val === null ? null : (string)$val;
512 }
513
530 public function getText( $name, $default = '' ) {
531 $val = $this->getVal( $name, $default );
532 return str_replace( "\r\n", "\n", $val );
533 }
534
542 public function setVal( $key, $value ) {
543 $ret = $this->data[$key] ?? null;
544 $this->data[$key] = $value;
545 return $ret;
546 }
547
554 public function unsetVal( $key ) {
555 if ( !isset( $this->data[$key] ) ) {
556 $ret = null;
557 } else {
558 $ret = $this->data[$key];
559 unset( $this->data[$key] );
560 }
561 return $ret;
562 }
563
573 public function getArray( $name, $default = null ) {
574 $val = $this->getGPCVal( $this->data, $name, $default );
575 if ( $val === null ) {
576 return null;
577 } else {
578 return (array)$val;
579 }
580 }
581
592 public function getIntArray( $name, $default = null ) {
593 $val = $this->getArray( $name, $default );
594 if ( is_array( $val ) ) {
595 $val = array_map( 'intval', $val );
596 }
597 return $val;
598 }
599
609 public function getInt( $name, $default = 0 ) {
610 return intval( $this->getRawVal( $name, $default ) );
611 }
612
621 public function getIntOrNull( $name ) {
622 $val = $this->getRawVal( $name );
623 return is_numeric( $val )
624 ? intval( $val )
625 : null;
626 }
627
638 public function getFloat( $name, $default = 0.0 ) {
639 return floatval( $this->getRawVal( $name, $default ) );
640 }
641
651 public function getBool( $name, $default = false ) {
652 return (bool)$this->getRawVal( $name, $default );
653 }
654
664 public function getFuzzyBool( $name, $default = false ) {
665 return $this->getBool( $name, $default )
666 && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
667 }
668
677 public function getCheck( $name ) {
678 # Checkboxes and buttons are only present when clicked
679 # Presence connotes truth, absence false
680 return $this->getRawVal( $name, null ) !== null;
681 }
682
690 public function getValues( ...$names ) {
691 if ( $names === [] ) {
692 $names = array_keys( $this->data );
693 }
694
695 $retVal = [];
696 foreach ( $names as $name ) {
697 $value = $this->getGPCVal( $this->data, $name, null );
698 if ( $value !== null ) {
699 $retVal[$name] = $value;
700 }
701 }
702 return $retVal;
703 }
704
711 public function getValueNames( $exclude = [] ) {
712 return array_diff( array_keys( $this->getValues() ), $exclude );
713 }
714
722 public function getQueryValues() {
723 return $this->queryAndPathParams;
724 }
725
735 public function getQueryValuesOnly() {
736 return $this->queryParams;
737 }
738
747 public function getPostValues() {
748 return $_POST;
749 }
750
758 public function getRawQueryString() {
759 return $_SERVER['QUERY_STRING'];
760 }
761
768 public function getRawPostString() {
769 if ( !$this->wasPosted() ) {
770 return '';
771 }
772 return $this->getRawInput();
773 }
774
782 public function getRawInput() {
783 static $input = null;
784 if ( $input === null ) {
785 $input = file_get_contents( 'php://input' );
786 }
787 return $input;
788 }
789
795 public function getMethod() {
796 return $_SERVER['REQUEST_METHOD'] ?? 'GET';
797 }
798
808 public function wasPosted() {
809 return $this->getMethod() == 'POST';
810 }
811
822 public function getSession() {
823 if ( $this->sessionId !== null ) {
824 $session = SessionManager::singleton()->getSessionById( (string)$this->sessionId, true, $this );
825 if ( $session ) {
826 return $session;
827 }
828 }
829
830 $session = SessionManager::singleton()->getSessionForRequest( $this );
831 $this->sessionId = $session->getSessionId();
832 return $session;
833 }
834
841 public function setSessionId( SessionId $sessionId ) {
842 $this->sessionId = $sessionId;
843 }
844
851 public function getSessionId() {
852 return $this->sessionId;
853 }
854
863 public function getCookie( $key, $prefix = null, $default = null ) {
864 if ( $prefix === null ) {
865 global $wgCookiePrefix;
866 $prefix = $wgCookiePrefix;
867 }
868 $name = $prefix . $key;
869 // Work around mangling of $_COOKIE
870 $name = strtr( $name, '.', '_' );
871 if ( isset( $_COOKIE[$name] ) ) {
872 return $_COOKIE[$name];
873 } else {
874 return $default;
875 }
876 }
877
886 public function getCrossSiteCookie( $key, $prefix = '', $default = null ) {
888 $name = $prefix . $key;
889 // Work around mangling of $_COOKIE
890 $name = strtr( $name, '.', '_' );
891 if ( isset( $_COOKIE[$name] ) ) {
892 return $_COOKIE[$name];
893 }
895 $legacyName = $prefix . "ss0-" . $key;
896 $legacyName = strtr( $legacyName, '.', '_' );
897 if ( isset( $_COOKIE[$legacyName] ) ) {
898 return $_COOKIE[$legacyName];
899 }
900 }
901 return $default;
902 }
903
911 public static function getGlobalRequestURL() {
912 // This method is called on fatal errors; it should not depend on anything complex.
913
914 if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
915 $base = $_SERVER['REQUEST_URI'];
916 } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] )
917 && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] )
918 ) {
919 // Probably IIS; doesn't set REQUEST_URI
920 $base = $_SERVER['HTTP_X_ORIGINAL_URL'];
921 } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
922 $base = $_SERVER['SCRIPT_NAME'];
923 if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
924 $base .= '?' . $_SERVER['QUERY_STRING'];
925 }
926 } else {
927 // This shouldn't happen!
928 throw new MWException( "Web server doesn't provide either " .
929 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
930 "of your web server configuration to https://phabricator.wikimedia.org/" );
931 }
932 // User-agents should not send a fragment with the URI, but
933 // if they do, and the web server passes it on to us, we
934 // need to strip it or we get false-positive redirect loops
935 // or weird output URLs
936 $hash = strpos( $base, '#' );
937 if ( $hash !== false ) {
938 $base = substr( $base, 0, $hash );
939 }
940
941 if ( $base[0] == '/' ) {
942 // More than one slash will look like it is protocol relative
943 return preg_replace( '!^/+!', '/', $base );
944 } else {
945 // We may get paths with a host prepended; strip it.
946 return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
947 }
948 }
949
957 public function getRequestURL() {
958 return self::getGlobalRequestURL();
959 }
960
971 public function getFullRequestURL() {
972 // Pass an explicit PROTO constant instead of PROTO_CURRENT so that we
973 // do not rely on state from the global $wgRequest object (which it would,
974 // via wfGetServerUrl/wfExpandUrl/$wgRequest->protocol).
975 if ( $this->getProtocol() === 'http' ) {
976 return wfGetServerUrl( PROTO_HTTP ) . $this->getRequestURL();
977 } else {
978 return wfGetServerUrl( PROTO_HTTPS ) . $this->getRequestURL();
979 }
980 }
981
987 public function appendQueryValue( $key, $value ) {
988 return $this->appendQueryArray( [ $key => $value ] );
989 }
990
997 public function appendQueryArray( $array ) {
998 $newquery = $this->getQueryValues();
999 unset( $newquery['title'] );
1000 $newquery = array_merge( $newquery, $array );
1001
1002 return wfArrayToCgi( $newquery );
1003 }
1004
1015 public function getLimitOffsetForUser( UserIdentity $user, $deflimit = 50, $optionname = 'rclimit' ) {
1016 $limit = $this->getInt( 'limit', 0 );
1017 if ( $limit < 0 ) {
1018 $limit = 0;
1019 }
1020 if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
1021 $limit = MediaWikiServices::getInstance()
1022 ->getUserOptionsLookup()
1023 ->getIntOption( $user, $optionname );
1024 }
1025 if ( $limit <= 0 ) {
1026 $limit = $deflimit;
1027 }
1028 if ( $limit > 5000 ) {
1029 $limit = 5000; # We have *some* limits...
1030 }
1031
1032 $offset = $this->getInt( 'offset', 0 );
1033 if ( $offset < 0 ) {
1034 $offset = 0;
1035 }
1036
1037 return [ $limit, $offset ];
1038 }
1039
1046 public function getFileTempname( $key ) {
1047 return $this->getUpload( $key )->getTempName();
1048 }
1049
1056 public function getUploadError( $key ) {
1057 return $this->getUpload( $key )->getError();
1058 }
1059
1071 public function getFileName( $key ) {
1072 return $this->getUpload( $key )->getName();
1073 }
1074
1081 public function getUpload( $key ) {
1082 return new WebRequestUpload( $this, $key );
1083 }
1084
1091 public function response() {
1092 /* Lazy initialization of response object for this request */
1093 if ( !is_object( $this->response ) ) {
1094 $class = ( $this instanceof FauxRequest ) ? FauxResponse::class : WebResponse::class;
1095 $this->response = new $class();
1096 }
1097 return $this->response;
1098 }
1099
1103 protected function initHeaders() {
1104 if ( count( $this->headers ) ) {
1105 return;
1106 }
1107
1108 $this->headers = array_change_key_case( getallheaders(), CASE_UPPER );
1109 }
1110
1116 public function getAllHeaders() {
1117 $this->initHeaders();
1118 return $this->headers;
1119 }
1120
1133 public function getHeader( $name, $flags = 0 ) {
1134 $this->initHeaders();
1135 $name = strtoupper( $name );
1136 if ( !isset( $this->headers[$name] ) ) {
1137 return false;
1138 }
1139 $value = $this->headers[$name];
1140 if ( $flags & self::GETHEADER_LIST ) {
1141 $value = array_map( 'trim', explode( ',', $value ) );
1142 }
1143 return $value;
1144 }
1145
1153 public function getSessionData( $key ) {
1154 return $this->getSession()->get( $key );
1155 }
1156
1162 public function setSessionData( $key, $data ) {
1163 $this->getSession()->set( $key, $data );
1164 }
1165
1175 public function checkUrlExtension( $extList = [] ) {
1176 wfDeprecated( __METHOD__, '1.35' );
1177 return true;
1178 }
1179
1193 public function getAcceptLang() {
1194 // Modified version of code found at
1195 // http://www.thefutureoftheweb.com/blog/use-accept-language-header
1196 $acceptLang = $this->getHeader( 'Accept-Language' );
1197 if ( !$acceptLang ) {
1198 return [];
1199 }
1200
1201 // Return the language codes in lower case
1202 $acceptLang = strtolower( $acceptLang );
1203
1204 // Break up string into pieces (languages and q factors)
1205 if ( !preg_match_all(
1206 '/
1207 # a language code or a star is required
1208 ([a-z]{1,8}(?:-[a-z]{1,8})*|\*)
1209 # from here everything is optional
1210 \s*
1211 (?:
1212 # this accepts only numbers in the range ;q=0.000 to ;q=1.000
1213 ;\s*q\s*=\s*
1214 (1(?:\.0{0,3})?|0(?:\.\d{0,3})?)?
1215 )?
1216 /x',
1217 $acceptLang,
1218 $matches,
1219 PREG_SET_ORDER
1220 ) ) {
1221 return [];
1222 }
1223
1224 // Create a list like "en" => 0.8
1225 $langs = [];
1226 foreach ( $matches as $match ) {
1227 $languageCode = $match[1];
1228 // When not present, the default value is 1
1229 $qValue = (float)( $match[2] ?? 1.0 );
1230 if ( $qValue ) {
1231 $langs[$languageCode] = $qValue;
1232 }
1233 }
1234
1235 // Sort list by qValue
1236 arsort( $langs, SORT_NUMERIC );
1237 return $langs;
1238 }
1239
1248 protected function getRawIP() {
1249 if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
1250 return null;
1251 }
1252
1253 if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) {
1254 throw new MWException( __METHOD__
1255 . " : Could not determine the remote IP address due to multiple values." );
1256 } else {
1257 $ipchain = $_SERVER['REMOTE_ADDR'];
1258 }
1259
1260 return IPUtils::canonicalize( $ipchain );
1261 }
1262
1272 public function getIP() {
1273 global $wgUsePrivateIPs;
1274
1275 # Return cached result
1276 if ( $this->ip !== null ) {
1277 return $this->ip;
1278 }
1279
1280 # collect the originating ips
1281 $ip = $this->getRawIP();
1282 if ( !$ip ) {
1283 throw new MWException( 'Unable to determine IP.' );
1284 }
1285
1286 # Append XFF
1287 $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
1288 if ( $forwardedFor !== false ) {
1289 $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1290 $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
1291 $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
1292 $ipchain = array_reverse( $ipchain );
1293 array_unshift( $ipchain, $ip );
1294
1295 # Step through XFF list and find the last address in the list which is a
1296 # trusted server. Set $ip to the IP address given by that trusted server,
1297 # unless the address is not sensible (e.g. private). However, prefer private
1298 # IP addresses over proxy servers controlled by this site (more sensible).
1299 # Note that some XFF values might be "unknown" with Squid/Varnish.
1300 foreach ( $ipchain as $i => $curIP ) {
1301 $curIP = IPUtils::sanitizeIP(
1302 IPUtils::canonicalize(
1303 self::canonicalizeIPv6LoopbackAddress( $curIP )
1304 )
1305 );
1306 if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
1307 || !$proxyLookup->isTrustedProxy( $curIP )
1308 ) {
1309 break; // IP is not valid/trusted or does not point to anything
1310 }
1311 if (
1312 IPUtils::isPublic( $ipchain[$i + 1] ) ||
1314 $proxyLookup->isConfiguredProxy( $curIP ) // T50919; treat IP as sane
1315 ) {
1316 $nextIP = $ipchain[$i + 1];
1317
1318 // Follow the next IP according to the proxy
1319 $nextIP = IPUtils::canonicalize(
1320 self::canonicalizeIPv6LoopbackAddress( $nextIP )
1321 );
1322 if ( !$nextIP && $isConfigured ) {
1323 // We have not yet made it past CDN/proxy servers of this site,
1324 // so either they are misconfigured or there is some IP spoofing.
1325 throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
1326 }
1327 $ip = $nextIP;
1328
1329 // keep traversing the chain
1330 continue;
1331 }
1332 break;
1333 }
1334 }
1335
1336 # Allow extensions to improve our guess
1337 Hooks::runner()->onGetIP( $ip );
1338
1339 if ( !$ip ) {
1340 throw new MWException( "Unable to determine IP." );
1341 }
1342
1343 $this->ip = $ip;
1344 return $ip;
1345 }
1346
1355 public static function canonicalizeIPv6LoopbackAddress( $ip ) {
1356 // Code moved from IPUtils library. See T248237#6614927
1357 $m = [];
1358 if ( preg_match( '/^0*' . IPUtils::RE_IPV6_GAP . '1$/', $ip, $m ) ) {
1359 return '127.0.0.1';
1360 }
1361 return $ip;
1362 }
1363
1369 public function setIP( $ip ) {
1370 $this->ip = $ip;
1371 }
1372
1385 public function hasSafeMethod() {
1386 if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
1387 return false; // CLI mode
1388 }
1389
1390 return in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
1391 }
1392
1411 public function isSafeRequest() {
1412 if ( $this->markedAsSafe && $this->wasPosted() ) {
1413 return true; // marked as a "safe" POST
1414 }
1415
1416 return $this->hasSafeMethod();
1417 }
1418
1429 public function markAsSafeRequest() {
1430 $this->markedAsSafe = true;
1431 }
1432}
$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.
$wgActionPaths
To set 'pretty' URL paths for actions other than plain page views, add to this array.
bool $wgUseSameSiteLegacyCookies
If true, when a cross-site cookie with SameSite=None is sent, a legacy cookie with an "ss0" prefix wi...
$wgArticlePath
The URL path for primary article page views.
$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.
$wgUsePrivateIPs
Should forwarded Private IPs be accepted?
const PROTO_HTTPS
Definition Defines.php:193
const PROTO_HTTP
Definition Defines.php:192
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....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
WebRequest clone which takes values from a provided array.
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:40
This serves as the entry point to the MediaWiki session handling system.
Manages data for an authenticated session.
Definition Session.php:48
PathRouter class.
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...
markAsSafeRequest()
Mark this request as identified as being nullipotent even if it is a POST request.
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
string[] $queryParams
The parameters from $_GET only.
getLimitOffsetForUser(UserIdentity $user, $deflimit=50, $optionname='rclimit')
Check for limit and offset parameters on the input, and return sensible defaults if not given.
getValueNames( $exclude=[])
Returns the names of all input values excluding those in $exclude.
bool $markedAsSafe
Whether this HTTP request is "safe" (even if it is an HTTP post)
getUpload( $key)
Return a WebRequestUpload object corresponding to the key.
string $protocol
Cached URL protocol.
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
getPostValues()
Get the values passed via POST.
static detectProtocol()
Detect the protocol from $_SERVER.
isSafeRequest()
Whether this request should be identified as being "safe".
getSession()
Return the session for this request.
getRawInput()
Return the raw request body, with no processing.
checkUrlExtension( $extList=[])
This function formerly did a security check to prevent an XSS vulnerability in IE6,...
getValues(... $names)
Extracts the (given) named values into an array.
getRawQueryString()
Return the contents of the Query with no decoding.
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
getVal( $name, $default=null)
Fetch a text string and partially normalized it.
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
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 current request ID.
getProtocol()
Get the current URL protocol (http or https)
getMethod()
Get the HTTP method used for this request.
initHeaders()
Initialise the header list.
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
static getRequestPathSuffix( $basePath)
If the request URL matches a given base path, extract the path part of the request URL after that bas...
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.
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
float $requestTime
The timestamp of the start of the request, with microsecond precision.
string[] $headers
Lazy-initialized request headers indexed by upper-case header name.
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
appendQueryArray( $array)
Appends or replaces value of query variables.
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.
getAcceptLang()
Parse the Accept-Language header sent by the client into an array.
static canonicalizeIPv6LoopbackAddress( $ip)
Converts ::1 (IPv6 loopback address) to 127.0.0.1 (IPv4 loopback address); assists in matching truste...
getRawPostString()
Return the contents of the POST with no decoding.
getQueryValues()
Get the values passed in the query string and the path router parameters.
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
setSessionData( $key, $data)
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
hasSafeMethod()
Check if this request uses a "safe" HTTP method.
getRawVal( $name, $default=null)
Fetch a string WITHOUT any Unicode or line break normalization.
getIntArray( $name, $default=null)
Fetch an array of integers, or return $default if it's not set.
appendQueryValue( $key, $value)
normalizeUnicode( $data)
Recursively normalizes UTF-8 strings in the given array.
static overrideRequestId( $id)
Override the unique request ID.
unsetVal( $key)
Unset an arbitrary value from our get/post data.
static getPathInfo( $want='all')
Extract relevant query arguments from the http request uri's path to be merged with the normal php pr...
SessionId null $sessionId
Session ID to use for this request.
getRawIP()
Fetch the raw IP from the request.
setSessionId(SessionId $sessionId)
Set the session for this request.
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
array $data
The parameters from $_GET, $_POST and the path router.
static extractTitle( $path, $bases, $key=false)
URL rewriting function; tries to extract page title and, optionally, one other fixed parameter value ...
getText( $name, $default='')
Fetch a text string and return it in normalized form.
getRequestURL()
Return the path and query string portion of the request URI.
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
getSessionData( $key)
Get data from the session.
string[] $queryAndPathParams
The parameters from $_GET.
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
Interface for objects representing user identity.