MediaWiki  REL1_22
WebRequest.php
Go to the documentation of this file.
00001 <?php
00038 class WebRequest {
00039     protected $data, $headers = array();
00040 
00045     private $response;
00046 
00051     private $ip;
00052 
00053     public function __construct() {
00057         $this->checkMagicQuotes();
00058 
00059         // POST overrides GET data
00060         // We don't use $_REQUEST here to avoid interference from cookies...
00061         $this->data = $_POST + $_GET;
00062     }
00063 
00079     public static function getPathInfo( $want = 'all' ) {
00080         global $wgUsePathInfo;
00081         // PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892
00082         // And also by Apache 2.x, double slashes are converted to single slashes.
00083         // So we will use REQUEST_URI if possible.
00084         $matches = array();
00085         if ( !empty( $_SERVER['REQUEST_URI'] ) ) {
00086             // Slurp out the path portion to examine...
00087             $url = $_SERVER['REQUEST_URI'];
00088             if ( !preg_match( '!^https?://!', $url ) ) {
00089                 $url = 'http://unused' . $url;
00090             }
00091             wfSuppressWarnings();
00092             $a = parse_url( $url );
00093             wfRestoreWarnings();
00094             if ( $a ) {
00095                 $path = isset( $a['path'] ) ? $a['path'] : '';
00096 
00097                 global $wgScript;
00098                 if ( $path == $wgScript && $want !== 'all' ) {
00099                     // Script inside a rewrite path?
00100                     // Abort to keep from breaking...
00101                     return $matches;
00102                 }
00103 
00104                 $router = new PathRouter;
00105 
00106                 // Raw PATH_INFO style
00107                 $router->add( "$wgScript/$1" );
00108 
00109                 if ( isset( $_SERVER['SCRIPT_NAME'] )
00110                     && preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] ) )
00111                 {
00112                     # Check for SCRIPT_NAME, we handle index.php explicitly
00113                     # But we do have some other .php files such as img_auth.php
00114                     # Don't let root article paths clober the parsing for them
00115                     $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" );
00116                 }
00117 
00118                 global $wgArticlePath;
00119                 if ( $wgArticlePath ) {
00120                     $router->add( $wgArticlePath );
00121                 }
00122 
00123                 global $wgActionPaths;
00124                 if ( $wgActionPaths ) {
00125                     $router->add( $wgActionPaths, array( 'action' => '$key' ) );
00126                 }
00127 
00128                 global $wgVariantArticlePath, $wgContLang;
00129                 if ( $wgVariantArticlePath ) {
00130                     $router->add( $wgVariantArticlePath,
00131                         array( 'variant' => '$2' ),
00132                         array( '$2' => $wgContLang->getVariants() )
00133                     );
00134                 }
00135 
00136                 wfRunHooks( 'WebRequestPathInfoRouter', array( $router ) );
00137 
00138                 $matches = $router->parse( $path );
00139             }
00140         } elseif ( $wgUsePathInfo ) {
00141             if ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
00142                 // Mangled PATH_INFO
00143                 // http://bugs.php.net/bug.php?id=31892
00144                 // Also reported when ini_get('cgi.fix_pathinfo')==false
00145                 $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
00146 
00147             } elseif ( isset( $_SERVER['PATH_INFO'] ) && $_SERVER['PATH_INFO'] != '' ) {
00148                 // Regular old PATH_INFO yay
00149                 $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
00150             }
00151         }
00152 
00153         return $matches;
00154     }
00155 
00162     public static function detectServer() {
00163         list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
00164 
00165         $varNames = array( 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' );
00166         $host = 'localhost';
00167         $port = $stdPort;
00168         foreach ( $varNames as $varName ) {
00169             if ( !isset( $_SERVER[$varName] ) ) {
00170                 continue;
00171             }
00172             $parts = IP::splitHostAndPort( $_SERVER[$varName] );
00173             if ( !$parts ) {
00174                 // Invalid, do not use
00175                 continue;
00176             }
00177             $host = $parts[0];
00178             if ( $parts[1] === false ) {
00179                 if ( isset( $_SERVER['SERVER_PORT'] ) ) {
00180                     $port = $_SERVER['SERVER_PORT'];
00181                 } // else leave it as $stdPort
00182             } else {
00183                 $port = $parts[1];
00184             }
00185             break;
00186         }
00187 
00188         return $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort );
00189     }
00190 
00194     public static function detectProtocolAndStdPort() {
00195         if ( ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ||
00196             ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) &&
00197             $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) ) {
00198             $arr = array( 'https', 443 );
00199         } else {
00200             $arr = array( 'http', 80 );
00201         }
00202         return $arr;
00203     }
00204 
00208     public static function detectProtocol() {
00209         list( $proto, ) = self::detectProtocolAndStdPort();
00210         return $proto;
00211     }
00212 
00220     public function interpolateTitle() {
00221         // bug 16019: title interpolation on API queries is useless and sometimes harmful
00222         if ( defined( 'MW_API' ) ) {
00223             return;
00224         }
00225 
00226         $matches = self::getPathInfo( 'title' );
00227         foreach ( $matches as $key => $val ) {
00228             $this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val;
00229         }
00230     }
00231 
00242     static function extractTitle( $path, $bases, $key = false ) {
00243         foreach ( (array)$bases as $keyValue => $base ) {
00244             // Find the part after $wgArticlePath
00245             $base = str_replace( '$1', '', $base );
00246             $baseLen = strlen( $base );
00247             if ( substr( $path, 0, $baseLen ) == $base ) {
00248                 $raw = substr( $path, $baseLen );
00249                 if ( $raw !== '' ) {
00250                     $matches = array( 'title' => rawurldecode( $raw ) );
00251                     if ( $key ) {
00252                         $matches[$key] = $keyValue;
00253                     }
00254                     return $matches;
00255                 }
00256             }
00257         }
00258         return array();
00259     }
00260 
00272     private function &fix_magic_quotes( &$arr, $topLevel = true ) {
00273         $clean = array();
00274         foreach ( $arr as $key => $val ) {
00275             if ( is_array( $val ) ) {
00276                 $cleanKey = $topLevel ? stripslashes( $key ) : $key;
00277                 $clean[$cleanKey] = $this->fix_magic_quotes( $arr[$key], false );
00278             } else {
00279                 $cleanKey = stripslashes( $key );
00280                 $clean[$cleanKey] = stripslashes( $val );
00281             }
00282         }
00283         $arr = $clean;
00284         return $arr;
00285     }
00286 
00293     private function checkMagicQuotes() {
00294         $mustFixQuotes = function_exists( 'get_magic_quotes_gpc' )
00295             && get_magic_quotes_gpc();
00296         if ( $mustFixQuotes ) {
00297             $this->fix_magic_quotes( $_COOKIE );
00298             $this->fix_magic_quotes( $_ENV );
00299             $this->fix_magic_quotes( $_GET );
00300             $this->fix_magic_quotes( $_POST );
00301             $this->fix_magic_quotes( $_REQUEST );
00302             $this->fix_magic_quotes( $_SERVER );
00303         }
00304     }
00305 
00313     function normalizeUnicode( $data ) {
00314         if ( is_array( $data ) ) {
00315             foreach ( $data as $key => $val ) {
00316                 $data[$key] = $this->normalizeUnicode( $val );
00317             }
00318         } else {
00319             global $wgContLang;
00320             $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal::cleanUp( $data );
00321         }
00322         return $data;
00323     }
00324 
00333     private function getGPCVal( $arr, $name, $default ) {
00334         # PHP is so nice to not touch input data, except sometimes:
00335         # http://us2.php.net/variables.external#language.variables.external.dot-in-names
00336         # Work around PHP *feature* to avoid *bugs* elsewhere.
00337         $name = strtr( $name, '.', '_' );
00338         if ( isset( $arr[$name] ) ) {
00339             global $wgContLang;
00340             $data = $arr[$name];
00341             if ( isset( $_GET[$name] ) && !is_array( $data ) ) {
00342                 # Check for alternate/legacy character encoding.
00343                 if ( isset( $wgContLang ) ) {
00344                     $data = $wgContLang->checkTitleEncoding( $data );
00345                 }
00346             }
00347             $data = $this->normalizeUnicode( $data );
00348             return $data;
00349         } else {
00350             return $default;
00351         }
00352     }
00353 
00364     public function getVal( $name, $default = null ) {
00365         $val = $this->getGPCVal( $this->data, $name, $default );
00366         if ( is_array( $val ) ) {
00367             $val = $default;
00368         }
00369         if ( is_null( $val ) ) {
00370             return $val;
00371         } else {
00372             return (string)$val;
00373         }
00374     }
00375 
00383     public function setVal( $key, $value ) {
00384         $ret = isset( $this->data[$key] ) ? $this->data[$key] : null;
00385         $this->data[$key] = $value;
00386         return $ret;
00387     }
00388 
00395     public function unsetVal( $key ) {
00396         if ( !isset( $this->data[$key] ) ) {
00397             $ret = null;
00398         } else {
00399             $ret = $this->data[$key];
00400             unset( $this->data[$key] );
00401         }
00402         return $ret;
00403     }
00404 
00414     public function getArray( $name, $default = null ) {
00415         $val = $this->getGPCVal( $this->data, $name, $default );
00416         if ( is_null( $val ) ) {
00417             return null;
00418         } else {
00419             return (array)$val;
00420         }
00421     }
00422 
00433     public function getIntArray( $name, $default = null ) {
00434         $val = $this->getArray( $name, $default );
00435         if ( is_array( $val ) ) {
00436             $val = array_map( 'intval', $val );
00437         }
00438         return $val;
00439     }
00440 
00450     public function getInt( $name, $default = 0 ) {
00451         return intval( $this->getVal( $name, $default ) );
00452     }
00453 
00462     public function getIntOrNull( $name ) {
00463         $val = $this->getVal( $name );
00464         return is_numeric( $val )
00465             ? intval( $val )
00466             : null;
00467     }
00468 
00478     public function getBool( $name, $default = false ) {
00479         return (bool)$this->getVal( $name, $default );
00480     }
00481 
00491     public function getFuzzyBool( $name, $default = false ) {
00492         return $this->getBool( $name, $default ) && strcasecmp( $this->getVal( $name ), 'false' ) !== 0;
00493     }
00494 
00503     public function getCheck( $name ) {
00504         # Checkboxes and buttons are only present when clicked
00505         # Presence connotes truth, absence false
00506         return $this->getVal( $name, null ) !== null;
00507     }
00508 
00521     public function getText( $name, $default = '' ) {
00522         global $wgContLang;
00523         $val = $this->getVal( $name, $default );
00524         return str_replace( "\r\n", "\n",
00525             $wgContLang->recodeInput( $val ) );
00526     }
00527 
00535     public function getValues() {
00536         $names = func_get_args();
00537         if ( count( $names ) == 0 ) {
00538             $names = array_keys( $this->data );
00539         }
00540 
00541         $retVal = array();
00542         foreach ( $names as $name ) {
00543             $value = $this->getGPCVal( $this->data, $name, null );
00544             if ( !is_null( $value ) ) {
00545                 $retVal[$name] = $value;
00546             }
00547         }
00548         return $retVal;
00549     }
00550 
00557     public function getValueNames( $exclude = array() ) {
00558         return array_diff( array_keys( $this->getValues() ), $exclude );
00559     }
00560 
00567     public function getQueryValues() {
00568         return $_GET;
00569     }
00570 
00577     public function getRawQueryString() {
00578         return $_SERVER['QUERY_STRING'];
00579     }
00580 
00587     public function getRawPostString() {
00588         if ( !$this->wasPosted() ) {
00589             return '';
00590         }
00591         return $this->getRawInput();
00592     }
00593 
00601     public function getRawInput() {
00602         static $input = false;
00603         if ( $input === false ) {
00604             $input = file_get_contents( 'php://input' );
00605         }
00606         return $input;
00607     }
00608 
00614     public function getMethod() {
00615         return isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET';
00616     }
00617 
00627     public function wasPosted() {
00628         return $this->getMethod() == 'POST';
00629     }
00630 
00642     public function checkSessionCookie() {
00643         return isset( $_COOKIE[session_name()] );
00644     }
00645 
00654     public function getCookie( $key, $prefix = null, $default = null ) {
00655         if ( $prefix === null ) {
00656             global $wgCookiePrefix;
00657             $prefix = $wgCookiePrefix;
00658         }
00659         return $this->getGPCVal( $_COOKIE, $prefix . $key, $default );
00660     }
00661 
00669     public function getRequestURL() {
00670         if ( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
00671             $base = $_SERVER['REQUEST_URI'];
00672         } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
00673             // Probably IIS; doesn't set REQUEST_URI
00674             $base = $_SERVER['HTTP_X_ORIGINAL_URL'];
00675         } elseif ( isset( $_SERVER['SCRIPT_NAME'] ) ) {
00676             $base = $_SERVER['SCRIPT_NAME'];
00677             if ( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
00678                 $base .= '?' . $_SERVER['QUERY_STRING'];
00679             }
00680         } else {
00681             // This shouldn't happen!
00682             throw new MWException( "Web server doesn't provide either " .
00683                 "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
00684                 "of your web server configuration to http://bugzilla.wikimedia.org/" );
00685         }
00686         // User-agents should not send a fragment with the URI, but
00687         // if they do, and the web server passes it on to us, we
00688         // need to strip it or we get false-positive redirect loops
00689         // or weird output URLs
00690         $hash = strpos( $base, '#' );
00691         if ( $hash !== false ) {
00692             $base = substr( $base, 0, $hash );
00693         }
00694 
00695         if ( $base[0] == '/' ) {
00696             // More than one slash will look like it is protocol relative
00697             return preg_replace( '!^/+!', '/', $base );
00698         } else {
00699             // We may get paths with a host prepended; strip it.
00700             return preg_replace( '!^[^:]+://[^/]+/+!', '/', $base );
00701         }
00702     }
00703 
00714     public function getFullRequestURL() {
00715         return wfExpandUrl( $this->getRequestURL(), PROTO_CURRENT );
00716     }
00717 
00724     public function appendQuery( $query ) {
00725         return $this->appendQueryArray( wfCgiToArray( $query ) );
00726     }
00727 
00735     public function escapeAppendQuery( $query ) {
00736         return htmlspecialchars( $this->appendQuery( $query ) );
00737     }
00738 
00745     public function appendQueryValue( $key, $value, $onlyquery = false ) {
00746         return $this->appendQueryArray( array( $key => $value ), $onlyquery );
00747     }
00748 
00757     public function appendQueryArray( $array, $onlyquery = false ) {
00758         global $wgTitle;
00759         $newquery = $this->getQueryValues();
00760         unset( $newquery['title'] );
00761         $newquery = array_merge( $newquery, $array );
00762         $query = wfArrayToCgi( $newquery );
00763         return $onlyquery ? $query : $wgTitle->getLocalURL( $query );
00764     }
00765 
00775     public function getLimitOffset( $deflimit = 50, $optionname = 'rclimit' ) {
00776         global $wgUser;
00777 
00778         $limit = $this->getInt( 'limit', 0 );
00779         if ( $limit < 0 ) {
00780             $limit = 0;
00781         }
00782         if ( ( $limit == 0 ) && ( $optionname != '' ) ) {
00783             $limit = $wgUser->getIntOption( $optionname );
00784         }
00785         if ( $limit <= 0 ) {
00786             $limit = $deflimit;
00787         }
00788         if ( $limit > 5000 ) {
00789             $limit = 5000; # We have *some* limits...
00790         }
00791 
00792         $offset = $this->getInt( 'offset', 0 );
00793         if ( $offset < 0 ) {
00794             $offset = 0;
00795         }
00796 
00797         return array( $limit, $offset );
00798     }
00799 
00806     public function getFileTempname( $key ) {
00807         $file = new WebRequestUpload( $this, $key );
00808         return $file->getTempName();
00809     }
00810 
00818     public function getFileSize( $key ) {
00819         wfDeprecated( __METHOD__, '1.17' );
00820         $file = new WebRequestUpload( $this, $key );
00821         return $file->getSize();
00822     }
00823 
00830     public function getUploadError( $key ) {
00831         $file = new WebRequestUpload( $this, $key );
00832         return $file->getError();
00833     }
00834 
00846     public function getFileName( $key ) {
00847         $file = new WebRequestUpload( $this, $key );
00848         return $file->getName();
00849     }
00850 
00857     public function getUpload( $key ) {
00858         return new WebRequestUpload( $this, $key );
00859     }
00860 
00867     public function response() {
00868         /* Lazy initialization of response object for this request */
00869         if ( !is_object( $this->response ) ) {
00870             $class = ( $this instanceof FauxRequest ) ? 'FauxResponse' : 'WebResponse';
00871             $this->response = new $class();
00872         }
00873         return $this->response;
00874     }
00875 
00879     private function initHeaders() {
00880         if ( count( $this->headers ) ) {
00881             return;
00882         }
00883 
00884         $apacheHeaders = function_exists( 'apache_request_headers' ) ? apache_request_headers() : false;
00885         if ( $apacheHeaders ) {
00886             foreach ( $apacheHeaders as $tempName => $tempValue ) {
00887                 $this->headers[strtoupper( $tempName )] = $tempValue;
00888             }
00889         } else {
00890             foreach ( $_SERVER as $name => $value ) {
00891                 if ( substr( $name, 0, 5 ) === 'HTTP_' ) {
00892                     $name = str_replace( '_', '-', substr( $name, 5 ) );
00893                     $this->headers[$name] = $value;
00894                 } elseif ( $name === 'CONTENT_LENGTH' ) {
00895                     $this->headers['CONTENT-LENGTH'] = $value;
00896                 }
00897             }
00898         }
00899     }
00900 
00906     public function getAllHeaders() {
00907         $this->initHeaders();
00908         return $this->headers;
00909     }
00910 
00917     public function getHeader( $name ) {
00918         $this->initHeaders();
00919         $name = strtoupper( $name );
00920         if ( isset( $this->headers[$name] ) ) {
00921             return $this->headers[$name];
00922         } else {
00923             return false;
00924         }
00925     }
00926 
00933     public function getSessionData( $key ) {
00934         if ( !isset( $_SESSION[$key] ) ) {
00935             return null;
00936         }
00937         return $_SESSION[$key];
00938     }
00939 
00946     public function setSessionData( $key, $data ) {
00947         $_SESSION[$key] = $data;
00948     }
00949 
00960     public function checkUrlExtension( $extWhitelist = array() ) {
00961         global $wgScriptExtension;
00962         $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
00963         if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
00964             if ( !$this->wasPosted() ) {
00965                 $newUrl = IEUrlExtension::fixUrlForIE6(
00966                     $this->getFullRequestURL(), $extWhitelist );
00967                 if ( $newUrl !== false ) {
00968                     $this->doSecurityRedirect( $newUrl );
00969                     return false;
00970                 }
00971             }
00972             throw new HttpError( 403,
00973                 'Invalid file extension found in the path info or query string.' );
00974         }
00975         return true;
00976     }
00977 
00985     protected function doSecurityRedirect( $url ) {
00986         header( 'Location: ' . $url );
00987         header( 'Content-Type: text/html' );
00988         $encUrl = htmlspecialchars( $url );
00989         echo <<<HTML
00990 <html>
00991 <head>
00992 <title>Security redirect</title>
00993 </head>
00994 <body>
00995 <h1>Security redirect</h1>
00996 <p>
00997 We can't serve non-HTML content from the URL you have requested, because
00998 Internet Explorer would interpret it as an incorrect and potentially dangerous
00999 content type.</p>
01000 <p>Instead, please use <a href="$encUrl">this URL</a>, which is the same as the URL you have requested, except that
01001 "&amp;*" is appended. This prevents Internet Explorer from seeing a bogus file
01002 extension.
01003 </p>
01004 </body>
01005 </html>
01006 HTML;
01007         echo "\n";
01008         return true;
01009     }
01010 
01033     public function isPathInfoBad( $extWhitelist = array() ) {
01034         wfDeprecated( __METHOD__, '1.17' );
01035         global $wgScriptExtension;
01036         $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
01037         return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
01038     }
01039 
01048     public function getAcceptLang() {
01049         // Modified version of code found at http://www.thefutureoftheweb.com/blog/use-accept-language-header
01050         $acceptLang = $this->getHeader( 'Accept-Language' );
01051         if ( !$acceptLang ) {
01052             return array();
01053         }
01054 
01055         // Return the language codes in lower case
01056         $acceptLang = strtolower( $acceptLang );
01057 
01058         // Break up string into pieces (languages and q factors)
01059         $lang_parse = null;
01060         preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
01061             $acceptLang, $lang_parse );
01062 
01063         if ( !count( $lang_parse[1] ) ) {
01064             return array();
01065         }
01066 
01067         $langcodes = $lang_parse[1];
01068         $qvalues = $lang_parse[4];
01069         $indices = range( 0, count( $lang_parse[1] ) - 1 );
01070 
01071         // Set default q factor to 1
01072         foreach ( $indices as $index ) {
01073             if ( $qvalues[$index] === '' ) {
01074                 $qvalues[$index] = 1;
01075             } elseif ( $qvalues[$index] == 0 ) {
01076                 unset( $langcodes[$index], $qvalues[$index], $indices[$index] );
01077             }
01078         }
01079 
01080         // Sort list. First by $qvalues, then by order. Reorder $langcodes the same way
01081         array_multisort( $qvalues, SORT_DESC, SORT_NUMERIC, $indices, $langcodes );
01082 
01083         // Create a list like "en" => 0.8
01084         $langs = array_combine( $langcodes, $qvalues );
01085 
01086         return $langs;
01087     }
01088 
01097     protected function getRawIP() {
01098         if ( !isset( $_SERVER['REMOTE_ADDR'] ) ) {
01099             return null;
01100         }
01101 
01102         if ( is_array( $_SERVER['REMOTE_ADDR'] ) || strpos( $_SERVER['REMOTE_ADDR'], ',' ) !== false ) {
01103             throw new MWException( __METHOD__ . " : Could not determine the remote IP address due to multiple values." );
01104         } else {
01105             $ipchain = $_SERVER['REMOTE_ADDR'];
01106         }
01107 
01108         return IP::canonicalize( $ipchain );
01109     }
01110 
01120     public function getIP() {
01121         global $wgUsePrivateIPs;
01122 
01123         # Return cached result
01124         if ( $this->ip !== null ) {
01125             return $this->ip;
01126         }
01127 
01128         # collect the originating ips
01129         $ip = $this->getRawIP();
01130 
01131         # Append XFF
01132         $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
01133         if ( $forwardedFor !== false ) {
01134             $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
01135             $ipchain = array_reverse( $ipchain );
01136             if ( $ip ) {
01137                 array_unshift( $ipchain, $ip );
01138             }
01139 
01140             # Step through XFF list and find the last address in the list which is a
01141             # trusted server. Set $ip to the IP address given by that trusted server,
01142             # unless the address is not sensible (e.g. private). However, prefer private
01143             # IP addresses over proxy servers controlled by this site (more sensible).
01144             foreach ( $ipchain as $i => $curIP ) {
01145                 $curIP = IP::sanitizeIP( IP::canonicalize( $curIP ) );
01146                 if ( wfIsTrustedProxy( $curIP ) && isset( $ipchain[$i + 1] ) ) {
01147                     if ( wfIsConfiguredProxy( $curIP ) || // bug 48919; treat IP as sane
01148                         IP::isPublic( $ipchain[$i + 1] ) ||
01149                         $wgUsePrivateIPs
01150                     ) {
01151                         $nextIP = IP::canonicalize( $ipchain[$i + 1] );
01152                         if ( !$nextIP && wfIsConfiguredProxy( $ip ) ) {
01153                             // We have not yet made it past CDN/proxy servers of this site,
01154                             // so either they are misconfigured or there is some IP spoofing.
01155                             throw new MWException( "Invalid IP given in XFF '$forwardedFor'." );
01156                         }
01157                         $ip = $nextIP;
01158                         continue;
01159                     }
01160                 }
01161                 break;
01162             }
01163         }
01164 
01165         # Allow extensions to improve our guess
01166         wfRunHooks( 'GetIP', array( &$ip ) );
01167 
01168         if ( !$ip ) {
01169             throw new MWException( "Unable to determine IP." );
01170         }
01171 
01172         wfDebug( "IP: $ip\n" );
01173         $this->ip = $ip;
01174         return $ip;
01175     }
01176 
01182     public function setIP( $ip ) {
01183         $this->ip = $ip;
01184     }
01185 }
01186 
01190 class WebRequestUpload {
01191     protected $request;
01192     protected $doesExist;
01193     protected $fileInfo;
01194 
01201     public function __construct( $request, $key ) {
01202         $this->request = $request;
01203         $this->doesExist = isset( $_FILES[$key] );
01204         if ( $this->doesExist ) {
01205             $this->fileInfo = $_FILES[$key];
01206         }
01207     }
01208 
01214     public function exists() {
01215         return $this->doesExist;
01216     }
01217 
01223     public function getName() {
01224         if ( !$this->exists() ) {
01225             return null;
01226         }
01227 
01228         global $wgContLang;
01229         $name = $this->fileInfo['name'];
01230 
01231         # Safari sends filenames in HTML-encoded Unicode form D...
01232         # Horrid and evil! Let's try to make some kind of sense of it.
01233         $name = Sanitizer::decodeCharReferences( $name );
01234         $name = $wgContLang->normalize( $name );
01235         wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" );
01236         return $name;
01237     }
01238 
01244     public function getSize() {
01245         if ( !$this->exists() ) {
01246             return 0;
01247         }
01248 
01249         return $this->fileInfo['size'];
01250     }
01251 
01257     public function getTempName() {
01258         if ( !$this->exists() ) {
01259             return null;
01260         }
01261 
01262         return $this->fileInfo['tmp_name'];
01263     }
01264 
01271     public function getError() {
01272         if ( !$this->exists() ) {
01273             return 0; # UPLOAD_ERR_OK
01274         }
01275 
01276         return $this->fileInfo['error'];
01277     }
01278 
01285     public function isIniSizeOverflow() {
01286         if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) {
01287             # PHP indicated that upload_max_filesize is exceeded
01288             return true;
01289         }
01290 
01291         $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
01292         if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) {
01293             # post_max_size is exceeded
01294             return true;
01295         }
01296 
01297         return false;
01298     }
01299 }
01300 
01306 class FauxRequest extends WebRequest {
01307     private $wasPosted = false;
01308     private $session = array();
01309 
01317     public function __construct( $data = array(), $wasPosted = false, $session = null ) {
01318         if ( is_array( $data ) ) {
01319             $this->data = $data;
01320         } else {
01321             throw new MWException( "FauxRequest() got bogus data" );
01322         }
01323         $this->wasPosted = $wasPosted;
01324         if ( $session ) {
01325             $this->session = $session;
01326         }
01327     }
01328 
01333     private function notImplemented( $method ) {
01334         throw new MWException( "{$method}() not implemented" );
01335     }
01336 
01342     public function getText( $name, $default = '' ) {
01343         # Override; don't recode since we're using internal data
01344         return (string)$this->getVal( $name, $default );
01345     }
01346 
01350     public function getValues() {
01351         return $this->data;
01352     }
01353 
01357     public function getQueryValues() {
01358         if ( $this->wasPosted ) {
01359             return array();
01360         } else {
01361             return $this->data;
01362         }
01363     }
01364 
01365     public function getMethod() {
01366         return $this->wasPosted ? 'POST' : 'GET';
01367     }
01368 
01372     public function wasPosted() {
01373         return $this->wasPosted;
01374     }
01375 
01376     public function getCookie( $key, $prefix = null, $default = null ) {
01377         return $default;
01378     }
01379 
01380     public function checkSessionCookie() {
01381         return false;
01382     }
01383 
01384     public function getRequestURL() {
01385         $this->notImplemented( __METHOD__ );
01386     }
01387 
01392     public function getHeader( $name ) {
01393         $name = strtoupper( $name );
01394         return isset( $this->headers[$name] ) ? $this->headers[$name] : false;
01395     }
01396 
01401     public function setHeader( $name, $val ) {
01402         $name = strtoupper( $name );
01403         $this->headers[$name] = $val;
01404     }
01405 
01410     public function getSessionData( $key ) {
01411         if ( isset( $this->session[$key] ) ) {
01412             return $this->session[$key];
01413         }
01414         return null;
01415     }
01416 
01421     public function setSessionData( $key, $data ) {
01422         $this->session[$key] = $data;
01423     }
01424 
01428     public function getSessionArray() {
01429         return $this->session;
01430     }
01431 
01436     public function isPathInfoBad( $extWhitelist = array() ) {
01437         return false;
01438     }
01439 
01444     public function getRawQueryString() {
01445         return '';
01446     }
01447 
01452     public function getRawPostString() {
01453         return '';
01454     }
01455 
01460     public function getRawInput() {
01461         return '';
01462     }
01463 
01468     public function checkUrlExtension( $extWhitelist = array() ) {
01469         return true;
01470     }
01471 
01475     protected function getRawIP() {
01476         return '127.0.0.1';
01477     }
01478 }
01479 
01488 class DerivativeRequest extends FauxRequest {
01489     private $base;
01490 
01491     public function __construct( WebRequest $base, $data, $wasPosted = false ) {
01492         $this->base = $base;
01493         parent::__construct( $data, $wasPosted );
01494     }
01495 
01496     public function getCookie( $key, $prefix = null, $default = null ) {
01497         return $this->base->getCookie( $key, $prefix, $default );
01498     }
01499 
01500     public function checkSessionCookie() {
01501         return $this->base->checkSessionCookie();
01502     }
01503 
01504     public function getHeader( $name ) {
01505         return $this->base->getHeader( $name );
01506     }
01507 
01508     public function getAllHeaders() {
01509         return $this->base->getAllHeaders();
01510     }
01511 
01512     public function getSessionData( $key ) {
01513         return $this->base->getSessionData( $key );
01514     }
01515 
01516     public function setSessionData( $key, $data ) {
01517         $this->base->setSessionData( $key, $data );
01518     }
01519 
01520     public function getAcceptLang() {
01521         return $this->base->getAcceptLang();
01522     }
01523 
01524     public function getIP() {
01525         return $this->base->getIP();
01526     }
01527 }