[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Deal with importing all those nasty globals and things 4 * 5 * Copyright © 2003 Brion Vibber <[email protected]> 6 * https://www.mediawiki.org/ 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License along 19 * with this program; if not, write to the Free Software Foundation, Inc., 20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 * http://www.gnu.org/copyleft/gpl.html 22 * 23 * @file 24 */ 25 26 /** 27 * The WebRequest class encapsulates getting at data passed in the 28 * URL or via a POSTed form stripping illegal input characters and 29 * normalizing Unicode sequences. 30 * 31 * Usually this is used via a global singleton, $wgRequest. You should 32 * not create a second WebRequest object; make a FauxRequest object if 33 * you want to pass arbitrary data to some function in place of the web 34 * input. 35 * 36 * @ingroup HTTP 37 */ 38 class WebRequest { 39 protected $data, $headers = array(); 40 41 /** 42 * Lazy-init response object 43 * @var WebResponse 44 */ 45 private $response; 46 47 /** 48 * Cached client IP address 49 * @var string 50 */ 51 private $ip; 52 53 /** 54 * Cached URL protocol 55 * @var string 56 */ 57 protected $protocol; 58 59 public function __construct() { 60 if ( function_exists( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc() ) { 61 throw new MWException( "MediaWiki does not function when magic quotes are enabled." ); 62 } 63 64 // POST overrides GET data 65 // We don't use $_REQUEST here to avoid interference from cookies... 66 $this->data = $_POST + $_GET; 67 } 68 69 /** 70 * Extract relevant query arguments from the http request uri's path 71 * to be merged with the normal php provided query arguments. 72 * Tries to use the REQUEST_URI data if available and parses it 73 * according to the wiki's configuration looking for any known pattern. 74 * 75 * If the REQUEST_URI is not provided we'll fall back on the PATH_INFO 76 * provided by the server if any and use that to set a 'title' parameter. 77 * 78 * @param string $want If this is not 'all', then the function 79 * will return an empty array if it determines that the URL is 80 * inside a rewrite path. 81 * 82 * @return array Any query arguments found in path matches. 83 */ 84 public static function getPathInfo( $want = 'all' ) { 85 global $wgUsePathInfo; 86 // PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892 87 // And also by Apache 2.x, double slashes are converted to single slashes. 88 // So we will use REQUEST_URI if possible. 89 $matches = array(); 90 if ( !empty( $_SERVER['REQUEST_URI'] ) ) { 91 // Slurp out the path portion to examine... 92 $url = $_SERVER['REQUEST_URI']; 93 if ( !preg_match( '!^https?://!', $url ) ) { 94 $url = 'http://unused' . $url; 95 } 96 wfSuppressWarnings(); 97 $a = parse_url( $url ); 98 wfRestoreWarnings(); 99 if ( $a ) { 100 $path = isset( $a['path'] ) ? $a['path'] : ''; 101 102 global $wgScript; 103 if ( $path == $wgScript && $want !== 'all' ) { 104 // Script inside a rewrite path? 105 // Abort to keep from breaking... 106 return $matches; 107 } 108 109 $router = new PathRouter; 110 111 // Raw PATH_INFO style 112 $router->add( "$wgScript/$1" ); 113 114 if ( isset( $_SERVER['SCRIPT_NAME'] ) 115 && preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] ) 116 ) { 117 # Check for SCRIPT_NAME, we handle index.php explicitly 118 # But we do have some other .php files such as img_auth.php 119 # Don't let root article paths clober the parsing for them 120 $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" ); 121 } 122 123 global $wgArticlePath; 124 if ( $wgArticlePath ) { 125 $router->add( $wgArticlePath ); 126 } 127 128 global $wgActionPaths; 129 if ( $wgActionPaths ) { 130 $router->add( $wgActionPaths, array( 'action' => '$key' ) ); 131 } 132 133 global $wgVariantArticlePath, $wgContLang; 134 if ( $wgVariantArticlePath ) { 135 $router->add( $wgVariantArticlePath, 136 array( 'variant' => '$2' ), 137 array( '$2' => $wgContLang->getVariants() ) 138 ); 139 } 140 141 wfRunHooks( 'WebRequestPathInfoRouter', array( $router ) ); 142 143 $matches = $router->parse( $path ); 144 } 145 } elseif ( $wgUsePathInfo ) { 146 if ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) { 147 // Mangled PATH_INFO 148 // http://bugs.php.net/bug.php?id=31892 149 // Also reported when ini_get('cgi.fix_pathinfo')==false 150 $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 ); 151 152 } elseif ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) { 153 // Regular old PATH_INFO yay 154 $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 ); 155 } 156 } 157 158 return $matches; 159 } 160 161 /** 162 * Work out an appropriate URL prefix containing scheme and host, based on 163 * information detected from $_SERVER 164 * 165 * @return string 166 */ 167 public static function detectServer() { 168 $proto = self::detectProtocol(); 169 $stdPort = $proto === 'https' ? 443 : 80; 170 171 $varNames = array( 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' ); 172 $host = 'localhost'; 173 $port = $stdPort; 174 foreach ( $varNames as $varName ) { 175 if ( !isset( $_SERVER[$varName] ) ) { 176 continue; 177 } 178 $parts = IP::splitHostAndPort( $_SERVER[$varName] ); 179 if ( !$parts ) { 180 // Invalid, do not use 181 continue; 182 } 183 $host = $parts[0]; 184 if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) { 185 // Bug 70021: Assume that upstream proxy is running on the default 186 // port based on the protocol. We have no reliable way to determine 187 // the actual port in use upstream. 188 $port = $stdPort; 189 } elseif ( $parts[1] === false ) { 190 if ( isset( $_SERVER['SERVER_PORT'] ) ) { 191 $port = $_SERVER['SERVER_PORT']; 192 } // else leave it as $stdPort 193 } else { 194 $port = $parts[1]; 195 } 196 break; 197 } 198 199 return $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort ); 200 } 201 202 /** 203 * Detect the protocol from $_SERVER. 204 * This is for use prior to Setup.php, when no WebRequest object is available. 205 * At other times, use the non-static function getProtocol(). 206 * 207 * @return array 208 */ 209 public static function detectProtocol() { 210 if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) || 211 ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 212 $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) { 213 return 'https'; 214 } else { 215 return 'http'; 216 } 217 } 218 219 /** 220 * Get the current URL protocol (http or https) 221 * @return string 222 */ 223 public function getProtocol() { 224 if ( $this->protocol === null ) { 225 $this->protocol = self::detectProtocol(); 226 } 227 return $this->protocol; 228 } 229 230 /** 231 * Check for title, action, and/or variant data in the URL 232 * and interpolate it into the GET variables. 233 * This should only be run after $wgContLang is available, 234 * as we may need the list of language variants to determine 235 * available variant URLs. 236 */ 237 public function interpolateTitle() { 238 // bug 16019: title interpolation on API queries is useless and sometimes harmful 239 if ( defined( 'MW_API' ) ) { 240 return; 241 } 242 243 $matches = self::getPathInfo( 'title' ); 244 foreach ( $matches as $key => $val ) { 245 $this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val; 246 } 247 } 248 249 /** 250 * URL rewriting function; tries to extract page title and, 251 * optionally, one other fixed parameter value from a URL path. 252 * 253 * @param string $path The URL path given from the client 254 * @param array $bases One or more URLs, optionally with $1 at the end 255 * @param string $key If provided, the matching key in $bases will be 256 * passed on as the value of this URL parameter 257 * @return array Array of URL variables to interpolate; empty if no match 258 */ 259 static function extractTitle( $path, $bases, $key = false ) { 260 foreach ( (array)$bases as $keyValue => $base ) { 261 // Find the part after $wgArticlePath 262 $base = str_replace( '$1', '', $base ); 263 $baseLen = strlen( $base ); 264 if ( substr( $path, 0, $baseLen ) == $base ) { 265 $raw = substr( $path, $baseLen ); 266 if ( $raw !== '' ) { 267 $matches = array( 'title' => rawurldecode( $raw ) ); 268 if ( $key ) { 269 $matches[$key] = $keyValue; 270 } 271 return $matches; 272 } 273 } 274 } 275 return array(); 276 } 277 278 /** 279 * Recursively normalizes UTF-8 strings in the given array. 280 * 281 * @param string|array $data 282 * @return array|string Cleaned-up version of the given 283 * @private 284 */ 285 function normalizeUnicode( $data ) { 286 if ( is_array( $data ) ) { 287 foreach ( $data as $key => $val ) { 288 $data[$key] = $this->normalizeUnicode( $val ); 289 } 290 } else { 291 global $wgContLang; 292 $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal::cleanUp( $data ); 293 } 294 return $data; 295 } 296 297 /** 298 * Fetch a value from the given array or return $default if it's not set. 299 * 300 * @param array $arr 301 * @param string $name 302 * @param mixed $default 303 * @return mixed 304 */ 305 private function getGPCVal( $arr, $name, $default ) { 306 # PHP is so nice to not touch input data, except sometimes: 307 # http://us2.php.net/variables.external#language.variables.external.dot-in-names 308 # Work around PHP *feature* to avoid *bugs* elsewhere. 309 $name = strtr( $name, '.', '_' ); 310 if ( isset( $arr[$name] ) ) { 311 global $wgContLang; 312 $data = $arr[$name]; 313 if ( isset( $_GET[$name] ) && !is_array( $data ) ) { 314 # Check for alternate/legacy character encoding. 315 if ( isset( $wgContLang ) ) { 316 $data = $wgContLang->checkTitleEncoding( $data ); 317 } 318 } 319 $data = $this->normalizeUnicode( $data ); 320 return $data; 321 } else { 322 return $default; 323 } 324 } 325 326 /** 327 * Fetch a scalar from the input or return $default if it's not set. 328 * Returns a string. Arrays are discarded. Useful for 329 * non-freeform text inputs (e.g. predefined internal text keys 330 * selected by a drop-down menu). For freeform input, see getText(). 331 * 332 * @param string $name 333 * @param string $default Optional default (or null) 334 * @return string 335 */ 336 public function getVal( $name, $default = null ) { 337 $val = $this->getGPCVal( $this->data, $name, $default ); 338 if ( is_array( $val ) ) { 339 $val = $default; 340 } 341 if ( is_null( $val ) ) { 342 return $val; 343 } else { 344 return (string)$val; 345 } 346 } 347 348 /** 349 * Set an arbitrary value into our get/post data. 350 * 351 * @param string $key Key name to use 352 * @param mixed $value Value to set 353 * @return mixed Old value if one was present, null otherwise 354 */ 355 public function setVal( $key, $value ) { 356 $ret = isset( $this->data[$key] ) ? $this->data[$key] : null; 357 $this->data[$key] = $value; 358 return $ret; 359 } 360 361 /** 362 * Unset an arbitrary value from our get/post data. 363 * 364 * @param string $key Key name to use 365 * @return mixed Old value if one was present, null otherwise 366 */ 367 public function unsetVal( $key ) { 368 if ( !isset( $this->data[$key] ) ) { 369 $ret = null; 370 } else { 371 $ret = $this->data[$key]; 372 unset( $this->data[$key] ); 373 } 374 return $ret; 375 } 376 377 /** 378 * Fetch an array from the input or return $default if it's not set. 379 * If source was scalar, will return an array with a single element. 380 * If no source and no default, returns null. 381 * 382 * @param string $name 383 * @param array $default Optional default (or null) 384 * @return array 385 */ 386 public function getArray( $name, $default = null ) { 387 $val = $this->getGPCVal( $this->data, $name, $default ); 388 if ( is_null( $val ) ) { 389 return null; 390 } else { 391 return (array)$val; 392 } 393 } 394 395 /** 396 * Fetch an array of integers, or return $default if it's not set. 397 * If source was scalar, will return an array with a single element. 398 * If no source and no default, returns null. 399 * If an array is returned, contents are guaranteed to be integers. 400 * 401 * @param string $name 402 * @param array $default Option default (or null) 403 * @return array Array of ints 404 */ 405 public function getIntArray( $name, $default = null ) { 406 $val = $this->getArray( $name, $default ); 407 if ( is_array( $val ) ) { 408 $val = array_map( 'intval', $val ); 409 } 410 return $val; 411 } 412 413 /** 414 * Fetch an integer value from the input or return $default if not set. 415 * Guaranteed to return an integer; non-numeric input will typically 416 * return 0. 417 * 418 * @param string $name 419 * @param int $default 420 * @return int 421 */ 422 public function getInt( $name, $default = 0 ) { 423 return intval( $this->getVal( $name, $default ) ); 424 } 425 426 /** 427 * Fetch an integer value from the input or return null if empty. 428 * Guaranteed to return an integer or null; non-numeric input will 429 * typically return null. 430 * 431 * @param string $name 432 * @return int|null 433 */ 434 public function getIntOrNull( $name ) { 435 $val = $this->getVal( $name ); 436 return is_numeric( $val ) 437 ? intval( $val ) 438 : null; 439 } 440 441 /** 442 * Fetch a floating point value from the input or return $default if not set. 443 * Guaranteed to return a float; non-numeric input will typically 444 * return 0. 445 * 446 * @since 1.23 447 * @param string $name 448 * @param float $default 449 * @return float 450 */ 451 public function getFloat( $name, $default = 0.0 ) { 452 return floatval( $this->getVal( $name, $default ) ); 453 } 454 455 /** 456 * Fetch a boolean value from the input or return $default if not set. 457 * Guaranteed to return true or false, with normal PHP semantics for 458 * boolean interpretation of strings. 459 * 460 * @param string $name 461 * @param bool $default 462 * @return bool 463 */ 464 public function getBool( $name, $default = false ) { 465 return (bool)$this->getVal( $name, $default ); 466 } 467 468 /** 469 * Fetch a boolean value from the input or return $default if not set. 470 * Unlike getBool, the string "false" will result in boolean false, which is 471 * useful when interpreting information sent from JavaScript. 472 * 473 * @param string $name 474 * @param bool $default 475 * @return bool 476 */ 477 public function getFuzzyBool( $name, $default = false ) { 478 return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0; 479 } 480 481 /** 482 * Return true if the named value is set in the input, whatever that 483 * value is (even "0"). Return false if the named value is not set. 484 * Example use is checking for the presence of check boxes in forms. 485 * 486 * @param string $name 487 * @return bool 488 */ 489 public function getCheck( $name ) { 490 # Checkboxes and buttons are only present when clicked 491 # Presence connotes truth, absence false 492 return $this->getVal( $name, null ) !== null; 493 } 494 495 /** 496 * Fetch a text string from the given array or return $default if it's not 497 * set. Carriage returns are stripped from the text, and with some language 498 * modules there is an input transliteration applied. This should generally 499 * be used for form "<textarea>" and "<input>" fields. Used for 500 * user-supplied freeform text input (for which input transformations may 501 * be required - e.g. Esperanto x-coding). 502 * 503 * @param string $name 504 * @param string $default Optional 505 * @return string 506 */ 507 public function getText( $name, $default = '' ) { 508 global $wgContLang; 509 $val = $this->getVal( $name, $default ); 510 return str_replace( "\r\n", "\n", 511 $wgContLang->recodeInput( $val ) ); 512 } 513 514 /** 515 * Extracts the given named values into an array. 516 * If no arguments are given, returns all input values. 517 * No transformation is performed on the values. 518 * 519 * @return array 520 */ 521 public function getValues() { 522 $names = func_get_args(); 523 if ( count( $names ) == 0 ) { 524 $names = array_keys( $this->data ); 525 } 526 527 $retVal = array(); 528 foreach ( $names as $name ) { 529 $value = $this->getGPCVal( $this->data, $name, null ); 530 if ( !is_null( $value ) ) { 531 $retVal[$name] = $value; 532 } 533 } 534 return $retVal; 535 } 536 537 /** 538 * Returns the names of all input values excluding those in $exclude. 539 * 540 * @param array $exclude 541 * @return array 542 */ 543 public function getValueNames( $exclude = array() ) { 544 return array_diff( array_keys( $this->getValues() ), $exclude ); 545 } 546 547 /** 548 * Get the values passed in the query string. 549 * No transformation is performed on the values. 550 * 551 * @return array 552 */ 553 public function getQueryValues() { 554 return $_GET; 555 } 556 557 /** 558 * Return the contents of the Query with no decoding. Use when you need to 559 * know exactly what was sent, e.g. for an OAuth signature over the elements. 560 * 561 * @return string 562 */ 563 public function getRawQueryString() { 564 return $_SERVER['QUERY_STRING']; 565 } 566 567 /** 568 * Return the contents of the POST with no decoding. Use when you need to 569 * know exactly what was sent, e.g. for an OAuth signature over the elements. 570 * 571 * @return string 572 */ 573 public function getRawPostString() { 574 if ( !$this->wasPosted() ) { 575 return ''; 576 } 577 return $this->getRawInput(); 578 } 579 580 /** 581 * Return the raw request body, with no processing. Cached since some methods 582 * disallow reading the stream more than once. As stated in the php docs, this 583 * does not work with enctype="multipart/form-data". 584 * 585 * @return string 586 */ 587 public function getRawInput() { 588 static $input = null; 589 if ( $input === null ) { 590 $input = file_get_contents( 'php://input' ); 591 } 592 return $input; 593 } 594 595 /** 596 * Get the HTTP method used for this request. 597 * 598 * @return string 599 */ 600 public function getMethod() { 601 return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 602 } 603 604 /** 605 * Returns true if the present request was reached by a POST operation, 606 * false otherwise (GET, HEAD, or command-line). 607 * 608 * Note that values retrieved by the object may come from the 609 * GET URL etc even on a POST request. 610 * 611 * @return bool 612 */ 613 public function wasPosted() { 614 return $this->getMethod() == 'POST'; 615 } 616 617 /** 618 * Returns true if there is a session cookie set. 619 * This does not necessarily mean that the user is logged in! 620 * 621 * If you want to check for an open session, use session_id() 622 * instead; that will also tell you if the session was opened 623 * during the current request (in which case the cookie will 624 * be sent back to the client at the end of the script run). 625 * 626 * @return bool 627 */ 628 public function checkSessionCookie() { 629 return isset( $_COOKIE[session_name()] ); 630 } 631 632 /** 633 * Get a cookie from the $_COOKIE jar 634 * 635 * @param string $key The name of the cookie 636 * @param string $prefix A prefix to use for the cookie name, if not $wgCookiePrefix 637 * @param mixed $default What to return if the value isn't found 638 * @return mixed Cookie value or $default if the cookie not set 639 */ 640 public function getCookie( $key, $prefix = null, $default = null ) { 641 if ( $prefix === null ) { 642 global $wgCookiePrefix; 643 $prefix = $wgCookiePrefix; 644 } 645 return $this->getGPCVal( $_COOKIE, $prefix . $key, $default ); 646 } 647 648 /** 649 * Return the path and query string portion of the request URI. 650 * This will be suitable for use as a relative link in HTML output. 651 * 652 * @throws MWException 653 * @return string 654 */ 655 public function getRequestURL() { 656 if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) { 657 $base = $_SERVER['REQUEST_URI']; 658 } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) 659 && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] ) 660 ) { 661 // Probably IIS; doesn't set REQUEST_URI 662 $base = $_SERVER['HTTP_X_ORIGINAL_URL']; 663 } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) { 664 $base = $_SERVER['SCRIPT_NAME']; 665 if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) { 666 $base .= '?' . $_SERVER['QUERY_STRING']; 667 } 668 } else { 669 // This shouldn't happen! 670 throw new MWException( "Web server doesn't provide either " . 671 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " . 672 "of your web server configuration to http://bugzilla.wikimedia.org/" ); 673 } 674 // User-agents should not send a fragment with the URI, but 675 // if they do, and the web server passes it on to us, we 676 // need to strip it or we get false-positive redirect loops 677 // or weird output URLs 678 $hash = strpos( $base, '#' ); 679 if ( $hash !== false ) { 680 $base = substr( $base, 0, $hash ); 681 } 682 683 if ( $base[0] == '/' ) { 684 // More than one slash will look like it is protocol relative 685 return preg_replace( '!^/+!', '/', $base ); 686 } else { 687 // We may get paths with a host prepended; strip it. 688 return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base ); 689 } 690 } 691 692 /** 693 * Return the request URI with the canonical service and hostname, path, 694 * and query string. This will be suitable for use as an absolute link 695 * in HTML or other output. 696 * 697 * If $wgServer is protocol-relative, this will return a fully 698 * qualified URL with the protocol that was used for this request. 699 * 700 * @return string 701 */ 702 public function getFullRequestURL() { 703 return wfExpandUrl( $this->getRequestURL(), PROTO_CURRENT ); 704 } 705 706 /** 707 * Take an arbitrary query and rewrite the present URL to include it 708 * @param string $query Query string fragment; do not include initial '?' 709 * 710 * @return string 711 */ 712 public function appendQuery( $query ) { 713 return $this->appendQueryArray( wfCgiToArray( $query ) ); 714 } 715 716 /** 717 * @param string $key 718 * @param string $value 719 * @param bool $onlyquery 720 * @return string 721 */ 722 public function appendQueryValue( $key, $value, $onlyquery = false ) { 723 return $this->appendQueryArray( array( $key => $value ), $onlyquery ); 724 } 725 726 /** 727 * Appends or replaces value of query variables. 728 * 729 * @param array $array Array of values to replace/add to query 730 * @param bool $onlyquery Whether to only return the query string and not the complete URL 731 * @return string 732 */ 733 public function appendQueryArray( $array, $onlyquery = false ) { 734 global $wgTitle; 735 $newquery = $this->getQueryValues(); 736 unset( $newquery['title'] ); 737 $newquery = array_merge( $newquery, $array ); 738 $query = wfArrayToCgi( $newquery ); 739 return $onlyquery ? $query : $wgTitle->getLocalURL( $query ); 740 } 741 742 /** 743 * Check for limit and offset parameters on the input, and return sensible 744 * defaults if not given. The limit must be positive and is capped at 5000. 745 * Offset must be positive but is not capped. 746 * 747 * @param int $deflimit Limit to use if no input and the user hasn't set the option. 748 * @param string $optionname To specify an option other than rclimit to pull from. 749 * @return array First element is limit, second is offset 750 */ 751 public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) { 752 global $wgUser; 753 754 $limit = $this->getInt( 'limit', 0 ); 755 if ( $limit < 0 ) { 756 $limit = 0; 757 } 758 if ( ( $limit == 0 ) && ( $optionname != '' ) ) { 759 $limit = $wgUser->getIntOption( $optionname ); 760 } 761 if ( $limit <= 0 ) { 762 $limit = $deflimit; 763 } 764 if ( $limit > 5000 ) { 765 $limit = 5000; # We have *some* limits... 766 } 767 768 $offset = $this->getInt( 'offset', 0 ); 769 if ( $offset < 0 ) { 770 $offset = 0; 771 } 772 773 return array( $limit, $offset ); 774 } 775 776 /** 777 * Return the path to the temporary file where PHP has stored the upload. 778 * 779 * @param string $key 780 * @return string|null String or null if no such file. 781 */ 782 public function getFileTempname( $key ) { 783 $file = new WebRequestUpload( $this, $key ); 784 return $file->getTempName(); 785 } 786 787 /** 788 * Return the upload error or 0 789 * 790 * @param string $key 791 * @return int 792 */ 793 public function getUploadError( $key ) { 794 $file = new WebRequestUpload( $this, $key ); 795 return $file->getError(); 796 } 797 798 /** 799 * Return the original filename of the uploaded file, as reported by 800 * the submitting user agent. HTML-style character entities are 801 * interpreted and normalized to Unicode normalization form C, in part 802 * to deal with weird input from Safari with non-ASCII filenames. 803 * 804 * Other than this the name is not verified for being a safe filename. 805 * 806 * @param string $key 807 * @return string|null String or null if no such file. 808 */ 809 public function getFileName( $key ) { 810 $file = new WebRequestUpload( $this, $key ); 811 return $file->getName(); 812 } 813 814 /** 815 * Return a WebRequestUpload object corresponding to the key 816 * 817 * @param string $key 818 * @return WebRequestUpload 819 */ 820 public function getUpload( $key ) { 821 return new WebRequestUpload( $this, $key ); 822 } 823 824 /** 825 * Return a handle to WebResponse style object, for setting cookies, 826 * headers and other stuff, for Request being worked on. 827 * 828 * @return WebResponse 829 */ 830 public function response() { 831 /* Lazy initialization of response object for this request */ 832 if ( !is_object( $this->response ) ) { 833 $class = ( $this instanceof FauxRequest ) ? 'FauxResponse' : 'WebResponse'; 834 $this->response = new $class(); 835 } 836 return $this->response; 837 } 838 839 /** 840 * Initialise the header list 841 */ 842 private function initHeaders() { 843 if ( count( $this->headers ) ) { 844 return; 845 } 846 847 $apacheHeaders = function_exists( 'apache_request_headers' ) ? apache_request_headers() : false; 848 if ( $apacheHeaders ) { 849 foreach ( $apacheHeaders as $tempName => $tempValue ) { 850 $this->headers[strtoupper( $tempName )] = $tempValue; 851 } 852 } else { 853 foreach ( $_SERVER as $name => $value ) { 854 if ( substr( $name, 0, 5 ) === 'HTTP_' ) { 855 $name = str_replace( '_', '-', substr( $name, 5 ) ); 856 $this->headers[$name] = $value; 857 } elseif ( $name === 'CONTENT_LENGTH' ) { 858 $this->headers['CONTENT-LENGTH'] = $value; 859 } 860 } 861 } 862 } 863 864 /** 865 * Get an array containing all request headers 866 * 867 * @return array Mapping header name to its value 868 */ 869 public function getAllHeaders() { 870 $this->initHeaders(); 871 return $this->headers; 872 } 873 874 /** 875 * Get a request header, or false if it isn't set 876 * @param string $name Case-insensitive header name 877 * 878 * @return string|bool False on failure 879 */ 880 public function getHeader( $name ) { 881 $this->initHeaders(); 882 $name = strtoupper( $name ); 883 if ( isset( $this->headers[$name] ) ) { 884 return $this->headers[$name]; 885 } else { 886 return false; 887 } 888 } 889 890 /** 891 * Get data from $_SESSION 892 * 893 * @param string $key Name of key in $_SESSION 894 * @return mixed 895 */ 896 public function getSessionData( $key ) { 897 if ( !isset( $_SESSION[$key] ) ) { 898 return null; 899 } 900 return $_SESSION[$key]; 901 } 902 903 /** 904 * Set session data 905 * 906 * @param string $key Name of key in $_SESSION 907 * @param mixed $data 908 */ 909 public function setSessionData( $key, $data ) { 910 $_SESSION[$key] = $data; 911 } 912 913 /** 914 * Check if Internet Explorer will detect an incorrect cache extension in 915 * PATH_INFO or QUERY_STRING. If the request can't be allowed, show an error 916 * message or redirect to a safer URL. Returns true if the URL is OK, and 917 * false if an error message has been shown and the request should be aborted. 918 * 919 * @param array $extWhitelist 920 * @throws HttpError 921 * @return bool 922 */ 923 public function checkUrlExtension( $extWhitelist = array() ) { 924 global $wgScriptExtension; 925 $extWhitelist[] = ltrim( $wgScriptExtension, '.' ); 926 if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) { 927 if ( !$this->wasPosted() ) { 928 $newUrl = IEUrlExtension::fixUrlForIE6( 929 $this->getFullRequestURL(), $extWhitelist ); 930 if ( $newUrl !== false ) { 931 $this->doSecurityRedirect( $newUrl ); 932 return false; 933 } 934 } 935 throw new HttpError( 403, 936 'Invalid file extension found in the path info or query string.' ); 937 } 938 return true; 939 } 940 941 /** 942 * Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in 943 * IE 6. Returns true if it was successful, false otherwise. 944 * 945 * @param string $url 946 * @return bool 947 */ 948 protected function doSecurityRedirect( $url ) { 949 header( 'Location: ' . $url ); 950 header( 'Content-Type: text/html' ); 951 $encUrl = htmlspecialchars( $url ); 952 echo <<<HTML 953 <html> 954 <head> 955 <title>Security redirect</title> 956 </head> 957 <body> 958 <h1>Security redirect</h1> 959 <p> 960 We can't serve non-HTML content from the URL you have requested, because 961 Internet Explorer would interpret it as an incorrect and potentially dangerous 962 content type.</p> 963 <p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the 964 URL you have requested, except that "&*" is appended. This prevents Internet 965 Explorer from seeing a bogus file extension. 966 </p> 967 </body> 968 </html> 969 HTML; 970 echo "\n"; 971 return true; 972 } 973 974 /** 975 * Parse the Accept-Language header sent by the client into an array 976 * 977 * @return array Array( languageCode => q-value ) sorted by q-value in 978 * descending order then appearing time in the header in ascending order. 979 * May contain the "language" '*', which applies to languages other than those explicitly listed. 980 * This is aligned with rfc2616 section 14.4 981 * Preference for earlier languages appears in rfc3282 as an extension to HTTP/1.1. 982 */ 983 public function getAcceptLang() { 984 // Modified version of code found at 985 // http://www.thefutureoftheweb.com/blog/use-accept-language-header 986 $acceptLang = $this->getHeader( 'Accept-Language' ); 987 if ( !$acceptLang ) { 988 return array(); 989 } 990 991 // Return the language codes in lower case 992 $acceptLang = strtolower( $acceptLang ); 993 994 // Break up string into pieces (languages and q factors) 995 $lang_parse = null; 996 preg_match_all( 997 '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/', 998 $acceptLang, 999 $lang_parse 1000 ); 1001 1002 if ( !count( $lang_parse[1] ) ) { 1003 return array(); 1004 } 1005 1006 $langcodes = $lang_parse[1]; 1007 $qvalues = $lang_parse[4]; 1008 $indices = range( 0, count( $lang_parse[1] ) - 1 ); 1009 1010 // Set default q factor to 1 1011 foreach ( $indices as $index ) { 1012 if ( $qvalues[$index] === '' ) { 1013 $qvalues[$index] = 1; 1014 } elseif ( $qvalues[$index] == 0 ) { 1015 unset( $langcodes[$index], $qvalues[$index], $indices[$index] ); 1016 } 1017 } 1018 1019 // Sort list. First by $qvalues, then by order. Reorder $langcodes the same way 1020 array_multisort( $qvalues, SORT_DESC, SORT_NUMERIC, $indices, $langcodes ); 1021 1022 // Create a list like "en" => 0.8 1023 $langs = array_combine( $langcodes, $qvalues ); 1024 1025 return $langs; 1026 } 1027 1028 /** 1029 * Fetch the raw IP from the request 1030 * 1031 * @since 1.19 1032 * 1033 * @throws MWException 1034 * @return string 1035 */ 1036 protected function getRawIP() { 1037 if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) { 1038 return null; 1039 } 1040 1041 if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) { 1042 throw new MWException( __METHOD__ 1043 . " : Could not determine the remote IP address due to multiple values." ); 1044 } else { 1045 $ipchain = $_SERVER['REMOTE_ADDR']; 1046 } 1047 1048 return IP::canonicalize( $ipchain ); 1049 } 1050 1051 /** 1052 * Work out the IP address based on various globals 1053 * For trusted proxies, use the XFF client IP (first of the chain) 1054 * 1055 * @since 1.19 1056 * 1057 * @throws MWException 1058 * @return string 1059 */ 1060 public function getIP() { 1061 global $wgUsePrivateIPs; 1062 1063 # Return cached result 1064 if ( $this->ip !== null ) { 1065 return $this->ip; 1066 } 1067 1068 # collect the originating ips 1069 $ip = $this->getRawIP(); 1070 if ( !$ip ) { 1071 throw new MWException( 'Unable to determine IP.' ); 1072 } 1073 1074 # Append XFF 1075 $forwardedFor = $this->getHeader( 'X-Forwarded-For' ); 1076 if ( $forwardedFor !== false ) { 1077 $isConfigured = IP::isConfiguredProxy( $ip ); 1078 $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) ); 1079 $ipchain = array_reverse( $ipchain ); 1080 array_unshift( $ipchain, $ip ); 1081 1082 # Step through XFF list and find the last address in the list which is a 1083 # trusted server. Set $ip to the IP address given by that trusted server, 1084 # unless the address is not sensible (e.g. private). However, prefer private 1085 # IP addresses over proxy servers controlled by this site (more sensible). 1086 # Note that some XFF values might be "unknown" with Squid/Varnish. 1087 foreach ( $ipchain as $i => $curIP ) { 1088 $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) ); 1089 if ( !$curIP || !isset( $ipchain[$i + 1] ) || $ipchain[$i + 1] === 'unknown' 1090 || !IP::isTrustedProxy( $curIP ) 1091 ) { 1092 break; // IP is not valid/trusted or does not point to anything 1093 } 1094 if ( 1095 IP::isPublic( $ipchain[$i + 1] ) || 1096 $wgUsePrivateIPs || 1097 IP::isConfiguredProxy( $curIP ) // bug 48919; treat IP as sane 1098 ) { 1099 // Follow the next IP according to the proxy 1100 $nextIP = IP::canonicalize( $ipchain[$i + 1] ); 1101 if ( !$nextIP && $isConfigured ) { 1102 // We have not yet made it past CDN/proxy servers of this site, 1103 // so either they are misconfigured or there is some IP spoofing. 1104 throw new MWException( "Invalid IP given in XFF '$forwardedFor'." ); 1105 } 1106 $ip = $nextIP; 1107 // keep traversing the chain 1108 continue; 1109 } 1110 break; 1111 } 1112 } 1113 1114 # Allow extensions to improve our guess 1115 wfRunHooks( 'GetIP', array( &$ip ) ); 1116 1117 if ( !$ip ) { 1118 throw new MWException( "Unable to determine IP." ); 1119 } 1120 1121 wfDebug( "IP: $ip\n" ); 1122 $this->ip = $ip; 1123 return $ip; 1124 } 1125 1126 /** 1127 * @param string $ip 1128 * @return void 1129 * @since 1.21 1130 */ 1131 public function setIP( $ip ) { 1132 $this->ip = $ip; 1133 } 1134 } 1135 1136 /** 1137 * Object to access the $_FILES array 1138 */ 1139 class WebRequestUpload { 1140 protected $request; 1141 protected $doesExist; 1142 protected $fileInfo; 1143 1144 /** 1145 * Constructor. Should only be called by WebRequest 1146 * 1147 * @param WebRequest $request The associated request 1148 * @param string $key Key in $_FILES array (name of form field) 1149 */ 1150 public function __construct( $request, $key ) { 1151 $this->request = $request; 1152 $this->doesExist = isset( $_FILES[$key] ); 1153 if ( $this->doesExist ) { 1154 $this->fileInfo = $_FILES[$key]; 1155 } 1156 } 1157 1158 /** 1159 * Return whether a file with this name was uploaded. 1160 * 1161 * @return bool 1162 */ 1163 public function exists() { 1164 return $this->doesExist; 1165 } 1166 1167 /** 1168 * Return the original filename of the uploaded file 1169 * 1170 * @return string|null Filename or null if non-existent 1171 */ 1172 public function getName() { 1173 if ( !$this->exists() ) { 1174 return null; 1175 } 1176 1177 global $wgContLang; 1178 $name = $this->fileInfo['name']; 1179 1180 # Safari sends filenames in HTML-encoded Unicode form D... 1181 # Horrid and evil! Let's try to make some kind of sense of it. 1182 $name = Sanitizer::decodeCharReferences( $name ); 1183 $name = $wgContLang->normalize( $name ); 1184 wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" ); 1185 return $name; 1186 } 1187 1188 /** 1189 * Return the file size of the uploaded file 1190 * 1191 * @return int File size or zero if non-existent 1192 */ 1193 public function getSize() { 1194 if ( !$this->exists() ) { 1195 return 0; 1196 } 1197 1198 return $this->fileInfo['size']; 1199 } 1200 1201 /** 1202 * Return the path to the temporary file 1203 * 1204 * @return string|null Path or null if non-existent 1205 */ 1206 public function getTempName() { 1207 if ( !$this->exists() ) { 1208 return null; 1209 } 1210 1211 return $this->fileInfo['tmp_name']; 1212 } 1213 1214 /** 1215 * Return the upload error. See link for explanation 1216 * http://www.php.net/manual/en/features.file-upload.errors.php 1217 * 1218 * @return int One of the UPLOAD_ constants, 0 if non-existent 1219 */ 1220 public function getError() { 1221 if ( !$this->exists() ) { 1222 return 0; # UPLOAD_ERR_OK 1223 } 1224 1225 return $this->fileInfo['error']; 1226 } 1227 1228 /** 1229 * Returns whether this upload failed because of overflow of a maximum set 1230 * in php.ini 1231 * 1232 * @return bool 1233 */ 1234 public function isIniSizeOverflow() { 1235 if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) { 1236 # PHP indicated that upload_max_filesize is exceeded 1237 return true; 1238 } 1239 1240 $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' ); 1241 if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) { 1242 # post_max_size is exceeded 1243 return true; 1244 } 1245 1246 return false; 1247 } 1248 } 1249 1250 /** 1251 * WebRequest clone which takes values from a provided array. 1252 * 1253 * @ingroup HTTP 1254 */ 1255 class FauxRequest extends WebRequest { 1256 private $wasPosted = false; 1257 private $session = array(); 1258 1259 /** 1260 * @param array $data Array of *non*-urlencoded key => value pairs, the 1261 * fake GET/POST values 1262 * @param bool $wasPosted Whether to treat the data as POST 1263 * @param array|null $session Session array or null 1264 * @param string $protocol 'http' or 'https' 1265 * @throws MWException 1266 */ 1267 public function __construct( $data = array(), $wasPosted = false, 1268 $session = null, $protocol = 'http' 1269 ) { 1270 if ( is_array( $data ) ) { 1271 $this->data = $data; 1272 } else { 1273 throw new MWException( "FauxRequest() got bogus data" ); 1274 } 1275 $this->wasPosted = $wasPosted; 1276 if ( $session ) { 1277 $this->session = $session; 1278 } 1279 $this->protocol = $protocol; 1280 } 1281 1282 /** 1283 * @param string $method 1284 * @throws MWException 1285 */ 1286 private function notImplemented( $method ) { 1287 throw new MWException( "{$method}() not implemented" ); 1288 } 1289 1290 /** 1291 * @param string $name 1292 * @param string $default 1293 * @return string 1294 */ 1295 public function getText( $name, $default = '' ) { 1296 # Override; don't recode since we're using internal data 1297 return (string)$this->getVal( $name, $default ); 1298 } 1299 1300 /** 1301 * @return array 1302 */ 1303 public function getValues() { 1304 return $this->data; 1305 } 1306 1307 /** 1308 * @return array 1309 */ 1310 public function getQueryValues() { 1311 if ( $this->wasPosted ) { 1312 return array(); 1313 } else { 1314 return $this->data; 1315 } 1316 } 1317 1318 public function getMethod() { 1319 return $this->wasPosted ? 'POST' : 'GET'; 1320 } 1321 1322 /** 1323 * @return bool 1324 */ 1325 public function wasPosted() { 1326 return $this->wasPosted; 1327 } 1328 1329 public function getCookie( $key, $prefix = null, $default = null ) { 1330 return $default; 1331 } 1332 1333 public function checkSessionCookie() { 1334 return false; 1335 } 1336 1337 public function getRequestURL() { 1338 $this->notImplemented( __METHOD__ ); 1339 } 1340 1341 public function getProtocol() { 1342 return $this->protocol; 1343 } 1344 1345 /** 1346 * @param string $name The name of the header to get (case insensitive). 1347 * @return bool|string 1348 */ 1349 public function getHeader( $name ) { 1350 $name = strtoupper( $name ); 1351 return isset( $this->headers[$name] ) ? $this->headers[$name] : false; 1352 } 1353 1354 /** 1355 * @param string $name 1356 * @param string $val 1357 */ 1358 public function setHeader( $name, $val ) { 1359 $name = strtoupper( $name ); 1360 $this->headers[$name] = $val; 1361 } 1362 1363 /** 1364 * @param string $key 1365 * @return array|null 1366 */ 1367 public function getSessionData( $key ) { 1368 if ( isset( $this->session[$key] ) ) { 1369 return $this->session[$key]; 1370 } 1371 return null; 1372 } 1373 1374 /** 1375 * @param string $key 1376 * @param array $data 1377 */ 1378 public function setSessionData( $key, $data ) { 1379 $this->session[$key] = $data; 1380 } 1381 1382 /** 1383 * @return array|mixed|null 1384 */ 1385 public function getSessionArray() { 1386 return $this->session; 1387 } 1388 1389 /** 1390 * FauxRequests shouldn't depend on raw request data (but that could be implemented here) 1391 * @return string 1392 */ 1393 public function getRawQueryString() { 1394 return ''; 1395 } 1396 1397 /** 1398 * FauxRequests shouldn't depend on raw request data (but that could be implemented here) 1399 * @return string 1400 */ 1401 public function getRawPostString() { 1402 return ''; 1403 } 1404 1405 /** 1406 * FauxRequests shouldn't depend on raw request data (but that could be implemented here) 1407 * @return string 1408 */ 1409 public function getRawInput() { 1410 return ''; 1411 } 1412 1413 /** 1414 * @param array $extWhitelist 1415 * @return bool 1416 */ 1417 public function checkUrlExtension( $extWhitelist = array() ) { 1418 return true; 1419 } 1420 1421 /** 1422 * @return string 1423 */ 1424 protected function getRawIP() { 1425 return '127.0.0.1'; 1426 } 1427 } 1428 1429 /** 1430 * Similar to FauxRequest, but only fakes URL parameters and method 1431 * (POST or GET) and use the base request for the remaining stuff 1432 * (cookies, session and headers). 1433 * 1434 * @ingroup HTTP 1435 * @since 1.19 1436 */ 1437 class DerivativeRequest extends FauxRequest { 1438 private $base; 1439 1440 /** 1441 * @param WebRequest $base 1442 * @param array $data Array of *non*-urlencoded key => value pairs, the 1443 * fake GET/POST values 1444 * @param bool $wasPosted Whether to treat the data as POST 1445 */ 1446 public function __construct( WebRequest $base, $data, $wasPosted = false ) { 1447 $this->base = $base; 1448 parent::__construct( $data, $wasPosted ); 1449 } 1450 1451 public function getCookie( $key, $prefix = null, $default = null ) { 1452 return $this->base->getCookie( $key, $prefix, $default ); 1453 } 1454 1455 public function checkSessionCookie() { 1456 return $this->base->checkSessionCookie(); 1457 } 1458 1459 public function getHeader( $name ) { 1460 return $this->base->getHeader( $name ); 1461 } 1462 1463 public function getAllHeaders() { 1464 return $this->base->getAllHeaders(); 1465 } 1466 1467 public function getSessionData( $key ) { 1468 return $this->base->getSessionData( $key ); 1469 } 1470 1471 public function setSessionData( $key, $data ) { 1472 $this->base->setSessionData( $key, $data ); 1473 } 1474 1475 public function getAcceptLang() { 1476 return $this->base->getAcceptLang(); 1477 } 1478 1479 public function getIP() { 1480 return $this->base->getIP(); 1481 } 1482 1483 public function getProtocol() { 1484 return $this->base->getProtocol(); 1485 } 1486 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |