MediaWiki REL1_40
WebRequest.php
Go to the documentation of this file.
1<?php
37use Wikimedia\IPUtils;
38
39// The point of this class is to be a wrapper around super globals
40// phpcs:disable MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
41
54 protected $data;
55
62
67 protected $queryParams;
68
73 protected $headers = [];
74
79 public const GETHEADER_LIST = 1;
80
85 private static $reqId;
86
91 private $response;
92
97 private $ip;
98
103 protected $requestTime;
104
109 protected $protocol;
110
119 protected $sessionId = null;
120
122 protected $markedAsSafe = false;
123
127 public function __construct() {
128 $this->requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
129
130 // POST overrides GET data
131 // We don't use $_REQUEST here to avoid interference from cookies...
132 $this->data = $_POST + $_GET;
133
134 $this->queryAndPathParams = $this->queryParams = $_GET;
135 }
136
157 protected static function getPathInfo( $want = 'all' ) {
158 // PATH_INFO is mangled due to https://bugs.php.net/bug.php?id=31892
159 // And also by Apache 2.x, double slashes are converted to single slashes.
160 // So we will use REQUEST_URI if possible.
161 if ( isset( $_SERVER['REQUEST_URI'] ) ) {
162 // Slurp out the path portion to examine...
163 $url = $_SERVER['REQUEST_URI'];
164 if ( !preg_match( '!^https?://!', $url ) ) {
165 $url = 'http://unused' . $url;
166 }
167 $a = parse_url( $url );
168 if ( !$a ) {
169 return [];
170 }
171 $path = $a['path'] ?? '';
172
173 global $wgScript;
174 if ( $path == $wgScript && $want !== 'all' ) {
175 // Script inside a rewrite path?
176 // Abort to keep from breaking...
177 return [];
178 }
179
180 $router = new PathRouter;
181
182 // Raw PATH_INFO style
183 $router->add( "$wgScript/$1" );
184
185 global $wgArticlePath;
186 if ( $wgArticlePath ) {
187 $router->validateRoute( $wgArticlePath, 'wgArticlePath' );
188 $router->add( $wgArticlePath );
189 }
190
191 global $wgActionPaths;
192 $articlePaths = PathRouter::getActionPaths( $wgActionPaths, $wgArticlePath );
193 if ( $articlePaths ) {
194 $router->add( $articlePaths, [ 'action' => '$key' ] );
195 }
196
198 if ( $wgVariantArticlePath ) {
199 $services = MediaWikiServices::getInstance();
200 $router->validateRoute( $wgVariantArticlePath, 'wgVariantArticlePath' );
201 $router->add( $wgVariantArticlePath,
202 [ 'variant' => '$2' ],
203 [ '$2' => $services->getLanguageConverterFactory()
204 ->getLanguageConverter( $services->getContentLanguage() )
205 ->getVariants() ]
206 );
207 }
208
209 Hooks::runner()->onWebRequestPathInfoRouter( $router );
210
211 $matches = $router->parse( $path );
212 } else {
213 global $wgUsePathInfo;
214 $matches = [];
215 if ( $wgUsePathInfo ) {
216 if ( !empty( $_SERVER['ORIG_PATH_INFO'] ) ) {
217 // Mangled PATH_INFO
218 // https://bugs.php.net/bug.php?id=31892
219 // Also reported when ini_get('cgi.fix_pathinfo')==false
220 $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
221 } elseif ( !empty( $_SERVER['PATH_INFO'] ) ) {
222 // Regular old PATH_INFO yay
223 $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
224 }
225 }
226 }
227
228 return $matches;
229 }
230
242 public static function getRequestPathSuffix( $basePath ) {
243 $basePath = rtrim( $basePath, '/' ) . '/';
244 $requestUrl = self::getGlobalRequestURL();
245 $qpos = strpos( $requestUrl, '?' );
246 if ( $qpos !== false ) {
247 $requestPath = substr( $requestUrl, 0, $qpos );
248 } else {
249 $requestPath = $requestUrl;
250 }
251 if ( !str_starts_with( $requestPath, $basePath ) ) {
252 return false;
253 }
254 return rawurldecode( substr( $requestPath, strlen( $basePath ) ) );
255 }
256
268 public static function detectServer( $assumeProxiesUseDefaultProtocolPorts = null ) {
269 $assumeProxiesUseDefaultProtocolPorts ??= $GLOBALS['wgAssumeProxiesUseDefaultProtocolPorts'];
270
271 $proto = self::detectProtocol();
272 $stdPort = $proto === 'https' ? 443 : 80;
273
274 $varNames = [ 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' ];
275 $host = 'localhost';
276 $port = $stdPort;
277 foreach ( $varNames as $varName ) {
278 if ( !isset( $_SERVER[$varName] ) ) {
279 continue;
280 }
281
282 $parts = IPUtils::splitHostAndPort( $_SERVER[$varName] );
283 if ( !$parts ) {
284 // Invalid, do not use
285 continue;
286 }
287
288 $host = $parts[0];
289 if ( $assumeProxiesUseDefaultProtocolPorts && isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) {
290 // T72021: Assume that upstream proxy is running on the default
291 // port based on the protocol. We have no reliable way to determine
292 // the actual port in use upstream.
293 $port = $stdPort;
294 } elseif ( $parts[1] === false ) {
295 if ( isset( $_SERVER['SERVER_PORT'] ) ) {
296 $port = $_SERVER['SERVER_PORT'];
297 } // else leave it as $stdPort
298 } else {
299 $port = $parts[1];
300 }
301 break;
302 }
303
304 return $proto . '://' . IPUtils::combineHostAndPort( $host, $port, $stdPort );
305 }
306
314 public static function detectProtocol() {
315 if ( ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ||
316 ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
317 $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) ) {
318 return 'https';
319 } else {
320 return 'http';
321 }
322 }
323
331 public function getElapsedTime() {
332 return microtime( true ) - $this->requestTime;
333 }
334
344 public static function getRequestId() {
345 // This method is called from various error handlers and MUST be kept simple and stateless.
346 if ( !self::$reqId ) {
348 if ( $wgAllowExternalReqID ) {
349 $id = $_SERVER['HTTP_X_REQUEST_ID'] ?? $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
350 } else {
351 $id = $_SERVER['UNIQUE_ID'] ?? wfRandomString( 24 );
352 }
353 self::$reqId = $id;
354 }
355
356 return self::$reqId;
357 }
358
366 public static function overrideRequestId( $id ) {
367 self::$reqId = $id;
368 }
369
374 public function getProtocol() {
375 $this->protocol ??= self::detectProtocol();
376 return $this->protocol;
377 }
378
386 public function interpolateTitle() {
387 $matches = self::getPathInfo( 'title' );
388 foreach ( $matches as $key => $val ) {
389 $this->data[$key] = $this->queryAndPathParams[$key] = $val;
390 }
391 }
392
403 public static function extractTitle( $path, $bases, $key = false ) {
404 foreach ( (array)$bases as $keyValue => $base ) {
405 // Find the part after $wgArticlePath
406 $base = str_replace( '$1', '', $base );
407 $baseLen = strlen( $base );
408 if ( substr( $path, 0, $baseLen ) == $base ) {
409 $raw = substr( $path, $baseLen );
410 if ( $raw !== '' ) {
411 $matches = [ 'title' => rawurldecode( $raw ) ];
412 if ( $key ) {
413 $matches[$key] = $keyValue;
414 }
415 return $matches;
416 }
417 }
418 }
419 return [];
420 }
421
429 public function normalizeUnicode( $data ) {
430 if ( is_array( $data ) ) {
431 foreach ( $data as $key => $val ) {
432 $data[$key] = $this->normalizeUnicode( $val );
433 }
434 } else {
435 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
436 $data = $contLang->normalize( $data );
437 }
438 return $data;
439 }
440
449 private function getGPCVal( $arr, $name, $default ) {
450 # PHP is so nice to not touch input data, except sometimes:
451 # https://www.php.net/variables.external#language.variables.external.dot-in-names
452 # Work around PHP *feature* to avoid *bugs* elsewhere.
453 $name = strtr( $name, '.', '_' );
454
455 if ( !isset( $arr[$name] ) ) {
456 return $default;
457 }
458
459 $data = $arr[$name];
460 # Optimisation: Skip UTF-8 normalization and legacy transcoding for simple ASCII strings.
461 $isAsciiStr = ( is_string( $data ) && preg_match( '/[^\x20-\x7E]/', $data ) === 0 );
462 if ( !$isAsciiStr ) {
463 if ( isset( $_GET[$name] ) && is_string( $data ) ) {
464 # Check for alternate/legacy character encoding.
465 $data = MediaWikiServices::getInstance()
466 ->getContentLanguage()
467 ->checkTitleEncoding( $data );
468 }
469 $data = $this->normalizeUnicode( $data );
470 }
471
472 return $data;
473 }
474
487 public function getRawVal( $name, $default = null ) {
488 $name = strtr( $name, '.', '_' ); // See comment in self::getGPCVal()
489 if ( isset( $this->data[$name] ) && !is_array( $this->data[$name] ) ) {
490 $val = $this->data[$name];
491 } else {
492 $val = $default;
493 }
494
495 return $val === null ? null : (string)$val;
496 }
497
514 public function getVal( $name, $default = null ) {
515 $val = $this->getGPCVal( $this->data, $name, $default );
516 if ( is_array( $val ) ) {
517 $val = $default;
518 }
519
520 return $val === null ? null : (string)$val;
521 }
522
539 public function getText( $name, $default = '' ) {
540 $val = $this->getVal( $name, $default );
541 return str_replace( "\r\n", "\n", $val );
542 }
543
551 public function setVal( $key, $value ) {
552 $ret = $this->data[$key] ?? null;
553 $this->data[$key] = $value;
554 return $ret;
555 }
556
563 public function unsetVal( $key ) {
564 if ( !isset( $this->data[$key] ) ) {
565 $ret = null;
566 } else {
567 $ret = $this->data[$key];
568 unset( $this->data[$key] );
569 }
570 return $ret;
571 }
572
582 public function getArray( $name, $default = null ) {
583 $val = $this->getGPCVal( $this->data, $name, $default );
584 if ( $val === null ) {
585 return null;
586 } else {
587 return (array)$val;
588 }
589 }
590
601 public function getIntArray( $name, $default = null ) {
602 $val = $this->getArray( $name, $default );
603 if ( is_array( $val ) ) {
604 $val = array_map( 'intval', $val );
605 }
606 return $val;
607 }
608
618 public function getInt( $name, $default = 0 ) {
619 // @phan-suppress-next-line PhanTypeMismatchArgument getRawVal does not return null here
620 return intval( $this->getRawVal( $name, $default ) );
621 }
622
631 public function getIntOrNull( $name ) {
632 $val = $this->getRawVal( $name );
633 return is_numeric( $val )
634 ? intval( $val )
635 : null;
636 }
637
648 public function getFloat( $name, $default = 0.0 ) {
649 // @phan-suppress-next-line PhanTypeMismatchArgument getRawVal does not return null here
650 return floatval( $this->getRawVal( $name, $default ) );
651 }
652
662 public function getBool( $name, $default = false ) {
663 // @phan-suppress-next-line PhanTypeMismatchArgument getRawVal does not return null here
664 return (bool)$this->getRawVal( $name, $default );
665 }
666
676 public function getFuzzyBool( $name, $default = false ) {
677 return $this->getBool( $name, $default )
678 && strcasecmp( $this->getRawVal( $name ), 'false' ) !== 0;
679 }
680
689 public function getCheck( $name ) {
690 # Checkboxes and buttons are only present when clicked
691 # Presence connotes truth, absence false
692 return $this->getRawVal( $name, null ) !== null;
693 }
694
702 public function getValues( ...$names ) {
703 if ( $names === [] ) {
704 $names = array_keys( $this->data );
705 }
706
707 $retVal = [];
708 foreach ( $names as $name ) {
709 $value = $this->getGPCVal( $this->data, $name, null );
710 if ( $value !== null ) {
711 $retVal[$name] = $value;
712 }
713 }
714 return $retVal;
715 }
716
723 public function getValueNames( $exclude = [] ) {
724 return array_diff( array_keys( $this->getValues() ), $exclude );
725 }
726
734 public function getQueryValues() {
735 return $this->queryAndPathParams;
736 }
737
747 public function getQueryValuesOnly() {
748 return $this->queryParams;
749 }
750
759 public function getPostValues() {
760 return $_POST;
761 }
762
770 public function getRawQueryString() {
771 return $_SERVER['QUERY_STRING'];
772 }
773
780 public function getRawPostString() {
781 if ( !$this->wasPosted() ) {
782 return '';
783 }
784 return $this->getRawInput();
785 }
786
794 public function getRawInput() {
795 static $input = null;
796 $input ??= file_get_contents( 'php://input' );
797 return $input;
798 }
799
805 public function getMethod() {
806 return $_SERVER['REQUEST_METHOD'] ?? 'GET';
807 }
808
818 public function wasPosted() {
819 return $this->getMethod() == 'POST';
820 }
821
832 public function getSession() {
833 if ( $this->sessionId !== null ) {
834 $session = SessionManager::singleton()->getSessionById( (string)$this->sessionId, true, $this );
835 if ( $session ) {
836 return $session;
837 }
838 }
839
840 $session = SessionManager::singleton()->getSessionForRequest( $this );
841 $this->sessionId = $session->getSessionId();
842 return $session;
843 }
844
851 public function setSessionId( SessionId $sessionId ) {
852 $this->sessionId = $sessionId;
853 }
854
861 public function getSessionId() {
862 return $this->sessionId;
863 }
864
873 public function getCookie( $key, $prefix = null, $default = null ) {
874 if ( $prefix === null ) {
875 global $wgCookiePrefix;
876 $prefix = $wgCookiePrefix;
877 }
878 $name = $prefix . $key;
879 // Work around mangling of $_COOKIE
880 $name = strtr( $name, '.', '_' );
881 if ( isset( $_COOKIE[$name] ) ) {
882 return $_COOKIE[$name];
883 } else {
884 return $default;
885 }
886 }
887
896 public function getCrossSiteCookie( $key, $prefix = '', $default = null ) {
898 $name = $prefix . $key;
899 // Work around mangling of $_COOKIE
900 $name = strtr( $name, '.', '_' );
901 if ( isset( $_COOKIE[$name] ) ) {
902 return $_COOKIE[$name];
903 }
905 $legacyName = $prefix . "ss0-" . $key;
906 $legacyName = strtr( $legacyName, '.', '_' );
907 if ( isset( $_COOKIE[$legacyName] ) ) {
908 return $_COOKIE[$legacyName];
909 }
910 }
911 return $default;
912 }
913
921 public static function getGlobalRequestURL() {
922 // This method is called on fatal errors; it should not depend on anything complex.
923
924 if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
925 $base = $_SERVER['REQUEST_URI'];
926 } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] )
927 && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] )
928 ) {
929 // Probably IIS; doesn't set REQUEST_URI
930 $base = $_SERVER['HTTP_X_ORIGINAL_URL'];
931 } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
932 $base = $_SERVER['SCRIPT_NAME'];
933 if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
934 $base .= '?' . $_SERVER['QUERY_STRING'];
935 }
936 } else {
937 // This shouldn't happen!
938 throw new MWException( "Web server doesn't provide either " .
939 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
940 "of your web server configuration to https://phabricator.wikimedia.org/" );
941 }
942 // User-agents should not send a fragment with the URI, but
943 // if they do, and the web server passes it on to us, we
944 // need to strip it or we get false-positive redirect loops
945 // or weird output URLs
946 $hash = strpos( $base, '#' );
947 if ( $hash !== false ) {
948 $base = substr( $base, 0, $hash );
949 }
950
951 if ( $base[0] == '/' ) {
952 // More than one slash will look like it is protocol relative
953 return preg_replace( '!^/+!', '/', $base );
954 } else {
955 // We may get paths with a host prepended; strip it.
956 return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
957 }
958 }
959
967 public function getRequestURL() {
968 return self::getGlobalRequestURL();
969 }
970
981 public function getFullRequestURL() {
982 // Pass an explicit PROTO constant instead of PROTO_CURRENT so that we
983 // do not rely on state from the global $wgRequest object (which it would,
984 // via wfGetServerUrl/wfExpandUrl/$wgRequest->protocol).
985 if ( $this->getProtocol() === 'http' ) {
986 return wfGetServerUrl( PROTO_HTTP ) . $this->getRequestURL();
987 } else {
988 return wfGetServerUrl( PROTO_HTTPS ) . $this->getRequestURL();
989 }
990 }
991
997 public function appendQueryValue( $key, $value ) {
998 return $this->appendQueryArray( [ $key => $value ] );
999 }
1000
1007 public function appendQueryArray( $array ) {
1008 $newquery = $this->getQueryValues();
1009 unset( $newquery['title'] );
1010 $newquery = array_merge( $newquery, $array );
1011
1012 return wfArrayToCgi( $newquery );
1013 }
1014
1025 public function getLimitOffsetForUser( UserIdentity $user, $deflimit = 50, $optionname = 'rclimit' ) {
1026 $limit = $this->getInt( 'limit', 0 );
1027 if ( $limit < 0 ) {
1028 $limit = 0;
1029 }
1030 if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
1031 $limit = MediaWikiServices::getInstance()
1032 ->getUserOptionsLookup()
1033 ->getIntOption( $user, $optionname );
1034 }
1035 if ( $limit <= 0 ) {
1036 $limit = $deflimit;
1037 }
1038 if ( $limit > 5000 ) {
1039 $limit = 5000; # We have *some* limits...
1040 }
1041
1042 $offset = $this->getInt( 'offset', 0 );
1043 if ( $offset < 0 ) {
1044 $offset = 0;
1045 }
1046
1047 return [ $limit, $offset ];
1048 }
1049
1056 public function getFileTempname( $key ) {
1057 return $this->getUpload( $key )->getTempName();
1058 }
1059
1066 public function getUploadError( $key ) {
1067 return $this->getUpload( $key )->getError();
1068 }
1069
1081 public function getFileName( $key ) {
1082 return $this->getUpload( $key )->getName();
1083 }
1084
1091 public function getUpload( $key ) {
1092 return new WebRequestUpload( $this, $key );
1093 }
1094
1101 public function response() {
1102 /* Lazy initialization of response object for this request */
1103 if ( !is_object( $this->response ) ) {
1104 $class = ( $this instanceof FauxRequest ) ? FauxResponse::class : WebResponse::class;
1105 $this->response = new $class();
1106 }
1107 return $this->response;
1108 }
1109
1113 protected function initHeaders() {
1114 if ( count( $this->headers ) ) {
1115 return;
1116 }
1117
1118 $this->headers = array_change_key_case( getallheaders(), CASE_UPPER );
1119 }
1120
1126 public function getAllHeaders() {
1127 $this->initHeaders();
1128 return $this->headers;
1129 }
1130
1143 public function getHeader( $name, $flags = 0 ) {
1144 $this->initHeaders();
1145 $name = strtoupper( $name );
1146 if ( !isset( $this->headers[$name] ) ) {
1147 return false;
1148 }
1149 $value = $this->headers[$name];
1150 if ( $flags & self::GETHEADER_LIST ) {
1151 $value = array_map( 'trim', explode( ',', $value ) );
1152 }
1153 return $value;
1154 }
1155
1163 public function getSessionData( $key ) {
1164 return $this->getSession()->get( $key );
1165 }
1166
1172 public function setSessionData( $key, $data ) {
1173 $this->getSession()->set( $key, $data );
1174 }
1175
1189 public function getAcceptLang() {
1190 // Modified version of code found at
1191 // http://www.thefutureoftheweb.com/blog/use-accept-language-header
1192 $acceptLang = $this->getHeader( 'Accept-Language' );
1193 if ( !$acceptLang ) {
1194 return [];
1195 }
1196
1197 // Return the language codes in lower case
1198 $acceptLang = strtolower( $acceptLang );
1199
1200 // Break up string into pieces (languages and q factors)
1201 if ( !preg_match_all(
1202 '/
1203 # a language code or a star is required
1204 ([a-z]{1,8}(?:-[a-z]{1,8})*|\*)
1205 # from here everything is optional
1206 \s*
1207 (?:
1208 # this accepts only numbers in the range ;q=0.000 to ;q=1.000
1209 ;\s*q\s*=\s*
1210 (1(?:\.0{0,3})?|0(?:\.\d{0,3})?)?
1211 )?
1212 /x',
1213 $acceptLang,
1214 $matches,
1215 PREG_SET_ORDER
1216 ) ) {
1217 return [];
1218 }
1219
1220 // Create a list like "en" => 0.8
1221 $langs = [];
1222 foreach ( $matches as $match ) {
1223 $languageCode = $match[1];
1224 // When not present, the default value is 1
1225 $qValue = (float)( $match[2] ?? 1.0 );
1226 if ( $qValue ) {
1227 $langs[$languageCode] = $qValue;
1228 }
1229 }
1230
1231 // Sort list by qValue
1232 arsort( $langs, SORT_NUMERIC );
1233 return $langs;
1234 }
1235
1242 protected function getRawIP() {
1243 $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? null;
1244 if ( !$remoteAddr ) {
1245 return null;
1246 }
1247 if ( is_array( $remoteAddr ) || str_contains( $remoteAddr, ',' ) ) {
1248 throw new MWException( 'Remote IP must not contain multiple values' );
1249 }
1250
1251 return IPUtils::canonicalize( $remoteAddr );
1252 }
1253
1261 public function getIP() {
1262 global $wgUsePrivateIPs;
1263
1264 # Return cached result
1265 if ( $this->ip !== null ) {
1266 return $this->ip;
1267 }
1268
1269 # collect the originating IPs
1270 $ip = $this->getRawIP();
1271 if ( !$ip ) {
1272 throw new MWException( 'Unable to determine IP.' );
1273 }
1274
1275 # Append XFF
1276 $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
1277 if ( $forwardedFor !== false ) {
1278 $proxyLookup = MediaWikiServices::getInstance()->getProxyLookup();
1279 $isConfigured = $proxyLookup->isConfiguredProxy( $ip );
1280 $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
1281 $ipchain = array_reverse( $ipchain );
1282 array_unshift( $ipchain, $ip );
1283
1284 # Step through XFF list and find the last address in the list which is a
1285 # trusted server. Set $ip to the IP address given by that trusted server,
1286 # unless the address is not sensible (e.g. private). However, prefer private
1287 # IP addresses over proxy servers controlled by this site (more sensible).
1288 # Note that some XFF values might be "unknown" with Squid/Varnish.
1289 foreach ( $ipchain as $i => $curIP ) {
1290 $curIP = IPUtils::sanitizeIP(
1291 IPUtils::canonicalize(
1292 self::canonicalizeIPv6LoopbackAddress( $curIP )
1293 )
1294 );
1295 if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown'
1296 || !$proxyLookup->isTrustedProxy( $curIP )
1297 ) {
1298 break; // IP is not valid/trusted or does not point to anything
1299 }
1300 if (
1301 IPUtils::isPublic( $ipchain[$i + 1] ) ||
1303 // T50919; treat IP as valid
1304 $proxyLookup->isConfiguredProxy( $curIP )
1305 ) {
1306 $nextIP = $ipchain[$i + 1];
1307
1308 // Follow the next IP according to the proxy
1309 $nextIP = IPUtils::canonicalize(
1310 self::canonicalizeIPv6LoopbackAddress( $nextIP )
1311 );
1312 if ( !$nextIP && $isConfigured ) {
1313 // We have not yet made it past CDN/proxy servers of this site,
1314 // so either they are misconfigured or there is some IP spoofing.
1315 throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
1316 }
1317 $ip = $nextIP;
1318
1319 // keep traversing the chain
1320 continue;
1321 }
1322 break;
1323 }
1324 }
1325
1326 // Allow extensions to modify the result
1327 // Optimisation: Hot code called on most requests (T85805).
1328 if ( Hooks::isRegistered( 'GetIP' ) ) {
1329 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
1330 Hooks::runner()->onGetIP( $ip );
1331 }
1332
1333 if ( !$ip ) {
1334 throw new MWException( 'Unable to determine IP.' );
1335 }
1336
1337 $this->ip = $ip;
1338 return $ip;
1339 }
1340
1349 public static function canonicalizeIPv6LoopbackAddress( $ip ) {
1350 // Code moved from IPUtils library. See T248237#6614927
1351 $m = [];
1352 if ( preg_match( '/^0*' . IPUtils::RE_IPV6_GAP . '1$/', $ip, $m ) ) {
1353 return '127.0.0.1';
1354 }
1355 return $ip;
1356 }
1357
1363 public function setIP( $ip ) {
1364 $this->ip = $ip;
1365 }
1366
1379 public function hasSafeMethod() {
1380 if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
1381 return false; // CLI mode
1382 }
1383
1384 return in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS', 'TRACE' ] );
1385 }
1386
1405 public function isSafeRequest() {
1406 if ( $this->markedAsSafe && $this->wasPosted() ) {
1407 return true; // marked as a "safe" POST
1408 }
1409
1410 return $this->hasSafeMethod();
1411 }
1412
1423 public function markAsSafeRequest() {
1424 $this->markedAsSafe = true;
1425 }
1426
1438 public function matchURLForCDN( array $cdnUrls ) {
1439 $reqUrl = wfExpandUrl( $this->getRequestURL(), PROTO_INTERNAL );
1440 $config = MediaWikiServices::getInstance()->getMainConfig();
1441 if ( $config->get( MainConfigNames::CdnMatchParameterOrder ) ) {
1442 // Strict matching
1443 return in_array( $reqUrl, $cdnUrls, true );
1444 }
1445
1446 // Loose matching (order of query parameters is ignored)
1447 $reqUrlParts = explode( '?', $reqUrl, 2 );
1448 $reqUrlBase = $reqUrlParts[0];
1449 $reqUrlParams = count( $reqUrlParts ) === 2 ? explode( '&', $reqUrlParts[1] ) : [];
1450 // The order of parameters after the sort() call below does not match
1451 // the order set by the CDN, and does not need to. The CDN needs to
1452 // take special care to preserve the relative order of duplicate keys
1453 // and array-like parameters.
1454 sort( $reqUrlParams );
1455 foreach ( $cdnUrls as $cdnUrl ) {
1456 if ( strlen( $reqUrl ) !== strlen( $cdnUrl ) ) {
1457 continue;
1458 }
1459 $cdnUrlParts = explode( '?', $cdnUrl, 2 );
1460 $cdnUrlBase = $cdnUrlParts[0];
1461 if ( $reqUrlBase !== $cdnUrlBase ) {
1462 continue;
1463 }
1464 $cdnUrlParams = count( $cdnUrlParts ) === 2 ? explode( '&', $cdnUrlParts[1] ) : [];
1465 sort( $cdnUrlParams );
1466 if ( $reqUrlParams === $cdnUrlParams ) {
1467 return true;
1468 }
1469 }
1470 return false;
1471 }
1472}
const PROTO_HTTPS
Definition Defines.php:194
const PROTO_INTERNAL
Definition Defines.php:200
const PROTO_HTTP
Definition Defines.php:193
wfRandomString( $length=32)
Get a random string containing a number of pseudo-random hex characters.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfGetServerUrl( $proto)
Get the wiki's "server", i.e.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
MediaWiki exception.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
WebRequest clone which takes values from a provided array.
MediaWiki\Request\PathRouter class.
add( $path, $params=[], $options=[])
Add a new path pattern to the path router.
Object to access the $_FILES array.
Allow programs to request this object from WebRequest::response() and handle all outputting (or lack ...
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:50
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
matchURLForCDN(array $cdnUrls)
Determine if the request URL matches one of a given set of canonical CDN URLs.
markAsSafeRequest()
Mark this request as identified as being nullipotent even if it is a POST request.
getIntOrNull( $name)
Fetch an integer value from the input or return null if empty.
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 MediaWiki\Request\WebRequestUpload object corresponding to the key.
string $protocol
Cached URL protocol.
getArray( $name, $default=null)
Fetch an array from the input or return $default if it's not set.
interpolateTitle()
Check for title, action, and/or variant data in the URL and interpolate it into the GET variables.
getPostValues()
Get the values passed via POST.
static detectProtocol()
Detect the protocol from $_SERVER.
isSafeRequest()
Whether this request should be identified as being "safe".
getSession()
Return the session for this request.
getRawInput()
Return the raw request body, with no processing.
getValues(... $names)
Extracts the (given) named values into an array.
getRawQueryString()
Return the contents of the Query with no decoding.
getFileTempname( $key)
Return the path to the temporary file where PHP has stored the upload.
getVal( $name, $default=null)
Fetch a text string and partially normalized it.
getFloat( $name, $default=0.0)
Fetch a floating point value from the input or return $default if not set.
string string[][] $queryParams
The parameters from $_GET only.
getUploadError( $key)
Return the upload error or 0.
getAllHeaders()
Get an array containing all request headers.
getFuzzyBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
static getRequestId()
Get the current request ID.
getProtocol()
Get the current URL protocol (http or https)
getMethod()
Get the HTTP method used for this request.
initHeaders()
Initialise the header list.
getBool( $name, $default=false)
Fetch a boolean value from the input or return $default if not set.
static getRequestPathSuffix( $basePath)
If the request URL matches a given base path, extract the path part of the request URL after that bas...
string string[][] $queryAndPathParams
The parameters from $_GET.
static getGlobalRequestURL()
Return the path and query string portion of the main request URI.
setVal( $key, $value)
Set an arbitrary value into our get/post data.
getFullRequestURL()
Return the request URI with the canonical service and hostname, path, and query string.
getElapsedTime()
Get the number of seconds to have elapsed since request start, in fractional seconds,...
float $requestTime
The timestamp of the start of the request, with microsecond precision.
string[] $headers
Lazy-initialized request headers indexed by upper-case header name.
getCrossSiteCookie( $key, $prefix='', $default=null)
Get a cookie set with SameSite=None possibly with a legacy fallback cookie.
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
appendQueryArray( $array)
Appends or replaces value of query variables.
getSessionId()
Get the session id for this request, if any.
getAcceptLang()
Parse the Accept-Language header sent by the client into an array.
static canonicalizeIPv6LoopbackAddress( $ip)
Converts ::1 (IPv6 loopback address) to 127.0.0.1 (IPv4 loopback address); assists in matching truste...
getRawPostString()
Return the contents of the POST with no decoding.
getQueryValues()
Get the values passed in the query string and the path router parameters.
response()
Return a handle to WebResponse style object, for setting cookies, headers and other stuff,...
getIP()
Work out the IP address based on various globals For trusted proxies, use the XFF client IP (first of...
static detectServer( $assumeProxiesUseDefaultProtocolPorts=null)
Work out an appropriate URL prefix containing scheme and host, based on information detected from $_S...
getInt( $name, $default=0)
Fetch an integer value from the input or return $default if not set.
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
setSessionData( $key, $data)
getFileName( $key)
Return the original filename of the uploaded file, as reported by the submitting user agent.
const GETHEADER_LIST
Flag to make WebRequest::getHeader return an array of values.
hasSafeMethod()
Check if this request uses a "safe" HTTP method.
getRawVal( $name, $default=null)
Fetch a string WITHOUT any Unicode or line break normalization.
getIntArray( $name, $default=null)
Fetch an array of integers, or return $default if it's not set.
appendQueryValue( $key, $value)
normalizeUnicode( $data)
Recursively normalizes UTF-8 strings in the given array.
static overrideRequestId( $id)
Override the unique request ID.
unsetVal( $key)
Unset an arbitrary value from our get/post data.
static getPathInfo( $want='all')
Extract relevant query arguments from the http request uri's path to be merged with the normal php pr...
SessionId null $sessionId
Session ID to use for this request.
getRawIP()
Fetch the raw IP from the request.
setSessionId(SessionId $sessionId)
Set the session for this request.
getCookie( $key, $prefix=null, $default=null)
Get a cookie from the $_COOKIE jar.
array $data
The parameters from $_GET, $_POST and the path router.
static extractTitle( $path, $bases, $key=false)
URL rewriting function; tries to extract page title and, optionally, one other fixed parameter value ...
getText( $name, $default='')
Fetch a text string and return it in normalized form.
getRequestURL()
Return the path and query string portion of the request URI.
getHeader( $name, $flags=0)
Get a request header, or false if it isn't set.
getSessionData( $key)
Get data from the session.
getQueryValuesOnly()
Get the values passed in the query string only, not including the path router parameters.
$wgUsePathInfo
Config variable stub for the UsePathInfo setting, for use by phpdoc and IDEs.
$wgUseSameSiteLegacyCookies
Config variable stub for the UseSameSiteLegacyCookies setting, for use by phpdoc and IDEs.
$wgScript
Config variable stub for the Script setting, for use by phpdoc and IDEs.
$wgActionPaths
Config variable stub for the ActionPaths setting, for use by phpdoc and IDEs.
$wgArticlePath
Config variable stub for the ArticlePath setting, for use by phpdoc and IDEs.
$wgAllowExternalReqID
Config variable stub for the AllowExternalReqID setting, for use by phpdoc and IDEs.
$wgVariantArticlePath
Config variable stub for the VariantArticlePath setting, for use by phpdoc and IDEs.
$wgCookiePrefix
Config variable stub for the CookiePrefix setting, for use by phpdoc and IDEs.
$wgUsePrivateIPs
Config variable stub for the UsePrivateIPs setting, for use by phpdoc and IDEs.
Interface for objects representing user identity.