MediaWiki  REL1_22
HttpFunctions.php
Go to the documentation of this file.
00001 <?php
00032 class Http {
00033     static $httpEngine = false;
00034 
00060     public static function request( $method, $url, $options = array() ) {
00061         wfDebug( "HTTP: $method: $url\n" );
00062         wfProfileIn( __METHOD__ . "-$method" );
00063 
00064         $options['method'] = strtoupper( $method );
00065 
00066         if ( !isset( $options['timeout'] ) ) {
00067             $options['timeout'] = 'default';
00068         }
00069         if ( !isset( $options['connectTimeout'] ) ) {
00070             $options['connectTimeout'] = 'default';
00071         }
00072 
00073         $req = MWHttpRequest::factory( $url, $options );
00074         $status = $req->execute();
00075 
00076         $content = false;
00077         if ( $status->isOK() ) {
00078             $content = $req->getContent();
00079         }
00080         wfProfileOut( __METHOD__ . "-$method" );
00081         return $content;
00082     }
00083 
00093     public static function get( $url, $timeout = 'default', $options = array() ) {
00094         $options['timeout'] = $timeout;
00095         return Http::request( 'GET', $url, $options );
00096     }
00097 
00106     public static function post( $url, $options = array() ) {
00107         return Http::request( 'POST', $url, $options );
00108     }
00109 
00116     public static function isLocalURL( $url ) {
00117         global $wgCommandLineMode, $wgConf;
00118 
00119         if ( $wgCommandLineMode ) {
00120             return false;
00121         }
00122 
00123         // Extract host part
00124         $matches = array();
00125         if ( preg_match( '!^http://([\w.-]+)[/:].*$!', $url, $matches ) ) {
00126             $host = $matches[1];
00127             // Split up dotwise
00128             $domainParts = explode( '.', $host );
00129             // Check if this domain or any superdomain is listed in $wgConf as a local virtual host
00130             $domainParts = array_reverse( $domainParts );
00131 
00132             $domain = '';
00133             for ( $i = 0; $i < count( $domainParts ); $i++ ) {
00134                 $domainPart = $domainParts[$i];
00135                 if ( $i == 0 ) {
00136                     $domain = $domainPart;
00137                 } else {
00138                     $domain = $domainPart . '.' . $domain;
00139                 }
00140 
00141                 if ( $wgConf->isLocalVHost( $domain ) ) {
00142                     return true;
00143                 }
00144             }
00145         }
00146 
00147         return false;
00148     }
00149 
00154     public static function userAgent() {
00155         global $wgVersion;
00156         return "MediaWiki/$wgVersion";
00157     }
00158 
00171     public static function isValidURI( $uri ) {
00172         return preg_match(
00173             '/^https?:\/\/[^\/\s]\S*$/D',
00174             $uri
00175         );
00176     }
00177 }
00178 
00186 class MWHttpRequest {
00187     const SUPPORTS_FILE_POSTS = false;
00188 
00189     protected $content;
00190     protected $timeout = 'default';
00191     protected $headersOnly = null;
00192     protected $postData = null;
00193     protected $proxy = null;
00194     protected $noProxy = false;
00195     protected $sslVerifyHost = true;
00196     protected $sslVerifyCert = true;
00197     protected $caInfo = null;
00198     protected $method = "GET";
00199     protected $reqHeaders = array();
00200     protected $url;
00201     protected $parsedUrl;
00202     protected $callback;
00203     protected $maxRedirects = 5;
00204     protected $followRedirects = false;
00205 
00209     protected $cookieJar;
00210 
00211     protected $headerList = array();
00212     protected $respVersion = "0.9";
00213     protected $respStatus = "200 Ok";
00214     protected $respHeaders = array();
00215 
00216     public $status;
00217 
00222     protected function __construct( $url, $options = array() ) {
00223         global $wgHTTPTimeout, $wgHTTPConnectTimeout;
00224 
00225         $this->url = wfExpandUrl( $url, PROTO_HTTP );
00226         $this->parsedUrl = wfParseUrl( $this->url );
00227 
00228         if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
00229             $this->status = Status::newFatal( 'http-invalid-url' );
00230         } else {
00231             $this->status = Status::newGood( 100 ); // continue
00232         }
00233 
00234         if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
00235             $this->timeout = $options['timeout'];
00236         } else {
00237             $this->timeout = $wgHTTPTimeout;
00238         }
00239         if ( isset( $options['connectTimeout'] ) && $options['connectTimeout'] != 'default' ) {
00240             $this->connectTimeout = $options['connectTimeout'];
00241         } else {
00242             $this->connectTimeout = $wgHTTPConnectTimeout;
00243         }
00244         if ( isset( $options['userAgent'] ) ) {
00245             $this->setUserAgent( $options['userAgent'] );
00246         }
00247 
00248         $members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
00249                 "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" );
00250 
00251         foreach ( $members as $o ) {
00252             if ( isset( $options[$o] ) ) {
00253                 // ensure that MWHttpRequest::method is always
00254                 // uppercased. Bug 36137
00255                 if ( $o == 'method' ) {
00256                     $options[$o] = strtoupper( $options[$o] );
00257                 }
00258                 $this->$o = $options[$o];
00259             }
00260         }
00261 
00262         if ( $this->noProxy ) {
00263             $this->proxy = ''; // noProxy takes precedence
00264         }
00265     }
00266 
00272     public static function canMakeRequests() {
00273         return function_exists( 'curl_init' ) || wfIniGetBool( 'allow_url_fopen' );
00274     }
00275 
00284     public static function factory( $url, $options = null ) {
00285         if ( !Http::$httpEngine ) {
00286             Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
00287         } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
00288             throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
00289                 ' Http::$httpEngine is set to "curl"' );
00290         }
00291 
00292         switch ( Http::$httpEngine ) {
00293             case 'curl':
00294                 return new CurlHttpRequest( $url, $options );
00295             case 'php':
00296                 if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
00297                     throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' .
00298                         ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
00299                 }
00300                 return new PhpHttpRequest( $url, $options );
00301             default:
00302                 throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
00303         }
00304     }
00305 
00311     public function getContent() {
00312         return $this->content;
00313     }
00314 
00321     public function setData( $args ) {
00322         $this->postData = $args;
00323     }
00324 
00330     public function proxySetup() {
00331         global $wgHTTPProxy;
00332 
00333         // If there is an explicit proxy set and proxies are not disabled, then use it
00334         if ( $this->proxy && !$this->noProxy ) {
00335             return;
00336         }
00337 
00338         // Otherwise, fallback to $wgHTTPProxy/http_proxy (when set) if this is not a machine
00339         // local URL and proxies are not disabled
00340         if ( Http::isLocalURL( $this->url ) || $this->noProxy ) {
00341             $this->proxy = '';
00342         } elseif ( $wgHTTPProxy ) {
00343             $this->proxy = $wgHTTPProxy;
00344         } elseif ( getenv( "http_proxy" ) ) {
00345             $this->proxy = getenv( "http_proxy" );
00346         }
00347     }
00348 
00352     public function setReferer( $url ) {
00353         $this->setHeader( 'Referer', $url );
00354     }
00355 
00360     public function setUserAgent( $UA ) {
00361         $this->setHeader( 'User-Agent', $UA );
00362     }
00363 
00369     public function setHeader( $name, $value ) {
00370         // I feel like I should normalize the case here...
00371         $this->reqHeaders[$name] = $value;
00372     }
00373 
00378     public function getHeaderList() {
00379         $list = array();
00380 
00381         if ( $this->cookieJar ) {
00382             $this->reqHeaders['Cookie'] =
00383                 $this->cookieJar->serializeToHttpRequest(
00384                     $this->parsedUrl['path'],
00385                     $this->parsedUrl['host']
00386                 );
00387         }
00388 
00389         foreach ( $this->reqHeaders as $name => $value ) {
00390             $list[] = "$name: $value";
00391         }
00392 
00393         return $list;
00394     }
00395 
00414     public function setCallback( $callback ) {
00415         if ( !is_callable( $callback ) ) {
00416             throw new MWException( 'Invalid MwHttpRequest callback' );
00417         }
00418         $this->callback = $callback;
00419     }
00420 
00429     public function read( $fh, $content ) {
00430         $this->content .= $content;
00431         return strlen( $content );
00432     }
00433 
00439     public function execute() {
00440         global $wgTitle;
00441 
00442         wfProfileIn( __METHOD__ );
00443 
00444         $this->content = "";
00445 
00446         if ( strtoupper( $this->method ) == "HEAD" ) {
00447             $this->headersOnly = true;
00448         }
00449 
00450         if ( is_object( $wgTitle ) && !isset( $this->reqHeaders['Referer'] ) ) {
00451             $this->setReferer( wfExpandUrl( $wgTitle->getFullURL(), PROTO_CURRENT ) );
00452         }
00453 
00454         $this->proxySetup(); // set up any proxy as needed
00455 
00456         if ( !$this->callback ) {
00457             $this->setCallback( array( $this, 'read' ) );
00458         }
00459 
00460         if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
00461             $this->setUserAgent( Http::userAgent() );
00462         }
00463 
00464         wfProfileOut( __METHOD__ );
00465     }
00466 
00472     protected function parseHeader() {
00473         wfProfileIn( __METHOD__ );
00474 
00475         $lastname = "";
00476 
00477         foreach ( $this->headerList as $header ) {
00478             if ( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
00479                 $this->respVersion = $match[1];
00480                 $this->respStatus = $match[2];
00481             } elseif ( preg_match( "#^[ \t]#", $header ) ) {
00482                 $last = count( $this->respHeaders[$lastname] ) - 1;
00483                 $this->respHeaders[$lastname][$last] .= "\r\n$header";
00484             } elseif ( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
00485                 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
00486                 $lastname = strtolower( $match[1] );
00487             }
00488         }
00489 
00490         $this->parseCookies();
00491 
00492         wfProfileOut( __METHOD__ );
00493     }
00494 
00503     protected function setStatus() {
00504         if ( !$this->respHeaders ) {
00505             $this->parseHeader();
00506         }
00507 
00508         if ( (int)$this->respStatus > 399 ) {
00509             list( $code, $message ) = explode( " ", $this->respStatus, 2 );
00510             $this->status->fatal( "http-bad-status", $code, $message );
00511         }
00512     }
00513 
00521     public function getStatus() {
00522         if ( !$this->respHeaders ) {
00523             $this->parseHeader();
00524         }
00525 
00526         return (int)$this->respStatus;
00527     }
00528 
00534     public function isRedirect() {
00535         if ( !$this->respHeaders ) {
00536             $this->parseHeader();
00537         }
00538 
00539         $status = (int)$this->respStatus;
00540 
00541         if ( $status >= 300 && $status <= 303 ) {
00542             return true;
00543         }
00544 
00545         return false;
00546     }
00547 
00556     public function getResponseHeaders() {
00557         if ( !$this->respHeaders ) {
00558             $this->parseHeader();
00559         }
00560 
00561         return $this->respHeaders;
00562     }
00563 
00570     public function getResponseHeader( $header ) {
00571         if ( !$this->respHeaders ) {
00572             $this->parseHeader();
00573         }
00574 
00575         if ( isset( $this->respHeaders[strtolower( $header )] ) ) {
00576             $v = $this->respHeaders[strtolower( $header )];
00577             return $v[count( $v ) - 1];
00578         }
00579 
00580         return null;
00581     }
00582 
00588     public function setCookieJar( $jar ) {
00589         $this->cookieJar = $jar;
00590     }
00591 
00597     public function getCookieJar() {
00598         if ( !$this->respHeaders ) {
00599             $this->parseHeader();
00600         }
00601 
00602         return $this->cookieJar;
00603     }
00604 
00614     public function setCookie( $name, $value = null, $attr = null ) {
00615         if ( !$this->cookieJar ) {
00616             $this->cookieJar = new CookieJar;
00617         }
00618 
00619         $this->cookieJar->setCookie( $name, $value, $attr );
00620     }
00621 
00625     protected function parseCookies() {
00626         wfProfileIn( __METHOD__ );
00627 
00628         if ( !$this->cookieJar ) {
00629             $this->cookieJar = new CookieJar;
00630         }
00631 
00632         if ( isset( $this->respHeaders['set-cookie'] ) ) {
00633             $url = parse_url( $this->getFinalUrl() );
00634             foreach ( $this->respHeaders['set-cookie'] as $cookie ) {
00635                 $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
00636             }
00637         }
00638 
00639         wfProfileOut( __METHOD__ );
00640     }
00641 
00654     public function getFinalUrl() {
00655         $headers = $this->getResponseHeaders();
00656 
00657         //return full url (fix for incorrect but handled relative location)
00658         if ( isset( $headers['location'] ) ) {
00659             $locations = $headers['location'];
00660             $domain = '';
00661             $foundRelativeURI = false;
00662             $countLocations = count( $locations );
00663 
00664             for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
00665                 $url = parse_url( $locations[$i] );
00666 
00667                 if ( isset( $url['host'] ) ) {
00668                     $domain = $url['scheme'] . '://' . $url['host'];
00669                     break; //found correct URI (with host)
00670                 } else {
00671                     $foundRelativeURI = true;
00672                 }
00673             }
00674 
00675             if ( $foundRelativeURI ) {
00676                 if ( $domain ) {
00677                     return $domain . $locations[$countLocations - 1];
00678                 } else {
00679                     $url = parse_url( $this->url );
00680                     if ( isset( $url['host'] ) ) {
00681                         return $url['scheme'] . '://' . $url['host'] . $locations[$countLocations - 1];
00682                     }
00683                 }
00684             } else {
00685                 return $locations[$countLocations - 1];
00686             }
00687         }
00688 
00689         return $this->url;
00690     }
00691 
00697     public function canFollowRedirects() {
00698         return true;
00699     }
00700 }
00701 
00705 class CurlHttpRequest extends MWHttpRequest {
00706     const SUPPORTS_FILE_POSTS = true;
00707 
00708     protected $curlOptions = array();
00709     protected $headerText = "";
00710 
00716     protected function readHeader( $fh, $content ) {
00717         $this->headerText .= $content;
00718         return strlen( $content );
00719     }
00720 
00721     public function execute() {
00722         wfProfileIn( __METHOD__ );
00723 
00724         parent::execute();
00725 
00726         if ( !$this->status->isOK() ) {
00727             wfProfileOut( __METHOD__ );
00728             return $this->status;
00729         }
00730 
00731         $this->curlOptions[CURLOPT_PROXY] = $this->proxy;
00732         $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
00733 
00734         // Only supported in curl >= 7.16.2
00735         if ( defined( 'CURLOPT_CONNECTTIMEOUT_MS' ) ) {
00736             $this->curlOptions[CURLOPT_CONNECTTIMEOUT_MS] = $this->connectTimeout * 1000;
00737         }
00738 
00739         $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
00740         $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
00741         $this->curlOptions[CURLOPT_HEADERFUNCTION] = array( $this, "readHeader" );
00742         $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
00743         $this->curlOptions[CURLOPT_ENCODING] = ""; # Enable compression
00744 
00745         /* not sure these two are actually necessary */
00746         if ( isset( $this->reqHeaders['Referer'] ) ) {
00747             $this->curlOptions[CURLOPT_REFERER] = $this->reqHeaders['Referer'];
00748         }
00749         $this->curlOptions[CURLOPT_USERAGENT] = $this->reqHeaders['User-Agent'];
00750 
00751         $this->curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->sslVerifyHost ? 2 : 0;
00752         $this->curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->sslVerifyCert;
00753 
00754         if ( $this->caInfo ) {
00755             $this->curlOptions[CURLOPT_CAINFO] = $this->caInfo;
00756         }
00757 
00758         if ( $this->headersOnly ) {
00759             $this->curlOptions[CURLOPT_NOBODY] = true;
00760             $this->curlOptions[CURLOPT_HEADER] = true;
00761         } elseif ( $this->method == 'POST' ) {
00762             $this->curlOptions[CURLOPT_POST] = true;
00763             $this->curlOptions[CURLOPT_POSTFIELDS] = $this->postData;
00764             // Suppress 'Expect: 100-continue' header, as some servers
00765             // will reject it with a 417 and Curl won't auto retry
00766             // with HTTP 1.0 fallback
00767             $this->reqHeaders['Expect'] = '';
00768         } else {
00769             $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $this->method;
00770         }
00771 
00772         $this->curlOptions[CURLOPT_HTTPHEADER] = $this->getHeaderList();
00773 
00774         $curlHandle = curl_init( $this->url );
00775 
00776         if ( !curl_setopt_array( $curlHandle, $this->curlOptions ) ) {
00777             wfProfileOut( __METHOD__ );
00778             throw new MWException( "Error setting curl options." );
00779         }
00780 
00781         if ( $this->followRedirects && $this->canFollowRedirects() ) {
00782             wfSuppressWarnings();
00783             if ( ! curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
00784                 wfDebug( __METHOD__ . ": Couldn't set CURLOPT_FOLLOWLOCATION. " .
00785                     "Probably safe_mode or open_basedir is set.\n" );
00786                 // Continue the processing. If it were in curl_setopt_array,
00787                 // processing would have halted on its entry
00788             }
00789             wfRestoreWarnings();
00790         }
00791 
00792         $curlRes = curl_exec( $curlHandle );
00793         if ( curl_errno( $curlHandle ) == CURLE_OPERATION_TIMEOUTED ) {
00794             $this->status->fatal( 'http-timed-out', $this->url );
00795         } elseif ( $curlRes === false ) {
00796             $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
00797         } else {
00798             $this->headerList = explode( "\r\n", $this->headerText );
00799         }
00800 
00801         curl_close( $curlHandle );
00802 
00803         $this->parseHeader();
00804         $this->setStatus();
00805 
00806         wfProfileOut( __METHOD__ );
00807 
00808         return $this->status;
00809     }
00810 
00814     public function canFollowRedirects() {
00815         if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) {
00816             wfDebug( "Cannot follow redirects in safe mode\n" );
00817             return false;
00818         }
00819 
00820         if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) {
00821             wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" );
00822             return false;
00823         }
00824 
00825         return true;
00826     }
00827 }
00828 
00829 class PhpHttpRequest extends MWHttpRequest {
00830 
00835     protected function urlToTcp( $url ) {
00836         $parsedUrl = parse_url( $url );
00837 
00838         return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
00839     }
00840 
00841     public function execute() {
00842         wfProfileIn( __METHOD__ );
00843 
00844         parent::execute();
00845 
00846         if ( is_array( $this->postData ) ) {
00847             $this->postData = wfArrayToCgi( $this->postData );
00848         }
00849 
00850         if ( $this->parsedUrl['scheme'] != 'http' &&
00851              $this->parsedUrl['scheme'] != 'https' ) {
00852             $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] );
00853         }
00854 
00855         $this->reqHeaders['Accept'] = "*/*";
00856         $this->reqHeaders['Connection'] = 'Close';
00857         if ( $this->method == 'POST' ) {
00858             // Required for HTTP 1.0 POSTs
00859             $this->reqHeaders['Content-Length'] = strlen( $this->postData );
00860             if ( !isset( $this->reqHeaders['Content-Type'] ) ) {
00861                 $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded";
00862             }
00863         }
00864 
00865         // Set up PHP stream context
00866         $options = array(
00867             'http' => array(
00868                 'method' => $this->method,
00869                 'header' => implode( "\r\n", $this->getHeaderList() ),
00870                 'protocol_version' => '1.1',
00871                 'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0,
00872                 'ignore_errors' => true,
00873                 'timeout' => $this->timeout,
00874                 // Curl options in case curlwrappers are installed
00875                 'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0,
00876                 'curl_verify_ssl_peer' => $this->sslVerifyCert,
00877             ),
00878             'ssl' => array(
00879                 'verify_peer' => $this->sslVerifyCert,
00880                 'SNI_enabled' => true,
00881             ),
00882         );
00883 
00884         if ( $this->proxy ) {
00885             $options['http']['proxy'] = $this->urlToTCP( $this->proxy );
00886             $options['http']['request_fulluri'] = true;
00887         }
00888 
00889         if ( $this->postData ) {
00890             $options['http']['content'] = $this->postData;
00891         }
00892 
00893         if ( $this->sslVerifyHost ) {
00894             $options['ssl']['CN_match'] = $this->parsedUrl['host'];
00895         }
00896 
00897         if ( is_dir( $this->caInfo ) ) {
00898             $options['ssl']['capath'] = $this->caInfo;
00899         } elseif ( is_file( $this->caInfo ) ) {
00900             $options['ssl']['cafile'] = $this->caInfo;
00901         } elseif ( $this->caInfo ) {
00902             throw new MWException( "Invalid CA info passed: {$this->caInfo}" );
00903         }
00904 
00905         $context = stream_context_create( $options );
00906 
00907         $this->headerList = array();
00908         $reqCount = 0;
00909         $url = $this->url;
00910 
00911         $result = array();
00912 
00913         do {
00914             $reqCount++;
00915             wfSuppressWarnings();
00916             $fh = fopen( $url, "r", false, $context );
00917             wfRestoreWarnings();
00918 
00919             if ( !$fh ) {
00920                 break;
00921             }
00922 
00923             $result = stream_get_meta_data( $fh );
00924             $this->headerList = $result['wrapper_data'];
00925             $this->parseHeader();
00926 
00927             if ( !$this->followRedirects ) {
00928                 break;
00929             }
00930 
00931             # Handle manual redirection
00932             if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) {
00933                 break;
00934             }
00935             # Check security of URL
00936             $url = $this->getResponseHeader( "Location" );
00937 
00938             if ( !Http::isValidURI( $url ) ) {
00939                 wfDebug( __METHOD__ . ": insecure redirection\n" );
00940                 break;
00941             }
00942         } while ( true );
00943 
00944         $this->setStatus();
00945 
00946         if ( $fh === false ) {
00947             $this->status->fatal( 'http-request-error' );
00948             wfProfileOut( __METHOD__ );
00949             return $this->status;
00950         }
00951 
00952         if ( $result['timed_out'] ) {
00953             $this->status->fatal( 'http-timed-out', $this->url );
00954             wfProfileOut( __METHOD__ );
00955             return $this->status;
00956         }
00957 
00958         // If everything went OK, or we received some error code
00959         // get the response body content.
00960         if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) {
00961             while ( !feof( $fh ) ) {
00962                 $buf = fread( $fh, 8192 );
00963 
00964                 if ( $buf === false ) {
00965                     $this->status->fatal( 'http-read-error' );
00966                     break;
00967                 }
00968 
00969                 if ( strlen( $buf ) ) {
00970                     call_user_func( $this->callback, $fh, $buf );
00971                 }
00972             }
00973         }
00974         fclose( $fh );
00975 
00976         wfProfileOut( __METHOD__ );
00977 
00978         return $this->status;
00979     }
00980 }