[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> WebRequest.php (source)

   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 "&amp;*" 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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1